Skip to content

Commit e936f71

Browse files
committed
fix account usage limit refresh handling
1 parent c4773fa commit e936f71

19 files changed

Lines changed: 682 additions & 88 deletions

api/docs/errors.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ All API errors follow this structure:
5757
|------|-------------|
5858
| `server_error` | Internal server error |
5959
| `service_unavailable` | Service temporarily unavailable |
60+
| `no_available_account` | No account is currently available for dispatch |
6061
| `upstream_error` | Error from upstream service |
6162
| `upstream_timeout` | Request to upstream timed out |
6263

api/openapi.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ components:
7070
- rate_limit_reached
7171
- server_error
7272
- service_unavailable
73+
- no_available_account
74+
- account_pool_usage_limit_reached
7375
- upstream_error
7476
- upstream_timeout
7577
- resource_not_found

auth/session_affinity_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package auth
22

33
import (
44
"context"
5+
"sync/atomic"
56
"testing"
67
"time"
78
)
@@ -192,6 +193,52 @@ func TestWaitForSessionAvailableRespectsExcludeSet(t *testing.T) {
192193
}
193194
}
194195

196+
func TestWaitForSessionAvailableReturnsImmediatelyWhenNoDispatchCandidate(t *testing.T) {
197+
store := &Store{
198+
accounts: []*Account{},
199+
maxConcurrency: 1,
200+
}
201+
202+
start := time.Now()
203+
acc, proxyURL := store.WaitForSessionAvailable(context.Background(), "", 2*time.Second, 0, nil)
204+
elapsed := time.Since(start)
205+
206+
if acc != nil {
207+
t.Fatalf("account = %+v, want nil", acc)
208+
}
209+
if proxyURL != "" {
210+
t.Fatalf("proxyURL = %q, want empty", proxyURL)
211+
}
212+
if elapsed > 150*time.Millisecond {
213+
t.Fatalf("WaitForSessionAvailable took %s with no dispatch candidates; want fast failure", elapsed)
214+
}
215+
}
216+
217+
func TestWaitForSessionAvailableKeepsWaitingWhenCandidateIsBusy(t *testing.T) {
218+
account := &Account{DBID: 1, AccessToken: "tok-1"}
219+
store := &Store{
220+
accounts: []*Account{account},
221+
maxConcurrency: 1,
222+
}
223+
atomic.StoreInt64(&account.ActiveRequests, 1)
224+
225+
go func() {
226+
time.Sleep(75 * time.Millisecond)
227+
store.Release(account)
228+
}()
229+
230+
acc, proxyURL := store.WaitForSessionAvailable(context.Background(), "", 500*time.Millisecond, 0, nil)
231+
if acc == nil {
232+
t.Fatal("expected busy candidate to become available")
233+
}
234+
if acc.DBID != 1 {
235+
t.Fatalf("account DBID = %d, want %d", acc.DBID, 1)
236+
}
237+
if proxyURL != "" {
238+
t.Fatalf("proxyURL = %q, want empty", proxyURL)
239+
}
240+
}
241+
195242
func TestUnbindSessionAffinityRemovesMatchingBinding(t *testing.T) {
196243
store := &Store{
197244
accounts: []*Account{

0 commit comments

Comments
 (0)