Repository navigation
Conversation
|
Updated 12:49 PM PT - Oct 16th, 2026
✅ @robobun, your commit c237859b39acfbc711c541b505460c1b0dac3572 passed in 🧪 To try this PR locally: bunx bun-pr 45203That installs a local version of the PR into your bun-45203 --bun |
|
Reproduced on main (linux x64, release build, 3 of 3 runs).
The repro script is in the Notes of the description. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
Walkthrough
ChangesWorker diagnostic handling
Suggested reviewers: Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
A worker that runs fired its 'error' event two times for each message in its VM log: after the entry loaded and again before shutdown. The messages come from a tsconfig.json or a package.json that the worker thread reads first, from a define or a --tsconfig-override file that each worker applies again, or from a later import. A node:worker_threads parent with no 'error' listener exited with code 1 for a worker that ran. spin() now reports the log only on the paths that end the worker. Each of them returns through shutdown(), so a message is reported at most once. What start-up logged is dropped before the entry resolves, so that it cannot join the report of a later failure.
6c60634 to
a46bac5
Compare
Problem
errortwice for each message in its VM log, such asBuildMessage: Non-relative path "sub/lib.ts" is not allowed when "baseUrl" is not setfrom a tsconfig.json that tsc accepts. Anode:worker_threadsparent with noerrorlistener exits 1.spin()(src/jsc/web_worker.rs:782) callsflush_logsafter the entry loads and again beforeshutdown(). No user reported this.Fix
spin()callsflush_logsonly on the five paths that end the worker. Each returns throughshutdown(), so a message is reported at most once.test/js/web/workers/worker-entry-point.test.tsandworker_threads.test.ts(15 new tests fail on main). Alsoworker.test.ts,worker_blob.test.ts, 110test-worker*Node tests.Background
bun_ast::Log.flush_logsturns a worker's log into theerrorevent of its parent.--defineagain.flush_logs: oneerrorstays and ends a parent with no listener. Node fireserroronly when a worker fails.Downsides
worker.onerrororself.onerror: since 1.2.13 for a config beside the entry, since 1.0.0 for a define..textis unchanged.Notes
Repro (main, linux x64, 3 of 3 runs)
Main prints
message: hi, then an uncaughtBuildMessage: Non-relative path "sub/lib.ts" is not allowed when "baseUrl" is not set (did you forget a leading "./"?), and exits 1. This PR printsmessage: hi,exit 0. tsc 5.9.3 accepts thisextendsandpathslayout (exit 0). With anerrorlistener, main fires the event two times and the worker exits 0.The worker thread must be the first thread that reads
sub/. The resolver caches each directory for the process.What puts a message in the log of a worker that runs
Each row fires
errortwo times on main and no event with this PR (release builds of the merge base and of this PR).main, a directory withindex.ts, a package innode_modules, animportsalias, ablob:URL of the file.--defineor bunfig[define]key that is not valid. Each worker applies the defines again. The main thread still printserror: define key "a-b" must be a valid identifierone time.--tsconfig-override. Each worker reads that file when its VM starts.import()of aBun.fileblob:URL from a directory with such a tsconfig.json: one event when the worker exits, now none.Other results:
errorlistener onselfin the worker: 2 calls, now 0.AggregateError: Error in worker, nowBuildMessage: ModuleNotFound resolving "..." (entry point). Onnode:worker_threadstheerror.codeisMODULE_NOT_FOUNDagain.AggregateError: Error in worker), an alias of a builtin,blob:foo, ablob:URL that is missing.History
WebWorker.create()resolved the entry on the parent thread with a temporary log. node:worker_threads: improve error messages, support environmentData, emit worker event #18768 (1.2.13) moved the resolve to the worker thread and its VM log. Releases from 1.2.13 fire two events (checked 1.2.13, 1.2.15, 1.2.17, 1.2.20, 1.3.0, 1.3.5, 1.3.10, 1.3.13, 1.3.14).flushLogscalls date from Implement Workers #3645.message hi,exit 0for a worker beside a tsconfig.json that does not parse. Node does not read tsconfig.json or defines. For a package.json that does not parse, Node fails the worker withERR_INVALID_PACKAGE_CONFIG. Bun runs the entry (with twoerrorevents before this PR, with none now).Question for a maintainer: drop or print?
This PR reports nothing from the log of a worker that runs. The main thread prints what reaches its log and leaves the exit code alone (
run_command.rs:1529-1532after the entry loads,:1630-1633at exit). Withsub/tsconfig.jsonthat holds{:bun sub/index.tsbun ./sub,bun ./sub/indexerror: Expected "}" but found end of filewith the path, exit 0import()of aBun.fileblob:URLIf a print is wanted for workers, the place is
spin(): after start-up and at the end of the loop. Each worker would then print an invalid define again, after the main thread. #37908 makes the level of the worker log follow the process.Open PRs near these lines
flush_logsskip a log with only warnings. This PR does not changeflush_logs, so the two do not conflict insrc/. The two Worker tests of Do not fail on a warning: gate Bun.Transpiler, FileSystemRouter and Worker on LogJsc::errors_to_js #45063 pass on this PR unchanged. After this PR, the Worker part of Do not fail on a warning: gate Bun.Transpiler, FileSystemRouter and Worker on LogJsc::errors_to_js #45063 matters only on the paths that end the worker. The two test files conflict (both PRs append to them).worker-entry-point.test.ts(appended tests).errorlistener report the event as an uncaught error. Without this PR, that would include the events above (from its description, not run).Sibling bugs that this PR does not fix
AggregateError: Error in worker, andnode:worker_threadslosesMODULE_NOT_FOUND. The fix is a scratch resolver log insideresolve_entry_point_specifier, for the entry and for preloads. It fits after Worker: a preload that does not resolve throws the resolver's message again, not TypeError: undefined #45067.new Bun.FileSystemRouter()throws on the first construct for the same first-reader messages (filesystem_router.rs:285).Bun.servehandler that throws inside a worker fireserrorand stops the worker (on_unhandled_rejection). The main thread prints and keeps the server.Measurements
Release builds (LTO) of commit 281698a without and with this change, linux x64.
error0 times (was 2). Anode:worker_threadsparent with no listener exits 0 (was 1). 4 fixtures, 3 of 3 runs each.{:WebWorker__dispatchError/BuildMessage__create/postErrorToWorkerObject= 0 / 0 / 0 (was 2 / 2 / 2). A clean worker callsflush_logs0 times per life (was 2).spin()(gdbstepion the worker thread, 2 of 2 runs): 48, base 85. The check after start-up adds 6. The flush after the entry loads (22) and the flush beforeshutdown()(21) are gone.onlinepost and the entry load: 5 and 6 on both builds. Writes to fd 2: 0 on both builds..textis 105,679,289 B on both builds. The thread closure that inlinesspin()is 4,933 B (was 4,911).flush_logsis 437 B on both.Tests
worker-entry-point.test.ts2 pass and 11 fail on base, 13 pass here.worker_threads.test.ts152 pass and 4 fail on base, 156 pass here.worker.test.ts42 pass andworker_blob.test.ts6 pass on both. The 110test/js/node/test/parallel/test-worker*files exit with the same code on both.worker-entry-point.test.ts13 pass,worker_threads.test.ts156 pass.worker-entry-point.test.tsnow setsBUN_DESTRUCT_VM_ON_EXIT=1for its child. Without it the child leaves a suppressed leak, and LeakSanitizer startsllvm-symbolizeron the whole binary: 8 s on a debug build, over the 5 s default ofbun bd test. With it the child takes 1.5 s. The test still fails when the fix it guards (resolver: free the exports/imports scratch buffers when a thread exits #39947) is taken out:Direct leak of 12288 byte(s)inmodule_bufs.Design notes
flush_logs(Honor preventDefault() on Worker error event #29060 had that line). With everyflush_logscall followed byshutdown(), that line has no reader, so it is not in this PR.Bun.serveinside the worker, which lists what the entry resolve logged (as on main). A test for that needs the whole 50 KB page from a worker that stops when its handler throws, and the page is cut off when it does not fit one socket write. So that part is not in this PR.