Skip to content

fix(event-loop): poll ready I/O before POSIX immediates - #142

Closed
steipete wants to merge 5 commits into
mainfrom
fix/posix-immediate-poll-order
Closed

steipete wants to merge 5 commits into
mainfrom
fix/posix-immediate-poll-order

Conversation

@steipete

@steipete steipete commented Oct 8, 2026 •

Copy link
Copy Markdown

Poll already-ready native I/O before the first POSIX task batch so queued close/exit events can run before a following immediate rejects process-group retirement. The native tick-depth guard prevents the added poll from re-entering an active uSockets dispatch; nested-task phase bookkeeping is consumed at tick completion. Windows retains its libuv ordering.

Ports and extends oven-sh/bun#41355; thanks @robobun. The original retirement and ready-pipe reentry regressions remain. Full TLS teardown parity is independent in #144. Neither Bun end-getter snapshot ordering matches Node 24, which destroys the transport without calling end.

Bounded investigation outcome: STOP the global approach; not READY. Head: 7e909ee8fd6e40c5b57d1b609cbb3334238eda8e. Do not merge this draft.

The HTTPS Agent trace disproved the apparent cache-miss explanation. The cache was populated and the handshake resumed. A synchronous response listener observed isSessionReused()=true, but EOF/destroy overtook its awaiting continuation, which read false from the missing native handle. The pre-task poll inherited tick()'s artificial task-drain scope, deferring checkpoints across separate I/O callbacks. Removing that scope only during the poll restored normal callback checkpoints while retaining enclosing JS scopes. The resulting candidate passes the Agent file 30/30, full timers and TLS-connect, and the original Linux retirement regression.

Completed evidence on this head:

  • Native CI: Linux and Darwin green.
  • Windows CI: x64/ARM64 build, smoke and Compatibility lanes green; publication disabled. The previous 42648 Compatibility jobs were cancelled during artifact download before tests, not assertion failures.
  • Full Darwin fork selection 52/52; main's identical-source baseline 52/52 was retained rather than rerun.
  • Darwin reentry: 50/50 main, 0/50 original unsafe head, 50/50 fixed, with each run retained and no retries.
  • OpenClaw's eight selected consumer files: Node 24.21 and patched Linux 14/14; main 13/14 with only the retirement case failing. Private test state throughout; no live Gateway was touched.
  • Contextual exact-head P2 review is scoped-clean. A hypothesized synchronous-wait ordering issue was tested against main and shown to be pre-existing; ordinary await matches Node 24.

The broader Darwin comparison nevertheless exposed another lifecycle failure: Bun.serve http2 lifecycle > server.stop(true) from inside an h2 handler (sync, POST) lets the process exit. Main passed 23/23; this candidate passed 22/23 and hung at the unchanged 20-second timeout. The runner killed the remaining child, producing exit 143. That observation is retained and was not rerun to seek green. Its cause has not been isolated; native CI's green result does not waive it.

Recommended narrower alternative: prototype from fork main, then rewrite this PR around an owner-only diff if qualified. Target Process terminal-status publication, Subprocess::on_process_exit, and child-owned stdio/extra-pipe completion (including fd3). Preserve retained ownership and exactly-once exit delivery, drain only actual ready bytes/EOF, and settle normal close accounting. Do not call the general uSockets poll, alter global check/timer or microtask order, synthesize EOF, or accept process-group ESRCH alone while required relay/control completion remains unsettled.

Node 24 evidence bounds the goal: in the original blocked-host retirement fixture the group vanished 9ms after acknowledgement, the host resumed 6000ms later, and exit/control-close delivery allowed both wait and extinction to fulfill. The fixture does not require an order between exit and control close, nor does Node promise every close precedes every immediate. The acceptance test remains OpenClaw's service-child-relay-host.retirement.test.ts, unchanged apart from its local Bun exclusion, followed by 50 independent candidate runs and live-descendant/held-pipe/queued-filesystem controls.

The remaining design obligation is how kernel readiness becomes an owned terminal record before the retirement observer runs. Reuse existing reap/waiter notifications where possible; prove any owner-specific readiness checkpoint against Node. Do not silently introduce a process-wide signal policy, watcher thread, unbounded process scan or timeout lenience. No narrower runtime implementation is submitted here.

Remaining full matrix, Linux fork selection/matrix, Rust 12, repeated retirement proof and Gateway smoke were stopped. The independent #144 workstream is unchanged. Nothing was merged or published.

Ports and extends oven-sh#41355 by @robobun. Preserve queued ready-I/O retirement ordering and the check boundary for task-created connections. Gate the added pre-task poll on native tick ownership so nested task waits preserve the active ready-event batch. Cover queued-task microtasks and nested pipe completion.
@steipete
steipete force-pushed the fix/posix-immediate-poll-order branch from 39c1510 to 77f389c Compare October 8, 2026 03:45
@autofix-troubleshooter

Copy link
Copy Markdown

Hi! I'm the autofix logoautofix.ci troubleshooter bot.

It looks like you correctly set up a CI job that uses the autofix.ci GitHub Action, but the autofix.ci GitHub App has not been installed for this repository. This means that autofix.ci unfortunately does not have the permissions to fix this pull request. If you are the repository owner, please install the app and then restart the CI workflow! 😃

@steipete

steipete commented Oct 8, 2026

Copy link
Copy Markdown
Author

Superseded by #147, which fixes the subprocess retirement ordering at its owner (publishing owned subprocess completion before retirement checks) without changing the global event-loop phase order. Closing this draft; its evidence and regressions informed #147.

@steipete steipete closed this Oct 8, 2026
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.

2 participants