Skip to content

JSModuleLoader: do not abort when terminate() lands during a worker’s module loads (WebKit pin bump) - #34655

Open
robobun wants to merge 6 commits into
mainfrom
farm/d6092233/module-loader-termination-assert
Open

JSModuleLoader: do not abort when terminate() lands during a worker’s module loads (WebKit pin bump)#34655
robobun wants to merge 6 commits into
mainfrom
farm/d6092233/module-loader-termination-assert

Conversation

@robobun

@robobun robobun commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Bumps WEBKIT_VERSION from ddea71318fec to 171babe26c3b (one commit: oven-sh/WebKit#391, JSModuleLoader: propagate a TerminationException from resolve() instead of treating it as a resolution failure) and adds a stress test for terminating node:worker_threads Workers mid-preload.

test/js/node/worker_threads/worker-transfer-list.test.ts intermittently SIGABRTs on the debug+ASAN lane (seen in build 75365; test-side workaround in #34644):

ASSERTION FAILED: !exception()
vendor/WebKit/Source/JavaScriptCore/runtime/ExceptionScope.h(61) : void JSC::ExceptionScope::assertNoException()

Cause

Since #31216 every node:worker_threads Worker preloads node:worker_threads on startup, so even new Worker("", { eval: true }) runs module-loader microtasks during startup. terminate() fires the NeedTermination trap on the worker VM; when that lands inside JSModuleLoader::hostLoadImportedModule's resolve() call, the TerminationException was caught as a resolution error: cached in m_resolutionFailures, left pending by rejectWithCaughtException (TRY_CLEAR_EXCEPTION refuses to clear a termination), and carried into finishLoadingImportedModule -> continueDynamicImport, whose scope.assertNoException() aborts under ENABLE(EXCEPTION_SCOPE_VERIFICATION). Frame-pointer trace from the worker thread at the abort:

continueDynamicImport              JSModuleLoader.cpp
finishLoadingImportedModule        JSModuleLoader.cpp
hostLoadImportedModule             JSModuleLoader.cpp
loadModule                         JSModuleLoader.cpp
moduleLoadTopSettled               JSMicrotask.cpp
drainMicrotasks / load_preloads

Release builds are unaffected: without ENABLE(EXCEPTION_SCOPE_VERIFICATION) the assertion compiles to an empty ASSERT and the next RETURN_IF_EXCEPTION in the caller unwinds normally.

Fix

oven-sh/WebKit#391 (merged): hostLoadImportedModule returns early when the exception caught from resolve() is a TerminationException, so the caller's RETURN_IF_EXCEPTION unwinds instead of entering FinishLoadingImportedModule with a termination pending. This PR pins that commit.

Testing

test/js/node/worker_threads/worker-terminate-during-preload.test.ts spawns a child that calibrates a warm worker's time-to-online, then sweeps terminate() through a band just below it against workers started with fourteen explicit node: preloads (widening the cumulative resolve() window so the sweep lands in it on any host speed). On the unfixed WebKit the child aborts; with the fix it exits 0.

Multi-run probe on debug+ASAN linux-x64 (the assertion only exists under exception-scope verification):

before (pin without the fix): 8/8 runs fail
  - ASSERTION FAILED: !exception()  (continueDynamicImport)
  - plus two adjacent termination-delivery symptoms in reifyStaticProperty /
    getOwnPropertyDescriptor, fixed by oven-sh/WebKit#282/#306 (already in the pin)
after  (pin with the fix): 23/23 runs pass

The fix-side change is the WEBKIT_VERSION bump in scripts/build/deps/webkit.ts; there is no src/ diff for the fail-before gate to stash, so the probe above is the fail-before evidence.

Supersedes the test-side workaround in #34644.


[decide:webkit] gate passed · iteration 4 · 2 files touched

passes on PR (with fix)
Test-only change.

Debug/ASAN (expected pass):
$ bun bd test 'test/js/node/worker_threads/worker-terminate-during-preload.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/js/node/worker_threads/worker-terminate-during-preload.test.ts
bun test v1.4.0 (bd9359dd7)

test/js/node/worker_threads/worker-terminate-during-preload.test.ts:
(pass) terminate() during worker preload does not abort in the module loader [84533.68ms]

 1 pass
 0 fail
 1 expect() calls
Ran 1 test across 1 file. [88.03s]
Exit: 0
diff hotspot
scripts/build/deps/webkit.ts                       |  5 +-
 .../worker-terminate-during-preload.test.ts        | 71 ++++++++++++++++++++++
 2 files changed, 75 insertions(+), 1 deletion(-)

gate history · 4 passed · 1 rejected · iteration 4

evidence per changed file
file                                                      reads  edits  tests
scripts/build/deps/webkit.ts                                  2      6      0
…/worker_threads/worker-terminate-during-preload.test.ts      2      8      0

…preload resolve()

Picks up oven-sh/WebKit#309 and adds a stress test that terminates
node:worker_threads Workers while they are still in their startup preload.
@robobun

robobun commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 4:11 PM PT - Aug 7th, 2026

@robobun, your commit bd9359dd77c7b4add1b3c1a7e5a12b4ed522e101 passed in Build #90299! 🎉


🧪   To try this PR locally:

bunx bun-pr 34655

That installs a local version of the PR into your bun-34655 executable, so you can run:

bun-34655 --bun

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The WebKit revision changes to include a termination-exception fix. A Node worker_threads integration test terminates workers during preload resolution and verifies successful process output and exit status.

Changes

Worker preload termination coverage

Layer / File(s) Summary
Update WebKit prebuilt version
scripts/build/deps/webkit.ts
WEBKIT_VERSION now references WebKit revision 171babe26c3b330ac0263d1bed3550571908c838, with documentation for the JSModuleLoader::hostLoadImportedModule change.
Test termination during worker preload
test/js/node/worker_threads/worker-terminate-during-preload.test.ts
Adds an integration test that calibrates worker startup timing, terminates workers during preload resolution, and verifies ok output with exit code 0. The test uses build-specific timeouts.

Possibly related issues

Possibly related PRs

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the JSModuleLoader termination fix and the related WebKit pin bump.
Description check ✅ Passed The description covers the change, cause, fix, verification steps, test results, and required template sections.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/build/deps/webkit.ts`:
- Around line 10-13: Replace the ephemeral preview value in WEBKIT_VERSION with
the merged stable autobuild-<sha> tag for WebKit PR `#309` before merging. Do not
leave autobuild-preview-pr-309-9cb28173 in the mergeable branch, and preserve
the existing version constant interface.

In `@test/js/node/worker_threads/worker-terminate-during-preload.test.ts`:
- Around line 61-63: Update the structured result assertions in the worker
termination test to treat stderr as diagnostic-only: preserve validation of
stdout containing “ok” and exitCode equal to 0, but remove the requirement that
stderr be empty. Ensure the subprocess continues draining stdout, stderr, and
exit concurrently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 45bd6182-2816-4881-80ef-1988ef940755

📥 Commits

Reviewing files that changed from the base of the PR and between a227ad9 and 5f03482.

📒 Files selected for processing (2)
  • scripts/build/deps/webkit.ts
  • test/js/node/worker_threads/worker-terminate-during-preload.test.ts

Comment thread scripts/build/deps/webkit.ts Outdated
Comment thread test/js/node/worker_threads/worker-terminate-during-preload.test.ts Outdated
Comment thread test/js/node/worker_threads/worker-terminate-during-preload.test.ts Outdated
Comment thread test/js/node/worker_threads/worker-terminate-during-preload.test.ts Outdated
Per review: keep stderr in the failure diff but do not require it to be
empty (debug/ASAN lanes can emit benign warnings), and wire the
calibration worker's error event to reject so a startup failure surfaces
through the child's own catch.
The previous preview was based on WebKit main, which pulled in the
JSMicrotask/JSPromise refactors (oven-sh/WebKit#295/#301) and nudged the
node-net mimalloc-page-count leak test past its threshold on aarch64.
The minimal branch carries only the termination fixes the new stress
test needs (plus Jarred's oven-sh/WebKit#286 for the property-lookup
assertions the same sweep surfaces).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/build/deps/webkit.ts`:
- Around line 11-15: Update the WEBKIT_VERSION constant to use the merged stable
autobuild-<sha> WebKit tag instead of the ephemeral
autobuild-preview-pr-309-5113fb40 preview tag; do not leave the branch mergeable
with the PR preview version.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 170f6dae-56c2-4653-b430-cac6a2c81431

📥 Commits

Reviewing files that changed from the base of the PR and between e06320e and 61eb2d0.

📒 Files selected for processing (2)
  • scripts/build/deps/webkit.ts
  • test/js/node/worker_threads/worker-terminate-during-preload.test.ts

Comment thread scripts/build/deps/webkit.ts Outdated
@robobun

robobun commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

Verified this also fixes #34690, the worker-transfer-terminate-stress abort at ExceptionScope.h:61 on the ASAN lane that was filed without a backtrace (the CI annotations truncate at the assert frame).

I captured a symbolized backtrace of the aborting worker thread by keeping the process alive after the report (ASAN sleep_before_dying) and attaching a debugger post-mortem. The stack is exactly the path this PR addresses, reached from the worker's startup preload:

JSC::ExceptionScope::assertNoException()            ExceptionScope.h:61
JSC::Exception::value()
JSC::JSModuleLoader::continueDynamicImport          JSModuleLoader.cpp:1026
JSC::JSModuleLoader::finishLoadingImportedModule    JSModuleLoader.cpp:959
JSC::JSModuleLoader::hostLoadImportedModule         JSModuleLoader.cpp:662
JSC::JSModuleLoader::loadModule
... worker thread bootstrap (WebWorker::create thread body, src/jsc/web_worker.rs)

Repro and verification on linux x64 debug ASAN, running 4 concurrent instances of test/js/node/worker_threads/worker-transfer-terminate-stress.test.ts per iteration:

This also identifies the residual ~1-in-4000 assertNoException() noted in #33966's verification section as the same module-loader path. Once this lands, the quarantine entry for worker-transfer-terminate-stress tracking #34690 can be lifted together with the #34095 ones.

Take main's WEBKIT_VERSION shape and bump it to 171babe26c3b (oven-sh/WebKit#391),
the merged successor of the #309 preview this branch was pinned to.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@robobun robobun changed the title JSModuleLoader: don't abort when terminate() lands during a worker's preload resolve() JSModuleLoader: dont abort when terminate() lands during a worker's module loads (WebKit pin bump) Aug 7, 2026
@robobun

robobun commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Revived this: oven-sh/WebKit#391 (the merged successor of #309) landed today and its autobuild is published, so the preview pin is replaced with the real one, 171babe26c3b, one commit past main's current pin. Merged main into the branch.

Verified locally on the debug ASAN build: this PR's test, worker-transfer-terminate-stress.test.ts, test-worker-message-port-transfer-terminate.js, and 18/18 concurrent runs of the worker_destruction fixture all pass; the same fixture loop aborts 3/18 on the old pin. Details and the lldb backtrace are in the updated PR body.

This assert is currently the only red on several otherwise green node-compat PRs (#34658, #34719, #34515, and worker_destruction on #34654), so it unblocks those once merged.

@robobun robobun changed the title JSModuleLoader: dont abort when terminate() lands during a worker's module loads (WebKit pin bump) JSModuleLoader: do not abort when terminate() lands during a worker’s module loads (WebKit pin bump) Aug 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/node/worker_threads/worker-terminate-during-preload.test.ts`:
- Around line 28-35: Update timeToOnline() to track whether the worker has
reached the online event and register an exit handler that rejects if the worker
exits beforehand. Keep the existing online resolution and error rejection
behavior, ensuring every pre-online failure settles the promise with the worker
exit diagnostic.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c14c0bd7-aad5-4a5a-9bf4-0542f713d70c

📥 Commits

Reviewing files that changed from the base of the PR and between f8b6342 and 2148b29.

📒 Files selected for processing (2)
  • scripts/build/deps/webkit.ts
  • test/js/node/worker_threads/worker-terminate-during-preload.test.ts

Comment thread test/js/node/worker_threads/worker-terminate-during-preload.test.ts

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-reviewed after the latest push and didn't find any bugs — the earlier feedback (stderr exact-match, wiring error/exit in the calibration wait, replacing the preview tag with the merged 171babe26c3b pin) is all addressed. Deferring to a maintainer only because this is a WebKit pin bump: even a one-commit engine bump should get a human sign-off that the autobuild artifacts are published and CI is green across all lanes on the new pin.

What was reviewed:

  • WEBKIT_VERSION now points at a stable merged sha (not a preview tag); the comment above it accurately names the upstream PR and delta.
  • Test drains stdout/stderr/exit concurrently, asserts a combined object with stderr: expect.any(String), and the calibration promise rejects on error and pre-online exit.
  • Sweep loop's busy-wait and terminate() sequencing look sound; no unbounded hangs (child .catchprocess.exit(1), outer per-test timeout branches on isDebug).
Extended reasoning...

Overview

This PR does two things: (1) bumps WEBKIT_VERSION in scripts/build/deps/webkit.ts from ddea71318fec to 171babe26c3b — a single upstream commit (oven-sh/WebKit#391) that makes JSModuleLoader::hostLoadImportedModule propagate a TerminationException from resolve() instead of caching it as a resolution failure and tripping scope.assertNoException() in continueDynamicImport; and (2) adds test/js/node/worker_threads/worker-terminate-during-preload.test.ts, a stress test that calibrates a warm worker's time-to-online and sweeps terminate() through the preload window to hit the module loader mid-resolve().

Security risks

None identified. The WebKit-side change is a narrow control-flow adjustment in the module loader's termination-exception path (already reviewed and merged in oven-sh/WebKit). The Bun-side diff is a version constant + comment and a test file; no auth, crypto, parsing of untrusted input, or new user-facing surface.

Level of scrutiny

High, purely because of what is being bumped. WebKit is Bun's JS engine and its ABI is tightly coupled to every native bun build (per the ASAN/layout notes in webkit.ts itself). Even though the delta is one targeted commit and the actual code change was reviewed upstream, a maintainer should confirm (a) the autobuild-171babe26c3b… release artifacts exist for every os/arch/abi combination the build downloads, and (b) CI is green on the current HEAD — the only CI status visible in the timeline is build #75506 on an earlier preview-tag commit, which failed build-cpp everywhere. The test file on its own would be trivially approvable.

Other factors

All prior review feedback is resolved: my two earlier nits (exact-empty stderr assertion; unrejected once('online') wait) and CodeRabbit's three (preview tag, stderr assertion, pre-online exit) are each reflected in the current diff — the calibration promise now wires online/error/exit, the assertion is a combined {stdout, exitCode, stderr} object with stderr: expect.any(String), and the pin is a stable merged sha. The bug-hunting pass on this revision found nothing. The PR description includes an 8/8-fail-before / 23/23-pass-after probe on debug+ASAN and robobun's follow-up confirms it also fixes #34690 with a symbolized backtrace matching the described path. Given all that, the code itself looks ready; I'm deferring solely so a human signs off on the engine bump and confirms green CI on the final pin.

robobun added a commit that referenced this pull request Aug 7, 2026
…sert is open

ASAN builds compile WebKit assertions in, and terminate() landing during
the worker's module loads trips ExceptionScope::assertNoException,
tracked in #34655. Non-ASAN lanes keep running all three methods.
@robobun

robobun commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Additional evidence that this fix covers another CI abort seen in the wild: test/js/node/test/parallel/test-worker-message-port-transfer-terminate.js SIGABRTs on the debug+ASAN lane with the same signature (build 90330, branch at d2fee4c, WebKit pin ddea71318fec):

ASSERTION FAILED: !exception()
vendor/WebKit/Source/JavaScriptCore/runtime/ExceptionScope.h(61) : void JSC::ExceptionScope::assertNoException()

That test looks like a message-dispatch race (it transfers a MessagePort and terminates), but measured on a debug+ASAN build the setImmediate terminate consistently fires 290-480ms after new Worker while the worker is still starting up, before online (5/5 runs). The queued message is never dispatched; terminate() lands inside the startup module loads, which is exactly the window this PR fixes.

Verified on current main (392726b, pin ddea71318fec, one commit behind this PR's 171babe26c3b, that one commit being oven-sh/WebKit#391):

  • the terminate-during-preload sweep from this PR's test aborts with the signature above (2/2 runs)
  • rebuilt with 171babe26c3b: the sweep passes 4/4, and 24 runs of test-worker-message-port-transfer-terminate.js under the same conditions show no aborts
  • targeted attempts to fire the abort through the message-dispatch path itself (large payloads to stretch deserialization, terminate sweeps across the dispatch window, parallel stress) produced zero hits in several hundred runs, so startup module loads are the only demonstrated trigger in that test

Note on the base: main's pin moved to e6e37cda (#36794) and back to ddea71318fec (#36990) since this PR was opened. e6e37cda is an ancestor of ddea71318fec, so no pin on main has included the fix, and this PR's one-commit bump is again exactly main's pin plus the fix.

robobun added a commit that referenced this pull request Aug 8, 2026
The fixture terminated 1ms after online. With online now firing before
entry evaluation, that raced module loading, and on ASAN builds
terminate() landed mid-load and tripped the WebKit assert tracked in
#34655 rather than the mid-I/O window the test is for. The worker now
posts a message once its action is in flight and the parent terminates
on it, making the timing deterministic, so the ASAN skip from 7d70027
comes back out. The pre-existing fetch skip stays: terminate landing
mid-fetch still hits its ASAN failure (about 1 in 25 locally).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant