Report uncaught exceptions from DeferredWorkTimer tasks#30857
Conversation
WalkthroughThis PR adds exception capture and reporting for deferred JavaScriptCore tasks when exceptions occur during garbage collection, validates the behavior with FinalizationRegistry cleanup callback tests, and updates overlay CSS string formatting during the build process. ChangesException Handling and Task Scheduling Updates
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
Updated 3:57 PM PT - May 23rd, 2026
❌ @robobun, your commit ce2fd99 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 30857That installs a local version of the PR into your bun-30857 --bun |
|
Found 1 issue this PR may fix:
🤖 Generated with Claude Code |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
There was a problem hiding this comment.
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/bun/util/finalization-registry-throwing-callback.test.ts`:
- Around line 32-35: Reorder the assertions in both subprocess tests so that
output validations run before exit-code checks: move the expects referencing
proc.signalCode and exitCode to after the assertions that validate captured
output (e.g., err and any stdout expectations). Specifically, in the test that
currently asserts expect(proc.signalCode).toBeNull(); expect([0,
1]).toContain(exitCode); expect(err).toContain(...), place the
proc.signalCode/exitCode assertions at the end, and do the same for the other
test around lines showing exitCode usage; keep the same assertion logic but
assert the exit status last.
🪄 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: a49be4c8-8872-47a0-bd5c-9f26e001cd6d
📒 Files selected for processing (12)
src/crash_handler/lib.rssrc/errno/lib.rssrc/jsc/bindings/JSCTaskScheduler.cppsrc/perf/tracy.rssrc/runtime/cli/Arguments.rssrc/runtime/cli/run_command.rssrc/runtime/cli/upgrade_command.rssrc/runtime/jsc_hooks.rssrc/runtime/webview/ChromeProcess.rssrc/spawn/process.rssrc/spawn_sys/spawn_process.rstest/js/bun/util/finalization-registry-throwing-callback.test.ts
There was a problem hiding this comment.
Thanks for addressing the test nits. The JSC fix looks correct (mirrors upstream DeferredWorkTimer::doWork), but since it changes exception handling in the runtime task scheduler and bundles an unrelated bake-codegen.ts change, I'd like a human to sign off.
Extended reasoning...
Overview
The substantive change is ~10 lines in src/jsc/bindings/JSCTaskScheduler.cpp: it wraps deferred-work task execution in a DECLARE_TOP_EXCEPTION_SCOPE and routes any thrown exception to reportUncaughtExceptionAtEventLoop, fixing a releaseAssertNoException() crash when a FinalizationRegistry cleanup callback throws. A new test file exercises both the unhandled-error path and the process.on('uncaughtException') path. The remaining .rs diffs are pure autofix.ci attribute reformatting (no behavior change), and src/codegen/bake-codegen.ts adds a JSON.stringify around the OVERLAY_CSS define value.
Security risks
None identified. No auth, crypto, permissions, or untrusted-input parsing is touched. The change narrows behavior from "crash on assertion" to "report uncaught exception", which is strictly safer.
Level of scrutiny
Moderate-to-high. JSCTaskScheduler::runPendingWork is on the hot path for FinalizationRegistry, Atomics.waitAsync, and wasm streaming compilation — getting the exception-scope / globalObject lookup wrong here could affect every Bun process. The fix faithfully mirrors upstream WebKit's DeferredWorkTimer::doWork pattern (top exception scope → clearExceptionExceptTermination → reportUncaughtExceptionAtEventLoop), and the bug-hunting pass found no issues, but I'd prefer a human familiar with the JSC bindings to confirm the ticket->target()->globalObject() derivation and termination-exception handling.
Other factors
- My two prior inline nits (use
test.concurrent; assert output before exit code) were addressed in 5ac2f5a and are now resolved. - The
bake-codegen.tschange is unrelated to the PR title/description — it makes theOVERLAY_CSSdefine an explicit JSON string instead of relying on the bundler's auto-quote fallback. It looks correct, but a reviewer should confirm it's intentional to ship in this PR. - github-actions flagged three potential duplicate PRs (#30835, #30844, #30846) for the same fix; a human should decide which one lands.
- No CODEOWNERS apply to the touched files.
JSC schedules FinalizationRegistry cleanup (and Atomics.waitAsync, wasm streaming) through DeferredWorkTimer. Upstream's DeferredWorkTimer::doWork wraps each task in an exception scope and routes any thrown exception to reportUncaughtExceptionAtEventLoop. Bun replaces doWork with its own JSCTaskScheduler but skipped that step, so an exception thrown from a FinalizationRegistry cleanup callback (e.g. a constructor passed as the callback and invoked without 'new') was left pending and tripped the releaseAssertNoException() in the Rust/Zig caller.
ed078e2 to
9ca8691
Compare
TicketData::target() asserts isTargetObject() which is only defined inline in DeferredWorkTimerInlines.h; without it the release-asan link fails with an undefined symbol depending on unified source bundling.
9ca8691 to
ce2fd99
Compare
| #include <JavaScriptCore/DeferredWorkTimerInlines.h> | ||
| #include <JavaScriptCore/Exception.h> | ||
| #include <JavaScriptCore/GlobalObjectMethodTable.h> | ||
| #include <JavaScriptCore/TopExceptionScope.h> |
There was a problem hiding this comment.
🟡 🟡 nit: the PR description still has the paragraph about wrapping OVERLAY_CSS in bake-codegen.ts with JSON.stringify, but src/codegen/bake-codegen.ts is no longer modified by this PR — the diff now only touches JSCTaskScheduler.cpp and the new test file. Drop that paragraph so the description matches the actual diff (and the earlier unresolved review comment about stale OVERLAY_CSS references in json_lexer.rs / bun-build-api.test.ts is now moot and can be resolved).
Extended reasoning...
What
The PR description still contains this paragraph:
It also makes the
OVERLAY_CSSdefine inbake-codegen.tsan explicitJSON.stringifystring literal so codegen does not depend on the bundler's define auto-quote recovery (needed when the bootstrap bun predates #30679).
But src/codegen/bake-codegen.ts is not in the PR diff anymore. The diff now contains exactly two files: src/jsc/bindings/JSCTaskScheduler.cpp and test/js/bun/util/finalization-registry-throwing-callback.test.ts. On this branch, bake-codegen.ts:56 still reads OVERLAY_CSS: css("../runtime/bake/client/overlay.css", !!debug), with no JSON.stringify wrapper.
Step-by-step
- An earlier revision of this PR included a drive-by edit to
src/codegen/bake-codegen.ts:56wrappingOVERLAY_CSSinJSON.stringify(...). The CodeRabbit walkthrough comment from that revision still lists "Overlay CSS build-time stringification" as one of the three changes. - A subsequent push (likely a rebase onto main, or splitting the change into a separate PR) dropped the
bake-codegen.tshunk.git diff f161e031..ce2fd99b --name-onlyshows only the two remaining files. - The PR description was not updated to remove the paragraph describing the dropped change.
- Inline review comment 3293301843 (anchored on
src/codegen/bake-codegen.ts) discusses follow-on staleness injson_lexer.rs/bun-build-api.test.tscaused by theJSON.stringifywrap. Since the wrap no longer exists in this PR, that comment's premise is gone and it should be resolved/dismissed rather than acted on.
Why nothing in the PR prevents it
This is purely PR-metadata drift — there is no mechanism that keeps the free-text description in sync with the diff after a force-push.
Impact
Zero runtime/code impact; this is a description-accuracy issue only. The risk is reviewer/future-reader confusion: someone reading the merged PR later will look for a bake-codegen.ts change that isn't there, and the unresolved inline comment on a file that's no longer in the diff adds to that confusion.
Fix
- Delete the
OVERLAY_CSSparagraph from the PR description. - Resolve/dismiss inline comment 3293301843 as moot (the change it critiques was dropped).
(Anchored on JSCTaskScheduler.cpp because the file the description references is not in the diff.)
What does this PR do?
Fuzzilli fingerprint:
58b78b97e2e39882JSC schedules
FinalizationRegistrycleanup (andAtomics.waitAsync, wasm streaming compilation) throughDeferredWorkTimer. Upstream'sDeferredWorkTimer::doWorkwraps each task in an exception scope and routes any thrown exception toreportUncaughtExceptionAtEventLoop. Bun replaces that with its ownJSCTaskScheduler::runPendingWorkbut skipped the exception handling, so an exception thrown from e.g. aFinalizationRegistrycleanup callback was left pending and trippedreleaseAssertNoException()in the caller (SIGABRT in debug/ASAN builds).This PR wraps the deferred task in a
TopExceptionScopeand reports any non-termination exception viareportUncaughtExceptionAtEventLoop, matching upstream and Node.js behavior (the error surfaces as an uncaught exception and can be observed viaprocess.on('uncaughtException')).Repro:
Before:
How did you verify your code works?
test/js/bun/util/finalization-registry-throwing-callback.test.tswith two regression tests: they fail (child SIGABRT) on a debug build without the fix and pass with it; both run viabun bd test.process.on('uncaughtException')).