worker_threads: don't build fd stdio or reify all of process at worker startup

Merged
DY
3mo ago
+276-190

What does this PR do?

Since #31216 every node:worker_threads Worker runs a bootstrap on the worker thread that rebinds process.stdout/stderr/stdin to port-backed streams. Two things in it were pure waste, paid per worker:

  • It used Object.defineProperty(process, "stdout", { value }), which first builds the real fd-backed stream (loads node:fs, internal/fs/streams, …) and then throws it away — for all three streams.
  • It deleted four main-thread-only process stubs. Deleting any static-table property reifies the whole table, which builds the nextTick queue and every other lazy process property.

Now the parent's stdio ports are registered on the worker's global and the existing lazy process.std* getters build the port-backed streams directly; the four stubs are just not installed on a worker's process; and node:worker_threads is loaded by a native hook before preloads/entry instead of being injected as an ESM preload. Semantics are unchanged (stdio and console are still set up eagerly, as in Node).

new Worker() → first message, Linux x64, CI release artifacts on the same machine: main 15.9 ms → 10.2 ms (min), 18.4 → 11.8 ms (median).

Split out of #39528; process.moduleLoadList is now its own PR.

How did you verify your code works?

test/js/node/worker_threads/*.test.ts, test/js/web/workers/worker.test.ts and the node test-worker-*.js files match main on a release build; drove captured / auto-piped stdout (incl. process.exit() flush), stdin: true and parentPort echo on the debug build with BUN_JSC_validateExceptionChecks=1.