gateway_helper_fastpath_test.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package handler
  2. import (
  3. "context"
  4. "sync/atomic"
  5. "testing"
  6. "time"
  7. "github.com/Wei-Shaw/sub2api/internal/service"
  8. "github.com/stretchr/testify/require"
  9. )
  10. type concurrencyCacheMock struct {
  11. acquireUserSlotFn func(ctx context.Context, userID int64, maxConcurrency int, requestID string) (bool, error)
  12. acquireAccountSlotFn func(ctx context.Context, accountID int64, maxConcurrency int, requestID string) (bool, error)
  13. acquireIngressLeaseFn func(ctx context.Context, apiKeyID int64, maxConnections int, leaseID string) (bool, error)
  14. releaseIngressLeaseFn func(ctx context.Context, apiKeyID int64, leaseID string) error
  15. releaseUserCalled int32
  16. releaseAccountCalled int32
  17. releaseIngressCalled int32
  18. }
  19. func (m *concurrencyCacheMock) AcquireAccountSlot(ctx context.Context, accountID int64, maxConcurrency int, requestID string) (bool, error) {
  20. if m.acquireAccountSlotFn != nil {
  21. return m.acquireAccountSlotFn(ctx, accountID, maxConcurrency, requestID)
  22. }
  23. return false, nil
  24. }
  25. func (m *concurrencyCacheMock) ReleaseAccountSlot(ctx context.Context, accountID int64, requestID string) error {
  26. atomic.AddInt32(&m.releaseAccountCalled, 1)
  27. return nil
  28. }
  29. func (m *concurrencyCacheMock) GetAccountConcurrency(ctx context.Context, accountID int64) (int, error) {
  30. return 0, nil
  31. }
  32. func (m *concurrencyCacheMock) GetAccountConcurrencyBatch(ctx context.Context, accountIDs []int64) (map[int64]int, error) {
  33. result := make(map[int64]int, len(accountIDs))
  34. for _, accountID := range accountIDs {
  35. result[accountID] = 0
  36. }
  37. return result, nil
  38. }
  39. func (m *concurrencyCacheMock) IncrementAccountWaitCount(ctx context.Context, accountID int64, maxWait int) (bool, error) {
  40. return true, nil
  41. }
  42. func (m *concurrencyCacheMock) DecrementAccountWaitCount(ctx context.Context, accountID int64) error {
  43. return nil
  44. }
  45. func (m *concurrencyCacheMock) GetAccountWaitingCount(ctx context.Context, accountID int64) (int, error) {
  46. return 0, nil
  47. }
  48. func (m *concurrencyCacheMock) AcquireUserSlot(ctx context.Context, userID int64, maxConcurrency int, requestID string) (bool, error) {
  49. if m.acquireUserSlotFn != nil {
  50. return m.acquireUserSlotFn(ctx, userID, maxConcurrency, requestID)
  51. }
  52. return false, nil
  53. }
  54. func (m *concurrencyCacheMock) ReleaseUserSlot(ctx context.Context, userID int64, requestID string) error {
  55. atomic.AddInt32(&m.releaseUserCalled, 1)
  56. return nil
  57. }
  58. func (m *concurrencyCacheMock) GetUserConcurrency(ctx context.Context, userID int64) (int, error) {
  59. return 0, nil
  60. }
  61. func (m *concurrencyCacheMock) IncrementWaitCount(ctx context.Context, userID int64, maxWait int) (bool, error) {
  62. return true, nil
  63. }
  64. func (m *concurrencyCacheMock) DecrementWaitCount(ctx context.Context, userID int64) error {
  65. return nil
  66. }
  67. func (m *concurrencyCacheMock) GetAccountsLoadBatch(ctx context.Context, accounts []service.AccountWithConcurrency) (map[int64]*service.AccountLoadInfo, error) {
  68. return map[int64]*service.AccountLoadInfo{}, nil
  69. }
  70. func (m *concurrencyCacheMock) GetUsersLoadBatch(ctx context.Context, users []service.UserWithConcurrency) (map[int64]*service.UserLoadInfo, error) {
  71. return map[int64]*service.UserLoadInfo{}, nil
  72. }
  73. func (m *concurrencyCacheMock) CleanupExpiredAccountSlots(ctx context.Context, accountID int64) error {
  74. return nil
  75. }
  76. func (m *concurrencyCacheMock) CleanupExpiredAccountSlotKeys(ctx context.Context) error {
  77. return nil
  78. }
  79. func (m *concurrencyCacheMock) CleanupStaleProcessSlots(ctx context.Context, activeRequestPrefix string) error {
  80. return nil
  81. }
  82. func (m *concurrencyCacheMock) AcquireOpenAIWSIngressLease(ctx context.Context, apiKeyID int64, maxConnections int, leaseID string) (bool, error) {
  83. if m.acquireIngressLeaseFn != nil {
  84. return m.acquireIngressLeaseFn(ctx, apiKeyID, maxConnections, leaseID)
  85. }
  86. return false, nil
  87. }
  88. func (m *concurrencyCacheMock) RefreshOpenAIWSIngressLease(context.Context, int64, string) (bool, error) {
  89. return true, nil
  90. }
  91. func (m *concurrencyCacheMock) ReleaseOpenAIWSIngressLease(ctx context.Context, apiKeyID int64, leaseID string) error {
  92. atomic.AddInt32(&m.releaseIngressCalled, 1)
  93. if m.releaseIngressLeaseFn != nil {
  94. return m.releaseIngressLeaseFn(ctx, apiKeyID, leaseID)
  95. }
  96. return nil
  97. }
  98. func TestConcurrencyHelper_TryAcquireUserSlot(t *testing.T) {
  99. cache := &concurrencyCacheMock{
  100. acquireUserSlotFn: func(ctx context.Context, userID int64, maxConcurrency int, requestID string) (bool, error) {
  101. return true, nil
  102. },
  103. }
  104. helper := NewConcurrencyHelper(service.NewConcurrencyService(cache), SSEPingFormatNone, time.Second)
  105. release, acquired, err := helper.TryAcquireUserSlot(context.Background(), 101, 2)
  106. require.NoError(t, err)
  107. require.True(t, acquired)
  108. require.NotNil(t, release)
  109. release()
  110. require.Equal(t, int32(1), atomic.LoadInt32(&cache.releaseUserCalled))
  111. }
  112. func TestConcurrencyHelper_TryAcquireAccountSlot_NotAcquired(t *testing.T) {
  113. cache := &concurrencyCacheMock{
  114. acquireAccountSlotFn: func(ctx context.Context, accountID int64, maxConcurrency int, requestID string) (bool, error) {
  115. return false, nil
  116. },
  117. }
  118. helper := NewConcurrencyHelper(service.NewConcurrencyService(cache), SSEPingFormatNone, time.Second)
  119. release, acquired, err := helper.TryAcquireAccountSlot(context.Background(), 201, 1)
  120. require.NoError(t, err)
  121. require.False(t, acquired)
  122. require.Nil(t, release)
  123. require.Equal(t, int32(0), atomic.LoadInt32(&cache.releaseAccountCalled))
  124. }