Cancellation/disconnect semantics: close gaps outside the established-stream path #165
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Creatidy/scarcity-router#165
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Provenance: independent review evidence deferred from #143 / PR #164 (merged as
db4e1bdondevelop). PR #164 itself is complete — it was a test-only synchronization fix for the scenario-11 flake and changed no production semantics. This issue captures the production cancellation paths its review surfaced but explicitly did not change or close.Confirmed-good path (proven; do not redesign)
For an established response stream, client disconnect detected during chunk emission reaches the normal cancellation path end to end:
_StreamState.emit()(scarcity_router/gateway_server.py) checks_client_gone()before each emission and raisesClientDisconnectedErroron client disappearance; write failures inside_write_frame()are translated to the same error (gateway_server.py:732-740);cancel_eventon catch (scarcity_router/gateway_coordinator.py:804-806,1782-1783);WorkerBridgedAdapter's ordinary chunk-emission branch catchesClientDisconnectedError, sends worker cancellation, then rethrows (scarcity_router/worker_bridged_adapter.py:212-223);WorkerSession.send_cancel()(scarcity_router/worker_endpoint.py:387) synchronously writes the cancellation frame.PR #164 fixed only the test synchronization around this behavior; production semantics on this path are unchanged and were not the defect.
Deferred paths to investigate/fix
1. Coordinator
cancel_eventtiming vspending.take()In
_await_outcome()(scarcity_router/worker_bridged_adapter.py:200-210), the top-of-loop cancel-send check runs beforepending.take()blocks. Ifcancel_eventbecomes set while blocked intake(), the subsequentchunkbranch returnsAdapterResult(status="cancelled")(worker_bridged_adapter.py:209-210) without calling_send_cancel_best_effort()— the loop never reaches the next top-of-loop check. The prior independent review used a deterministic probe and observed zero cancel-send calls on this path.Inspect the analogous continuation loop
_await_resume()(worker_bridged_adapter.py:430-441), which has the identical shape.2. Header-write failure
_StreamState._begin()(scarcity_router/gateway_server.py:674-684) emits status and headers viasend_response()/send_header()/end_headers(), which sit outside_write_frame()'sClientDisconnectedErrortranslation (gateway_server.py:732-740). ABrokenPipeErrorduring header emission can escape through the generic unwind as a rawOSError: the adapter's cancel-before-rethrow catch never matches, and the coordinator'scancel_eventis never set. The prior review reproduced this with a deterministic probe.3. Initial tool-suspension emissions
The suspension branch of
_await_outcome()(scarcity_router/worker_bridged_adapter.py:257-269) and of_await_resume()(worker_bridged_adapter.py:474ff.) callcontext.emit_chunk(...)for the tool-call and finish frames outside anyClientDisconnectedErrorcatch — unlike the ordinary chunk branch. A client disconnect during these early emissions is therefore not guaranteed an immediate cancel-before-unwind.4. Worker connection loss (contract clarification)
Clarify and test the intended contract when the worker transport itself disappears mid-attempt. Current behavior is best-effort cancellation with ambiguous-outcome handling:
worker_connection_lostat submit maps toAdapterAmbiguousError(worker_bridged_adapter.py:176-189), and aninterruptedoutcome raisesAdapterAmbiguousError(worker_bridged_adapter.py:303-309). Ambiguity handling may be the correct behavior here — do not assume that guaranteed cancel delivery is required.Required outcome
The future implementation task must first define the intended invariant for each of the four paths (recording any new decision in
docs/decisions.md), then:Non-goals: no changes to #143's merged test synchronization; no re-litigation of PR #164, which is done.
kilo referenced this issue2026-10-04 23:43:29 +00:00
2026-10-27 owner backlog disposition (D-083, governance issue #228). This issue remains open with its
wontfixlabel unchanged; history preserved. The 2026-10-27 reconciliation re-verified all four recorded paths againstdevelop4a182affa477: paths 1–3 remain present as described (path 2 additionally duplicated by_ResponsesStreamState._write_eventsadded with #181/D-080 on 2026-10-25), and path 4 (ambiguous-outcome contract) is intentional and preserved. The defects are re-homed to active successor #225 with owner-declared priority P1; the established-stream cancellation path and PR #164's merged test synchronization remain proven and untouched. See D-083 indocs/decisions.md.