diff --git a/docs/plans/v1/_files/internal/limiter/limiter_test.go b/docs/plans/v1/_files/internal/limiter/limiter_test.go index bf90588..6ab30e2 100644 --- a/docs/plans/v1/_files/internal/limiter/limiter_test.go +++ b/docs/plans/v1/_files/internal/limiter/limiter_test.go @@ -32,10 +32,10 @@ func TestParallelAndQueue(t *testing.T) { go func() { rel, waited, err := l.Acquire(ctx, "alpha", "m") if err == nil { - defer rel() if waited < 40*time.Millisecond { err = errors.New("third acquire did not wait") } + rel() // release before reporting, so the final count check cannot race it } got3 <- err }() diff --git a/docs/plans/v1/_files/internal/proxy/proxy_test.go b/docs/plans/v1/_files/internal/proxy/proxy_test.go index c1bb26a..39980aa 100644 --- a/docs/plans/v1/_files/internal/proxy/proxy_test.go +++ b/docs/plans/v1/_files/internal/proxy/proxy_test.go @@ -85,6 +85,19 @@ func TestDifferentConversationsSpreadByFreeSlots(t *testing.T) { } } +// waitUntil polls cond every 5 ms for up to two seconds and fails the test if it never holds. +func waitUntil(t *testing.T, cond func() bool) { + t.Helper() + deadline := time.Now().Add(2 * time.Second) + for time.Now().Before(deadline) { + if cond() { + return + } + time.Sleep(5 * time.Millisecond) + } + t.Fatal("condition not reached within two seconds") +} + func TestQueueFullIs503(t *testing.T) { alpha := newUpstream(t, "alpha") alpha.delay = 400 * time.Millisecond @@ -99,14 +112,20 @@ hosts = ["alpha"] default_model = "shared" `, alpha) codes := make(chan int, 3) - for i := 1; i <= 3; i++ { - go func(i int) { + fire := func(i int) { + go func() { resp := r.post("/r/v1/chat/completions", conversation(i, 1)) drain(resp) codes <- resp.StatusCode - }(i) - time.Sleep(30 * time.Millisecond) // arrival order: 1 runs, 2 queues, 3 finds the queue full + }() } + // Arrival order is enforced by watching the limiter, not by sleeping: 1 runs, 2 queues, + // 3 finds the queue full. + fire(1) + waitUntil(t, func() bool { return r.lim.InFlight("alpha", "shared") == 1 }) + fire(2) + waitUntil(t, func() bool { return r.lim.Queued("alpha", "shared") == 1 }) + fire(3) got := map[int]int{} for i := 0; i < 3; i++ { got[<-codes]++ diff --git a/docs/plans/v2/README.md b/docs/plans/v2/README.md index 5194a88..8e5c75a 100644 --- a/docs/plans/v2/README.md +++ b/docs/plans/v2/README.md @@ -82,3 +82,17 @@ reason. per argv element, so check 3 could never pass. Test fault (mine): the body now goes through a file (`-d @file`). Ornith diagnosed it correctly. Resumed from the working tree with the corrected script. +- 2026-09-25, task 05, third session: `TestParallelAndQueue` (v1 given `limiter_test.go`) failed + once under full-suite `-race` load with `after releases: inflight 1 queued 0`. Test fault + (mine): the third acquirer sent its result before its deferred release ran, so the final + count check could observe one slot still held. The given file now releases before reporting. + Ornith found it and measured the flake rate rather than editing the protected file. +- 2026-09-25, task 05, third session: also saw `TestQueueFullIs503` fail with + `Requests:3 Errors:2`. Two causes. (1) Test fault (mine): arrival order rested on 30 ms + sleeps; the given test now waits on the limiter's in-flight and queued counts. (2) A real v1 + defect, verified by the owner with a diagnostic build (5 of 8 runs): after a forward completes, + `forward.go` checks `r.Context().Err()` and, when the client has already closed its connection, + records a served 200 as a 499 "client cancelled" error. Cancellation must be what the reverse + proxy itself observed, never a post-hoc context check. Scheduled as v2.1 task 01; not fixed in + task 05, which is wiring only. The session then ended on a refused read of `/proc/loadavg` — + the seventh refusal-ending. Model fault.