Skip to content

feat: graceful run termination reports real results on abort (#36) - #41

Merged
schplitt merged 1 commit into
mainfrom
feat/graceful-terminate
Jul 25, 2026
Merged

feat: graceful run termination reports real results on abort (#36)#41
schplitt merged 1 commit into
mainfrom
feat/graceful-terminate

Conversation

@schplitt

Copy link
Copy Markdown
Owner

Closes #36.

Problem

Aborting a run destroyed the socket; Rust saw EOF and reclaimed the isolate but never sent a Result frame, so the TypeScript side fabricated the aborted RunResult (durationMs: 0, cpuTimeMs: 0, bridgeCalls: []). Because durable-isolates suspension is implemented as an abort, every suspension lost its timings and per-call bridge records.

Approach

  • TS sends the existing Terminate frame (0x07) carrying the run ID instead of tearing the socket down.
  • Rust, parked in the run's bridge-wait poll loop (exactly where a suspension sits — awaiting a bridge response), consumes Terminate, terminate_execution()s, and returns a real ERR_ABORTED Result with durationMs, cpuTimeMs, and the bridge-call records collected so far.
  • TS waits a short grace period (100 ms). On success the connection stays healthy and is reused (no reconnect); index.ts remaps the ERR_ABORTED Result to status: 'aborted' with the abort reason, keeping the telemetry. If no Result arrives (sandbox stuck in a tight synchronous loop, so Rust never reaches the frame read), it falls back to today's socket teardown + synthesized zeros.

Prompt interruption of a purely CPU-bound (busy) isolate stays deferred per DESIGN.md §14.7 — the CPU guard remains the backstop; a code comment at the poll-loop site documents this.

Changes

  • Rust: parse_terminate_payload (ipc.rs); RunError::Aborted + poll-loop Terminate handling (v8.rs); ERR_ABORTED/AbortError mapping (wire.rs); session main-loop now ignores a stray Terminate instead of closing (keeps the slot poolable).
  • TS: encodeTerminatePayload (ipc.ts); graceful terminate + grace/fallback in drainUntilResult (client.ts); graceful-abort remap in index.ts; stale-comment fixes in types.ts.
  • Docs: protocol.md (ERR_TERMINATEDERR_ABORTED), DESIGN.md §5.2/§14.7; changeset (minor).

Testing

  • Rust: 211 passed — incl. new terminate_frame_aborts_run_with_telemetry, parse_terminate_payload, aborted_maps_to_err_aborted.
  • TS: 454 passed / 1 skipped — converted the "aborted runs carry no bridge report" test to assert graceful telemetry; added global + import-leaf abort-telemetry coverage.
  • pnpm fmt:native:check, pnpm lint, pnpm typecheck clean.

Note: pnpm lint:native (clippy -D warnings) reports 15 pre-existing warnings in untouched code (dead-code/complexity lints); this diff adds none.

Aborting a run now sends a Terminate frame (with the run ID) instead of
immediately destroying the socket. Rust, parked in its bridge-wait poll
loop, consumes it, terminates the run, and returns a real ERR_ABORTED
Result carrying durationMs, cpuTimeMs, and the bridge-call records
collected so far — so aborted runs (including every durable-isolates
suspension) keep their telemetry instead of synthesized zeros.

TS waits a short grace period (100ms) for that Result and reuses the now
still-healthy connection; if none arrives (sandbox stuck in a tight
synchronous loop, so Rust never reads the frame) it falls back to
today's socket teardown + synthesized result. Prompt interruption of a
busy isolate remains deferred per DESIGN.md §14.7.
@schplitt
schplitt merged commit 9332cca into main Jul 25, 2026
1 check passed
@schplitt
schplitt deleted the feat/graceful-terminate branch July 25, 2026 17:57
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.

Graceful terminate: aborted runs should get a real result from Rust

1 participant