fix(cag): resolve data races, add queue observability, fix test coverage
Cycle 2 fixes from reviewer-sre + reviewer-quality: - scheduler.go: remove delete() under RLock (data race); expired entries skipped on read, cleaned lazily by next MarkFailed write - proxy.go: pre-populate transports for scheduler backends at startup to prevent concurrent map writes from ServeHTTP goroutines - proxy.go: log + IncrQueueFull() on queue-full 503 path (was silent) - proxy.go: remove stub total_tokens/hit_rate from cache-hit log event - serviceadapter/router.go: Warnf when Auth.Required but jwtValidator nil - observability: add queue_full_requests_total counter + Prometheus export - scheduler_test.go: TestBackendAcquireCancelledContext (ctx cancel branch) - proxy_test.go: implement TestWarmupHeaderHandling (remove t.Skip) - router_test.go: TestAuthenticate_MethodCapabilityOverrideDirect (auth coverage)
This commit is contained in:
@@ -100,6 +100,57 @@ func TestSchedulerSelectNoKeyRoundRobin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBackendAcquireCancelledContext verifies that Acquire returns ctx.Err() when the
|
||||
// context is cancelled while the caller is blocked waiting for a concurrency slot,
|
||||
// and that the queue depth returns to 0 after cancellation.
|
||||
func TestBackendAcquireCancelledContext(t *testing.T) {
|
||||
b := newBackend("host:1", 1, 1)
|
||||
|
||||
// G1: acquire and hold the only slot indefinitely.
|
||||
if err := b.Acquire(context.Background()); err != nil {
|
||||
t.Fatalf("G1 Acquire: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
g2Done := make(chan error, 1)
|
||||
go func() {
|
||||
g2Done <- b.Acquire(ctx)
|
||||
}()
|
||||
|
||||
// Give G2 time to enter the queue and block waiting for a slot.
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
|
||||
// Verify G2 is queued (queue channel has one item).
|
||||
if qd := len(b.queue); qd != 1 {
|
||||
t.Errorf("expected queue depth 1 while G2 blocked, got %d", qd)
|
||||
}
|
||||
|
||||
// Cancel ctx while G2 is waiting for the slot.
|
||||
cancel()
|
||||
|
||||
select {
|
||||
case err := <-g2Done:
|
||||
if err == nil {
|
||||
t.Fatal("G2 Acquire: expected ctx.Err(), got nil")
|
||||
}
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("G2 Acquire: expected context.Canceled, got %v", err)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("G2 did not return within 1s after context cancel")
|
||||
}
|
||||
|
||||
// Queue depth must return to 0 after G2 is drained.
|
||||
if qd := len(b.queue); qd != 0 {
|
||||
t.Errorf("expected queue depth 0 after cancellation, got %d", qd)
|
||||
}
|
||||
|
||||
// Cleanup: release G1's slot.
|
||||
b.Release()
|
||||
}
|
||||
|
||||
// TestSchedulerMarkFailedExcludesBackend verifies that a failed backend is not
|
||||
// selected within the fail window (30 s default).
|
||||
func TestSchedulerMarkFailedExcludesBackend(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user