Skip to content

runtime: core reliability — definitive resume, teardown + task-retention fixes (PR #122 stage D)#136

Merged
Meirtz merged 1 commit into
masterfrom
core/reliability
Jul 2, 2026
Merged

runtime: core reliability — definitive resume, teardown + task-retention fixes (PR #122 stage D)#136
Meirtz merged 1 commit into
masterfrom
core/reliability

Conversation

@Meirtz

@Meirtz Meirtz commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Fourth slice of the #122 split (stage map in the coordination comment; A = #133, B = #134, C = #135). Ports the "bucket A" core-reliability subset of the source branch's reliability refactor (b81f2fc) onto master — no wire-contract removals: the HTTP /call fast path, reconnection knobs, structured /log bridge, and codec ext types all stay (those belong to later stages).

What's in

Definitive resume (server)resume now resolves every id: cached result → replayed; still running → left alone; evicted/unknown → call:error type=ResultUnavailable instead of silence that hung the host's remote() forever. The completed-task transition is atomic w.r.t. resume: results are cached synchronously inside the completion callback batch (same loop turn as the calls pop), on both the SIO and HTTP submission paths — a window the source branch itself had, where a resume racing a completion misfired ResultUnavailable for a live result.

Task-retention fixes — result-emit tasks are strongly referenced (emit_tasks); the worker's cancel frame is enqueued synchronously; the worker shutdown drain is bounded (2s) so a wedged outbound pipe can't hang shutdown.

Client teardown — a disconnected stale SIO handle is shutdown() (not disconnect(), which is a library no-op on a dropped transport and never stops the reconnect loop) before a fresh client is built; close() gets the same treatment. Without this, every transport flap leaked an auto-reconnecting client + aiohttp session and eventually a second live /rpc socket.

Typed failure vocabulary — server-side cancellation raises CallCancelled (a RemoteCallError subclass), not a bare asyncio.CancelledError that reads as local task cancellation (intentional breaking change). New try_remote() -> Result[R] (Ok | Failed) on RuntimeClient and Sandbox for harnesses that branch on outcomes at scale. The HTTP fast path is now opportunistic — transport failures fall back to SIO instead of leaking raw httpx errors through try_remote()'s contract. A malformed call:error resolves to a typed MalformedError, not a KeyError.

Provider contractsession() deletes the sandbox even when aclose() raises (the container and its reserved port must never leak).

Docs — PROTOCOL.md documents resume/ack, the no-silent-loss invariant, and the client error mapping; docs/concepts/remote-calls.mdx updated.

Deliberately NOT ported

The single-transport migration (POST /call removal), reconnection-knob removal, boot_error cleanup, the /log rewrite, codec ext-type removal — and every "deletion" that was actually merge-base lag (the source branch forked before #117/#126#130/#132: test_extract, test_swe_env, docker-retry tests etc. all survive untouched).

Verification

  • uv run pytest tests/ plugins/ — 506 passed (10 new tests: ResultUnavailable + running-call resume, try_remote Ok/Failed, stale-handle shutdown ×2, fast-path fallback, CallCancelled mapping, MalformedError, session-delete contract, export guards). RED evidence captured for the key fixes (each fails against the pre-fix code).
  • uv run ruff check . / uv run pyright — clean / 0 errors
  • Adversarial review (2 perspectives + rebutters; the rate-limited perspective re-run standalone): 8 findings, all fixed here — including two the port itself would have shipped (disconnect() no-op teardown, HTTP-path resume window)

🤖 Generated with Claude Code

…ion fixes (PR #122 stage D)

Bucket-A slice of the origin/abridge/gateway-tito reliability work
(commit b81f2fc), ported surgically onto master with NO wire-contract
removals (the HTTP /call fast path, the /log structured bridge, and the
codec ext types all stay; those belong to later stages of the #122
split).

Server (runtime/server/sio.py):
- resume is now definitive: a cached result replays, a running call is
  left alone, and an evicted/unknown call_id gets
  call:error type=ResultUnavailable — never silence that hangs the
  host's remote() forever.
- the completed-task transition is atomic w.r.t. resume: the result is
  stored into pending_results synchronously inside the done-callback
  (same loop turn as the calls.pop), so a resume can never observe a
  completing call_id in neither map and misreport a live result as
  unavailable. (This window existed on the source branch; ported
  as-fixed.)
- result-emit tasks are strongly referenced (emit_tasks set) — a bare
  create_task in a done-callback can be GC'd before it runs, silently
  dropping a completed call's result. Applied to both the SIO path and
  the retained HTTP fast path's timeout branch.

Client (runtime/client/client.py):
- _ensure_sio tears down a disconnected stale handle before building a
  fresh one — overwriting it leaked the aiohttp session + reconnect
  task and could leave a second live /rpc socket.
- a malformed call:error with no error payload resolves to a typed
  MalformedError instead of a bare KeyError escaping remote().
- server-side cancellation now raises CallCancelled (RemoteCallError
  subclass), not a bare asyncio.CancelledError that reads as local
  task cancellation.
- new try_remote() -> Result[R] (Ok | Failed) for harnesses that
  branch on outcomes at scale; also on Sandbox.

Provider (provider/base.py):
- session() deletes the sandbox even when sandbox.aclose() raises —
  the container and its reserved port must never leak.

Worker (runtime/server/worker/process.py):
- shutdown drain is bounded (2s): a wedged outbound pipe would hang
  the queue join forever.
- cancel enqueues its Cancelled frame synchronously instead of via an
  untracked create_task that GC could drop.

Adversarial-review fixes on top of the port:
- the stale-handle teardown and close() use socketio's shutdown(), not
  disconnect() — disconnect() is a library no-op on a dropped transport
  and never stops the background reconnect task, so the leak/double-
  socket the teardown targets persisted.
- submit_http_call attaches its cache+emit callback at submission (not
  in the timeout branch), so the calls-pop/pending-store transition is
  in the same completion-callback batch on the HTTP path too; the
  HTTP-answered path self-acks its cached copy.
- the HTTP fast path is opportunistic: transport/status failures fall
  back to SIO instead of leaking raw httpx errors through remote() and
  try_remote()'s Result contract.
- docs/concepts/remote-calls.mdx + try_remote docstring updated for
  CallCancelled; client.py __all__ and test_public_exports extended.

Docs: PROTOCOL.md documents resume/ack, the no-silent-loss invariant,
and the CallCancelled/WorkerExited client mapping.

NOT ported (deliberately): the single-transport migration (POST /call
removal), reconnection-knob removal, boot_error cleanup, /log rewrite,
codec ext-type removal, and every master test the branch's merge-base
lag would have deleted (test_extract, test_swe_env, docker retry,
client options — all phantom deletions).

Co-authored-by: FatPigeorz <wjhhhhhha0@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Meirtz Meirtz merged commit 9e333fe into master Jul 2, 2026
5 checks passed
@Meirtz Meirtz deleted the core/reliability branch July 2, 2026 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant