test(worker-threads): reap the spawned child after a timeout - #386
Merged
Conversation
A mocha timeout rejects the test but does not kill the process the test
spawned, and `npm test` runs mocha without --exit, so mocha waits for the
event loop to drain before exiting. A live child keeps its process handle
on that loop, so a child outliving its test holds the whole run open. When
the child is merely slow this is invisible — mocha waits the extra couple
of seconds and exits. When the child is wedged, the run never ends and the
CI job burns a runner until the job limit instead of failing in seconds.
Seen on win32-test-22: the suite printed its epilogue inside the first
minute, then the job sat in_progress for half an hour on
`Run ./.prebuildify/test` after `should work` timed out.
Capture the ChildProcess (promisify(execFile) exposes it on the returned
promise) and kill it from afterEach, which mocha still runs after a
timeout. Measured with the child replaced by a 10-minute sleep and the
test timeout forced low:
with the reap: 1.19s total
without the reap: never exits (killed by a 30s watchdog)
This only bounds the damage; it does not address why that child wedges on
win32 in the first place, which is a separate investigation. Reaping is
preferred over adding --exit to the mocha invocation: --exit would paper
over any handle leak, and this suite exists partly to catch worker threads
that fail to exit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
szegedi
requested review from
IlyasShabi,
nsavoire and
r1viollet
as code owners
August 7, 2026 07:50
Overall package sizeSelf size: 2.45 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | pprof-format | 2.3.0 | 503.97 kB | 503.97 kB | | source-map | 0.8.0 | 185.66 kB | 185.66 kB | | node-gyp-build | 4.8.4 | 13.86 kB | 13.86 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
This comment has been minimized.
This comment has been minimized.
szegedi
enabled auto-merge (squash)
August 7, 2026 07:56
IlyasShabi
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
win32-test-22on #385 printed its mocha epilogue inside the first minute, then satin_progressonRun ./.prebuildify/testfor half an hour:Tests done in a minute, job still running 30 minutes later.
Cause
A mocha timeout rejects the test but does not kill the process the test spawned, and
npm testisnyc mocha …with no--exit, so mocha waits for the event loop to drain before exiting. A live child keeps its process handle on that loop, so a child that outlives its test holds the entire run open.When the child is merely slow this is invisible — mocha waits the extra couple of seconds and exits, which is why it never shows up on Linux (
worker.jsfinishes in ~4.5s there). When the child is wedged, the run never ends and the job burns a runner until the CI job limit instead of failing in ~20s.Fix
promisify(execFile)exposes theChildProcesson the returned promise, so capture it and kill it fromafterEach— mocha still runs hooks after a timeout. Measured locally with the child replaced by a 10-minute sleep and the test timeout forced low:Chose this over adding
--exitto the mocha invocation:--exitwould paper over any handle leak, and this suite exists partly to catch worker threads that fail to exit.Scope
This bounds the damage only. It does not address why that child wedges on win32, which is the actual bug and a separate investigation. It is worth noting that Windows takes the plain V8
CpuProfilerpath (no SIGPROF, andworker.jssetswithContexts/useCPEDto false off-platform), and this codebase already carriesdetectV8Bug/v8ProfilerStuckEventLoopDetectedmachinery for V8 sampler stalls.Also unaddressed here:
should workhas a 20s budget while its heavier in-process sibling gets 30s, and Windows runs these ~10x slower than Linux. That inconsistency is worth revisiting, but raising the timeout would not have fixed this run — the parent waits on the child regardless of the test timeout.