Scenario 11: synchronize cancel assertion on the worker cancel record (#143) #164
No reviewers
Labels
No labels
accessibility
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
P1
P2
P3
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Creatidy/scarcity-router!164
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/143-scenario11-cancel-race"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #143. Test-only synchronization fix; zero production changes.
Root cause (reproduced, instrumented)
Both scenario-11 cancellation tests asserted
worker.cancelsimmediately after the drain loop observed client-socket EOF. The production ordering is correct — the HTTP handler thread runs the dispatch synchronously, so when chunk 2 trips_client_gone(), the adapter writes the cancel message to the worker socket before the handler unwinds and the client connection closes. What is not ordered is the worker-sideScriptedWorkerreader thread receiving, parsing and appending that cancel toworker.cancels.Instrumented reproduction on this branch's base (2026-09-30,
17f4918a, otherwise-pristineCancellationTestsloop): 6 failures across ~60 executions (1/8, 2/12, and 3 in a diagnostic loop), every one with:The drain exited via the EOF
breakand the cancel landed inworker.cancels~1.1 ms later. Valid asynchronous behavior observed too early — a test synchronization defect, not a production semantics defect. (Matches the original full-suite-load evidence oncompat/logical-model-resolutionand the 2026-09-28contract/reasoning-effort-serializationvariant recorded in the issue comment.)Fix
tests/test_e2e_execution.py, both scenario-11 tests: after the drain, the assertion synchronizes on the real observable contract —wait_until(lambda: bool(worker.cancels), timeout=<remaining pre-existing 15 s budget>, message="cancel never reached the worker")— instead of inferring delivery from socket EOF. The drain (realistic client read-until-close) is kept; EOF is no longer load-bearing for the assertion.Explicitly not done: no sleeps, no timeout bumps, no blind retries, no weakened assertion (the cancel must still reach the owning worker — and in the second test, only the owning worker — within the same 15 s budget, or the test fails with the same message), no production changes.
Validation
test_e2e_executionmodule running concurrently as loadtest_e2e_executionmodule (12 tests, under that same concurrent load)make checkuv run basedpyrightgit diff --checkDiscrimination: the old assertion failed ~10% of executions on this machine under the same conditions; the synchronized version passed 60/60 under equal-or-worse load, and the whole suite passed end to end.
Scope audit
tests/test_e2e_execution.py(+22/−5), entirely inside the two scenario-11 tests.wait_until's 20 ms poll is granularity, not the mechanism).Residual risk
Low. The wait is bounded and ordered after a production write that provably precedes the client close, so a genuine regression (cancel never sent) still fails the test — within the same 15 s budget, with the same message. An independent concurrency review should confirm the one remaining assumption: that
_send_cancel_best_effortwrites the cancel frame before the handler thread closes the client socket on every disconnect path (chunk-emission EOF, write-error/RST, and the coordinator'scancel_eventroute), not only the path exercised here.UX impact: none (test-only).
origin/develop@17f4918a49139c6f539dc073695308cc7516f50f1176af3(single commit, single file)Both blocking findings remediated on HEAD
6c1517c7aae0a5aa4187279d3be87e7d7066f2a4(reviewed head was1176af3). PR left open; no merge performed.Blocker 1 — condition-first bounded wait
wait_until(shared helper,tests/m10_fixtures.py) checked the clock before the predicate, so a zero or exhausted remaining budget raisedAssertionErrorwithout ever evaluating an already-satisfied condition. The helper is now condition-first:The predicate is evaluated before any timeout decision; an already-satisfied predicate succeeds even at zero remaining budget; a false predicate fails immediately at the original deadline (no extension, no sleep on the failure path). The 15 s scenario-11 budget is unchanged.
Why the shared helper rather than a local change: the clock-first flaw belongs to the helper's contract — any caller passing a computed remaining budget (exactly the pattern both scenario-11 tests use) can hit it; a local workaround would hand-roll a second bounded-wait implementation in two tests; and condition-first semantics are strictly stronger for every caller with no false-pass risk (predicate and hard deadline unchanged). Per the review instruction, the full gate was run for the other callers: all four e2e suites import this helper (
test_e2e_execution,test_e2e_codex_acceptance,test_e2e_zcode_acceptance,test_e2e_sources_acceptance) and all are covered by the fullmake check(2496 tests OK). The three other module-localwait_untilcopies (test_worker_client.py,test_worker_endpoint.py,test_worker_bridged_adapter.py) are separate helpers, untouched.Deterministic boundary coverage added in
tests/test_wait_until.py(4 tests, 3 ms total): already-true predicate with zero budget (asserts exactly one evaluation — the old code raised without looking), already-true with near-zero budget, false predicate at exhausted budget failing immediately (asserts no budget-sized wait and message preserved), and the not-true-at-entry path becoming true (predicate-driven, clock-independent).Blocker 2 — expected-attempt discrimination
Both scenario-11 tests now wait on
lambda: execute.attempt_id in worker.cancels(drain-loop guard likewise), using the real attempt ID from that test'snext_execute. An unrelated cancellation can never satisfy the wait — the test fails at the deadline withcancel never receivedif only unrelated cancels land. Proven by a wrong-attempt diagnostic run (not committed): with the predicate pointed at a bogus ID, the test FAILED withcancel never reached the workereven though the real cancellation for the actual attempt arrived in that window.Validation
test_e2e_executionmodule: 12/12 under concurrent load.make check(exact gate commands): 2496 tests OK + basedpyright 0 errors/0 warnings/0 notes +git diff --checkclean.Pre-existing flake encountered (not fixed here — out of scope)
One intermediate
make checkrun failed ontests/test_openai_codex_acquisition.py: the harness asserts captured stderr is empty (line 416), but the fixture's_feedfeeder thread can crash inside the capture window —_close_write()joins the feeder withtimeout=5.0; if the pipe is full the feeder is in its retry sleep, the join times out, the write fd is closed, and the feeder's nextos.writeraises EBADF, whichthreadingprints asException in thread Thread-924 (_feed)into the redirected stderr. Evidence it is pre-existing and unrelated to this diff: the module imports nothing changed here; in-isolation loops pass 20/20 on BOTH pristinedevelopand this branch; and a full-suite run on pristinedevelop@17f4918afailed (1 failure) under concurrent load while this branch's suite passed 2496/2496 in the same conditions. Same load-sensitive family as #143, different module and mechanism. Left untouched per scope (no unrelated cleanup) — it merits its own issue.Scope confirmation: the review's DEFER items (coordinator
cancel_eventtiming, header-writeBrokenPipeError, initial tool-suspension emissions, worker-connection-loss semantics) were NOT implemented. Diff:tests/m10_fixtures.py(+12/−3),tests/test_e2e_execution.py(+9/−4),tests/test_wait_until.py(new, +57). UX impact: none (test-only).