Skip to content

Count signal-killed renderers apart and check the exit path statically - #3

Merged
Portll merged 1 commit into
mainfrom
test/og-exit-guards
Oct 11, 2026
Merged

Portll merged 1 commit into
mainfrom
test/og-exit-guards

Conversation

@Portll

@Portll Portll commented Oct 11, 2026

Copy link
Copy Markdown
Owner

Fixes the macOS Node 24 failure on #2's CI run 38101706076, which passed on rerun: bin/test/docsite-og-image-entry.test.mjs, "the renderer exits under concurrent-allocation stress".

What failed

The test starts 24 bin/docsite-og-image.mjs --check processes at once under --stress-concurrent-allocation. It guards against the Node 24/26 process.exit() deadlock (nodejs/node#64274). On the failing run, 22 children exited 1 as expected and 2 were killed by a signal. None hung. The test treated the two killed children as failures, but neither ever reached its exit, so neither says anything about the exit path.

Changes

Signal-killed children are counted apart. A hung child still fails the test, because that is the deadlock. A child killed by a signal is reported as unmeasured in a test diagnostic. If more than a third of the 24 are killed, the test skips with the reason, because too few exits were measured to count as a pass.

All 24 still run at once. Contention is what exposes the deadlock. With process.exit(run()) reinstated in the renderer and 24 runs per round, I measured:

Concurrency Children hung Rounds that caught it
24 at once 15/96 4/4
batches of 4 4/96 1/4
one at a time 0/96 0/4

A lighter version of the test would mostly stop catching the regression.

A new static test, "the renderer never calls process.exit() and catches every throw from run()". It strips comments with stripComments from bin/lib/tracked-imports.mjs, then fails if the renderer calls process.exit( or calls run() outside a try/catch that sets process.exitCode. It runs in about 1 ms, and it catches a regression on every run, including rounds where the stress test happens to miss it. The test also runs the check on a renderer that has both hazards, and on a comment that only names process.exit(). That shows the check fires on the first and ignores the second.

Verification

  • docsite-og-image-entry.test.mjs passed 7/7 on three consecutive runs. The stress test took 1.6–2.7 s locally.
  • With process.exit(run()) reinstated, the static test reports both hazards. With the try/catch removed, it reports the uncaught run().
  • tracked-imports passes 19/0 against this commit.

The og-image stress test failed macOS Node 24 once on #2:
two of 24 renderers were killed by a signal under
--stress-concurrent-allocation. None hung. A killed child never reached
its exit, so it is now reported as unmeasured, and the test skips as
unmeasured if more than a third are killed. All 24 still run at once:
with process.exit() reinstated, 24 at once hung 15 of 96 children and
one at a time hung none. A new static test reads the renderer and fails
if it calls process.exit() or calls run() outside a try/catch. It
failed on both hazards reinstated.
@Portll
Portll merged commit 81ef72b into main Oct 11, 2026
24 checks passed
@Portll
Portll deleted the test/og-exit-guards branch October 11, 2026 02:20
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