ReviewOS

home-lang/home

Worker parentPort: implement close events and event-loop ref ownership

#477
Closed chris opened this 11 days ago ·
11 days ago

Follow-up to #66, #461, #465, and #470.

The strengthened nested-worker normal-exit control exposed that Home's embedded node:worker_threads parentPort still uses fakeParentPort with no-op close, ref, and unref. Calling close with a message listener attached leaves the worker event loop alive; no close event fires. This is independent of descendant joining.

Reproduction is committed as tests/runtime/worker-parent-port-close.pending.mjs: a worker registers a message listener and close listener, sends a handshake, and closes its parentPort. Expected: natural exit 0 and exactly one close callback. Current native Home: a bounded 3-second deadline failure, exit 1 after bounded worker cleanup. The exact Node control (only executable guard removed) passes.

Implement the native parent-port lifecycle and ref ownership, preserving outgoing accepted messages and rejecting/dropping post-close delivery appropriately. Include listener cleanup, close-event ordering, ref/unref/hasRef, repeated close, and worker shutdown. Do not alias close to process.exit: other worker resources may keep it alive, and close is asynchronous.

The #470 descendant regression uses a one-shot message listener for its normal control so that it tests descendant exit independently. The close failure above is kept explicit and is not counted as passing coverage.

11 days ago

The explicit native failure and matching Node control are now published in commit 41f10cb on codex/runtime-port: tests/runtime/worker-parent-port-close.pending.mjs. Home exits 1 after the bounded natural-exit deadline; Node exits 0 and observes exactly one close event. This issue remains open and is linked from #66 and #470.

11 days ago

Implemented on published branch codex/runtime-port in 3b0721c32b2d237c38380900ed449217ef1af853, authored and committed by Chris Breuer without coauthors. Not merged into main; leaving this issue open for integration tracking. Related #66, #470, #461.

Implementation

  • Replace fakeParentPort with a real native transferable MessagePort. Preserve pre-start FIFO, synchronous receive, outgoing accepted messages, structured-clone/transfer validation, and Web Worker global message events.
  • Native close, ref/unref, listener ownership, and owner-thread transport cleanup now participate in worker lifetime. Closing the port is asynchronous and does not kill unrelated timers/resources.
  • Own the worker-global-scope and JSMessagePort bindings through ABI-validated source replacement; Worker and MessagePort class layouts remain unchanged.
  • Correct close-event callback arguments based on Node controls. Restore #470's original persistent message listener with real parentPort.close().

Verification

  • Native build and unchanged cached rebuild: 16/16. All 19 native regression files pass.
  • Promoted the pending probe into tests/runtime/native-worker-parent-port.test.mjs: 11 Home scenarios covering close ordering/idempotence, accepted versus post-close messages, ref/unref and listener removal, synchronous falsy/FIFO receive, transfer to a descendant, self-transfer rejection, and Web Worker global compatibility. All ten shared scenarios pass on Node 24.18 controls twice; the extra global-event compatibility scenario is Home-specific. Two additional native runs with BUN_DESTRUCT_VM_ON_EXIT=1 pass.
  • Nested-shutdown five-scenario suite passes twice in Home and once in Node after restoring the persistent listener. Generic close-event suite: 13 cases pass in Node with --expose-gc.
  • Fifteen byte-identical upstream Node worker fixtures pass natively, including parent-port ref, onmessage clearing, synchronous receive, transfer errors, wasm/shared buffers, and nested termination.
  • Strict native worker corpus (both HOME_NATIVE_VM=1 and HOME_CORPUS_FULL_VM=1): 15 pass / 3 existing debug-sanitizer guard skips / 0 fail / 529 assertions. Supplementary derivative with only the three guards removed: 13 pass / 0 fail / 525 assertions. No sanitizer claim and no skipped tests counted as parity.
  • Build-helper/source-contract tests: 12 pass / 123 assertions; Zig helper tests: 4/4; formatting and focused lint checks pass (builtin source retains its existing quote-style warning). Details and commands are in docs/docs/NATIVE_RUNTIME_BINDINGS.md.

Still unresolved: #471 heap-snapshot cancellation probe times out; #465 generic native task cancellation and #468 producer quiescence remain separate work. This is not 100% Bun parity.

11 days ago

The pending parent-port implementation is now integrated on main: 3b0721c32 installs the worker VM's real native MessagePort, building on the asynchronous close ownership in 007858c52.

Reverified current native Home with native-worker-parent-port.test.mjs: all 11 lifecycle scenarios pass, including async once-only close, natural exit, real/idempotent ref and unref, listener ref release, synchronous FIFO receive, pending-close delivery, descendant transfer, self-transfer rejection, and Web Worker global message compatibility. The full 59-file runtime aggregate also passes.

Closing this integrated lifecycle item; broader worker parity remains under #66.

Sign in to comment on this issue.