ReviewOS

home-lang/home

Worker heap snapshots: settle pending requests during termination

#471
Closed chris opened this 8 days ago ·
8 days ago

Current status (2026-08-27)

Implemented and verified in c6a4e4a3513dfb2a05a13357c06f598ad9f1d6ce on codex/runtime-port, authored and committed by Chris Breuer without coauthors. Published, not merged into main; this issue remains open for integration tracking. The pending probe is promoted to tests/runtime/native-worker-heap-snapshot.test.mjs. See the checkpoint comment for complete verification and remaining boundaries (#465, #468, #481, #482).

Original reproduction before this checkpoint

Follow-up to #66, #461, and #465. The bounded worker-heap-snapshot-cancellation.pending.mjs probe queues a snapshot while its worker is blocked, terminates the worker, then checks promise settlement. Home leaves the promise pending until the explicit deadline. The linked JSWorker.cpp captures a raw pointer to a parent-owned Strong promise handle; queued task destruction drops that pointer without parent-thread cleanup.

Implement cancellation-aware ownership for the complete round trip. Settle or cancel requests on the parent thread, release the Strong handle there, and handle parent shutdown without cross-thread HandleSet mutation. General task deletion alone is insufficient. The probe permits either a resolved readable stream or ERR_WORKER_NOT_RUNNING rejection; it checks settlement, not snapshot payload correctness. Node can resolve despite termination. Consuming the stream during the race caused a Node 24.18.0 crash, so that crash is not a required behavior or a passing control.

8 days ago

Validation correction: the Node 24.18.0 snapshot/termination race also SIGSEGVed after immediate stream destruction, even after printing the settlement message. That run is not a pass. No further crash repeats are needed. Home consistently fails its bounded settlement deadline with status 1. The probe checks settlement and stream interface only; snapshot payload validation is separate. Do not require rejection exclusively: Node may resolve before its later crash.

8 days ago

Published c6a4e4a3513dfb2a05a13357c06f598ad9f1d6ce on codex/runtime-port, authored and committed by Chris Breuer without coauthors. Not merged into main. Related #66.

Ownership change

Home now owns JSWorker.cpp through ABI-validated source replacement. A locked request registry owns the parent VM's Strong promise handle and any completed JSON payload; cross-thread tasks carry unique IDs only. Worker exit settles requests on the live parent thread. Main/worker VM shutdown releases their requests before JSC destruction without running JS reactions. Late notifications become no-ops. Successful requests still produce real V8-format snapshot JSON and readable streams.

Verification

  • Native build and cached rebuild: 16/16. Shared-cache missing-object failures required isolated local/global caches; failed attempts are not passes. Commands are in docs/docs/NATIVE_RUNTIME_BINDINGS.md.
  • All 20 native regression files pass. The snapshot suite passes four complete six-scenario runs, including two with BUN_DESTRUCT_VM_ON_EXIT=1: six queued cancellations, four pending requests during self-exit, nine concurrent valid snapshots under GC, nested-parent shutdown with blocked/running children, and main-VM destruction. Stopped-worker rejection and a live snapshot sentinel are checked.
  • The cancellation case fails on the prior installed binary with the original unresolved-promise timeout. Tests explicitly await online and schedule blocking work after startup, proving admitted requests rather than pre-start rejection. Five-run ordering controls show Home and Bun both allow a message before online while top-level code is running; Node differs.
  • Sixteen byte-identical upstream Node worker fixtures pass, including the normal snapshot JSON fixture. Node passes that fixture and the selected nine-snapshot concurrent-GC success scenario. No passing Node termination-race control is claimed; the earlier Node crash is not required behavior.
  • The byte-identical upstream snapshot-GC test and fixture pass the full 15 attempts × 300 completed snapshots = 4,500 workload, with 15 assertions, natively in about 65 seconds. The fixture uses process.execPath, which is Home. No workload reduction or sanitizer claim.
  • Strict native MessagePort corpus: 15 pass / 3 existing guard skips / 0 fail / 529 assertions. Both native execution flags were set. Generator/ABI/source-contract tests: 12 pass / 135 assertions; Zig helper tests: 4/4; focused lint/format checks pass.

Remaining boundaries

The full worker_threads file reports 33 pass / 1 timeout / 1 between-test error despite its three snapshot cases passing. Native CLI filtering is missing (#481), and the unchanged 100 MiB eval-source case exceeds its original five-second deadline (#482). The exact standalone leak fixture passes its memory threshold in native debug at 20.22 seconds; this is not proof of a retained-source leak. Neither full-file success nor complete Bun parity is claimed. General task allocation reclamation (#465), producer quiescence (#468), other-platform verification, and integration into main remain separate.

8 days ago

Integrated status verified on main at b4ddf30. The implementation commit c6a4e4a is an ancestor of main. In the current ReleaseFast installation-shaped runtime, tests/runtime/native-worker-heap-snapshot.test.mjs passes all six ownership scenarios, including exact settlement of six cancelled requests, four self-exit requests, nine concurrent snapshots under parent GC, blocked/running nested snapshot children, and main-VM destruction. No promise-settlement workaround or relaxed deadline was added. Closing the stale integration tracker; generic native task auditing remains #465.

Sign in to comment on this issue.