Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
7f82a21
node:worker_threads: add 5 upstream Node worker tests that already pass
cirospaciari Jul 16, 2026
f69da29
node:worker_threads: add 3 more upstream Node worker tests that alrea…
cirospaciari Jul 17, 2026
356f936
node:worker_threads: drop test-worker-dns-terminate, exempt fshandles…
cirospaciari Jul 17, 2026
f57c53b
node:worker_threads: exempt both fshandles tests from LeakSanitizer
cirospaciari Jul 17, 2026
2872f8f
node:worker_threads: drop the two fshandles tests instead of exemptin…
cirospaciari Jul 17, 2026
c8a6e90
worker_threads: publish node's 'worker_threads' diagnostics channel
cirospaciari Jul 17, 2026
10fe341
worker_threads: async_hooks WORKER resource, error-clone stack, share…
cirospaciari Jul 17, 2026
c308af4
BroadcastChannel: ref() should return the channel
cirospaciari Jul 17, 2026
f1384cd
worker_threads: fire 'online' before the entry point runs, as node does
cirospaciari Jul 17, 2026
5056973
Merge origin/main into ciro/worker-threads-node-tests
cirospaciari Jul 17, 2026
d7e1659
worker_threads: address review feedback and drop test-worker-memory
robobun Jul 17, 2026
c7d3e81
cli: support node's -pe alias
cirospaciari Jul 17, 2026
80aae50
worker_threads: profile worker threads under --cpu-prof
cirospaciari Jul 17, 2026
e61bfa4
cli: scope the -pe alias to the bun/node entry points in execArgv
robobun Jul 17, 2026
4f599cf
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
fb8b3d3
cpu profiler: clamp the sampling interval to a usable range
cirospaciari Jul 17, 2026
dbe27a0
tls: make --use-system-ca a per-Environment option, add --no-use-syst…
cirospaciari Jul 17, 2026
0fe453a
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
e20129c
cpu profiler: do not inherit --cpu-prof-name into workers
robobun Jul 17, 2026
3a09c2e
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
fc01f2f
worker_threads: keep error.code when the thrown value cannot be cloned
cirospaciari Jul 17, 2026
57cf856
perf_hooks: implement eventLoopUtilization()
cirospaciari Jul 17, 2026
dfe31cd
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
aba81dd
cpu profiler: let workers inherit --cpu-prof-name, as node does
cirospaciari Jul 17, 2026
74c1ebf
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
f17365c
fix lint and clippy fallout from the eventLoopUtilization work
cirospaciari Jul 17, 2026
997ce14
address review: once() this-binding, init sites, nested cpu-prof, lint
robobun Jul 17, 2026
e19fa93
Merge branch 'main' into ciro/worker-threads-node-tests
cirospaciari Jul 17, 2026
1fbc0d1
eventLoopUtilization: fix two cross-thread races
robobun Jul 18, 2026
19b36ff
eventLoopUtilization: use seq_cst for the idle_ns/idle_entry_ns pair
robobun Jul 18, 2026
c7cfb4e
web_worker: fix two aliasing/race hazards in the cross-thread ELU/cpu…
robobun Jul 18, 2026
f0f5e8e
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 18, 2026
de70866
ci: retrigger
robobun Jul 18, 2026
31fb9fc
epoll_kqueue: hoist the post-park clock read before zeroing idle_entr…
robobun Jul 18, 2026
a757b03
Worker.cpp: guard errorCodeOf against a pending TerminationException
robobun Jul 18, 2026
46f9f32
Merge branch 'main' into ciro/worker-threads-node-tests
cirospaciari Jul 19, 2026
1efb86f
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
robobun Jul 22, 2026
89e5f6e
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
robobun Aug 3, 2026
747cbbb
trim comments to <=3 lines, cite spec/node source
robobun Aug 3, 2026
79b3862
Worker.cpp: guard WebWorker__dispatchError/dispatchErrorWithValue aga…
robobun Aug 3, 2026
388af0e
bun_get_loop_elu: use raw us_loop_idle_ns, drop the &self idle_ns wra…
robobun Aug 3, 2026
134c6b0
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
robobun Aug 4, 2026
5e6f3ac
Merge branch 'main' into ciro/worker-threads-node-tests
cirospaciari Aug 5, 2026
fb72372
web_worker: don't report a terminate()-rejected entry promise as unca…
robobun Aug 5, 2026
f99b7c0
Add missing SAFETY comment on us_loop_idle_ns call
cirospaciari Aug 5, 2026
20f5734
web_worker: close the Bun-side termination-exception holes in the ter…
robobun Aug 5, 2026
04dca3c
worker: keep flush_logs and teardown termination-safe on the remainin…
cirospaciari Aug 5, 2026
a4993b9
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
robobun Aug 7, 2026
5d77db6
Merge branch 'main' into ciro/worker-threads-node-tests
cirospaciari Aug 7, 2026
3b28311
execArgv: drop the unconditional -pe insertion the merge reintroduced
robobun Aug 7, 2026
49f1a4f
Trim comments to node-source/spec references
robobun Aug 7, 2026
7d70027
Skip worker_destruction under ASAN while the terminate-during-load as…
robobun Aug 7, 2026
c6045a8
worker_destruction: terminate when the worker reports its action started
robobun Aug 8, 2026
3288369
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
dylan-conway Aug 8, 2026
bc4eab3
worker_threads: read a Worker's hasRef / loop utilization through pri…
dylan-conway Aug 8, 2026
f3c28b8
Reattach release_parent_poll_ref doc comment to its function
robobun Aug 8, 2026
8c4567d
Drop unreachable keep-alive guard in WorkerMessagingProxy::hasRef
robobun Aug 8, 2026
94db9ae
worker_threads: honour --cpu-prof-dir, --cpu-prof-name and --cpu-prof…
dylan-conway Aug 8, 2026
fec0b60
perf_hooks: eventLoopUtilization needs no null-loop guard
dylan-conway Aug 8, 2026
47cceef
tls: a Worker's --use-system-ca / --no-use-system-ca governs the root…
dylan-conway Aug 9, 2026
e5e73b0
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
dylan-conway Aug 9, 2026
153e900
Fix two review findings in the per-worker use-system-ca plumbing
robobun Aug 9, 2026
e7f89fd
Only install OpenSSL default cert paths in the system-CA store variant
robobun Aug 9, 2026
ab623d0
Merge branch 'main' into ciro/worker-threads-node-tests
cirospaciari Aug 10, 2026
dc6b8bb
tls.getCACertificates('default') reports the decision connections use
robobun Aug 10, 2026
6222f4d
root_certs: cite node's NewRootCertStore for the default-paths gating
robobun Aug 10, 2026
6ab78d8
Restore per-thread NODE_USE_SYSTEM_CA fallback in getCACertificates
robobun Aug 10, 2026
28ea0a8
worker_threads: apply the thread's --use-system-ca to the default cli…
cirospaciari Aug 10, 2026
bb88250
worker_threads: resolve a flagless thread's CA option from its env, r…
cirospaciari Aug 10, 2026
6fc1c9d
tls/worker_threads: keep OpenSSL's default lookups in the flagless st…
cirospaciari Aug 10, 2026
efc5781
tls: back to node's root-store rules; make --use-openssl-ca exclusive
cirospaciari Aug 10, 2026
8eb5f3e
uws: safety comment on us_get_shared_default_ca_store matches its arg…
robobun Aug 10, 2026
9d5b413
tls.getCACertificates('default') leaves bundled and system roots out …
robobun Aug 10, 2026
ef0dc09
tls/worker_threads: system store is the OS store alone, openssl-ca re…
cirospaciari Aug 10, 2026
b6f0c62
Pin the web Worker 'open' event to thread start, ahead of the entry p…
robobun Aug 10, 2026
a24ad08
web_worker: borrow the ELU atomics directly (clippy deref_addrof)
cirospaciari Aug 11, 2026
3fcfde3
Point the use_system_ca_flag docs at InitOptions; arm the ELU test's …
robobun Aug 11, 2026
da612ae
Merge branch 'main' into ciro/worker-threads-node-tests
alii Aug 11, 2026
09f3e89
bake: init the production VM as the main thread like its siblings
robobun Aug 11, 2026
7e37ca0
bake: pass the CA flag explicitly instead of initializing as the main…
robobun Aug 11, 2026
ca91126
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 16, 2026
136b4a9
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 16, 2026
d86e8fb
Pin the monotonic half of the shared process origin
robobun Aug 16, 2026
b5238ab
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/js/node/async_hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,16 +377,19 @@
throw $ERR_ASYNC_CALLBACK("hook.promiseResolve");

let enabledInit;
return {
const asyncHook = {
enable() {
if (init !== undefined && enabledInit === undefined) {
// init is delivered for TickObject resources (process.nextTick);
// other resource types are still unimplemented.
// init is delivered for TickObject (process.nextTick) and WORKER
// resources; other resource types are still unimplemented.

Check warning on line 384 in src/js/node/async_hooks.ts

View check run for this annotation

Claude / Claude Code Review

Stale header comment in internal/async_hooks_tick.ts

nit: the sibling header comment in `src/js/internal/async_hooks_tick.ts` (lines 9-11) still says "Currently only TickObject `init` events are delivered … promise, timer and native resource events are still unimplemented", which is now stale since `worker_threads.ts` dispatches WORKER init events into the same `tickInitHooks` array. Worth updating alongside this comment for consistency.
Comment thread
robobun marked this conversation as resolved.
Outdated
// Per-instance wrapper: two hooks registered with the same init
// function must stay independently removable (removal is by
// identity, and removing the other instance's entry would reorder
// its callback relative to unrelated hooks).
enabledInit = (asyncId, type, triggerAsyncId, resource) => init(asyncId, type, triggerAsyncId, resource);
// node invokes init as a method on the AsyncHook instance
// (lib/internal/async_hooks.js), so `this.disable()` inside init works.
enabledInit = (asyncId, type, triggerAsyncId, resource) =>
init.$call(asyncHook, asyncId, type, triggerAsyncId, resource);
require("internal/async_hooks_tick").tickInitHooks.push(enabledInit);
}
if (before !== undefined || after !== undefined || destroy !== undefined || promiseResolve !== undefined) {
Expand All @@ -413,6 +416,7 @@
return this;
},
};
return asyncHook;
}

const executionAsyncIdNotImpl = createWarning(
Expand Down
55 changes: 55 additions & 0 deletions src/js/node/worker_threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@
// Used to ensure that Blobs created to hold the source code for `eval: true` Workers get cleaned up
// after their Worker exits
let urlRevokeRegistry: FinalizationRegistry<string> | undefined = undefined;
// Resolved at module load, as node does (lib/internal/worker.js): resolving it
// lazily inside the constructor would build the channel out of whatever
// Map/Object user code had tampered with by then.
const workerThreadsChannel = require("node:diagnostics_channel").channel("worker_threads");
const { tickInitHooks, newAsyncId } = require("internal/async_hooks_tick");

function injectFakeEmitter(Class) {
// Per-instance registry mapping each event to (user listener -> wrapper), so
Expand Down Expand Up @@ -928,6 +933,9 @@
#stderr;
#stdoutAutoPipe = false;
#stderrAutoPipe = false;
// Mirrors ref()/unref() for the async_hooks WORKER resource's hasRef();
// undefined once the thread has exited, as node's handle reads back.
#hasRef: boolean | undefined = true;

Check warning on line 938 in src/js/node/worker_threads.ts

View check run for this annotation

Claude / Claude Code Review

#hasRef initialized to true regardless of options.ref

`#hasRef` is unconditionally initialized to `true`, but the native WebWorker constructor honours Bun's `options.ref` (JSWorker.cpp:191-195), and this constructor spreads user options through — so `new Worker(f, { eval: true, ref: false })` starts unref'd on the event loop while the async_hooks WORKER resource's `hasRef()` reports `true`. Nit only (`ref` is a Bun-only extension), but the initializer could read `options?.ref !== false` to keep the stated "mirrors ref()/unref()" invariant.
Comment thread
robobun marked this conversation as resolved.
Outdated

// this is used by terminate();
// either is the exit code if exited, a promise resolving to the exit code, or undefined if we haven't sent .terminate() yet
Expand Down Expand Up @@ -1088,6 +1096,45 @@
}
urlRevokeRegistry.register(this.#worker, this.#urlToRevoke);
}
// node publishes the newly-constructed Worker here, at the end of the
// constructor (lib/internal/worker.js).
if (workerThreadsChannel.hasSubscribers) {
workerThreadsChannel.publish({ worker: this });
}
this.#emitAsyncHooksInit();

Check warning on line 1104 in src/js/node/worker_threads.ts

View check run for this annotation

Claude / Claude Code Review

async_hooks WORKER init and diagnostics_channel publish fire in reverse order vs Node

The async_hooks `WORKER` init and the `worker_threads` diagnostics_channel publish fire in the opposite order to Node — Node emits the async_hooks init first (synchronously from AsyncWrap's constructor when `this[kHandle] = new WorkerImpl(...)` runs mid-constructor), then does the dc publish at the very end. Swapping these two blocks so `this.#emitAsyncHooksInit()` runs before `workerThreadsChannel.publish(...)` would match Node exactly.
Comment thread
robobun marked this conversation as resolved.
Outdated
}

// node's WORKER resource is the C++ handle, so hasRef() follows ref()/unref()
// and reads back undefined once the handle is gone. Bun delivered init for
// TickObject only; this extends the same array to the type worker consumers
// look for. Only hasRef() is exposed, not the full handle.
#emitAsyncHooksInit() {
const count = tickInitHooks.length;
if (count === 0) return;
const worker = this;
const resource = {
hasRef() {
return worker.#hasRef;
},
};
const asyncId = newAsyncId();
// Snapshot: enable()/disable() from inside a hook must not affect the
// in-flight dispatch (node stages such mutations in tmp_array).
const snapshot = $newArrayWithSize<Function>(count);
for (let i = 0; i < count; i++) snapshot[i] = tickInitHooks[i];
for (let i = 0; i < count; i++) {
try {
snapshot[i](asyncId, "WORKER", 0, resource);
} catch (err) {
// node: a throwing init hook is fatal (fatalError: print + exit 1),
// never surfaced to the `new Worker` caller — which here has already
// spawned the thread. console is user-mutable, so shield the print.
try {
console.error(typeof err?.stack === "string" ? err.stack : err);
} catch {}
process.exit(1);
}
}
}

get threadId() {
Expand All @@ -1105,13 +1152,17 @@
if (!this.#stdoutAutoPipe) this.#stdoutPort?.ref();
if (!this.#stderrAutoPipe) this.#stderrPort?.ref();
this.#stdinPort?.ref();
// node's ref()/unref() no-op once the handle is gone, leaving hasRef()
// undefined rather than resurrecting it.
if (!this.#exited) this.#hasRef = true;
}

unref() {
this.#worker.unref();
if (!this.#stdoutAutoPipe) this.#stdoutPort?.unref();
if (!this.#stderrAutoPipe) this.#stderrPort?.unref();
this.#stdinPort?.unref();
if (!this.#exited) this.#hasRef = false;
}

get stdin() {
Expand Down Expand Up @@ -1305,6 +1356,10 @@
this.#stdinPort?.close();
this.#onExitPromise = e.code;
this.emit("exit", e.code);
// node's WORKER handle is gone once the thread has exited, so its
// hasRef() reads back undefined. 'exit' listeners ran synchronously above
// and still saw the live value.
this.#hasRef = undefined;
}

#onError(event: ErrorEvent) {
Expand Down
14 changes: 12 additions & 2 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1978,6 +1978,15 @@ fn get_origin_timestamp() -> u64 {
(now - ORIGIN_RELATIVE_EPOCH).max(0) as u64
}

/// `performance.timeOrigin` is the PROCESS start time and every thread reports
/// the same one — a worker's timeOrigin equals the main thread's in node, and
/// `performance.now()` inside a worker is relative to that same origin. Capture
/// it on the first VM so worker VMs inherit it instead of restarting the clock.
fn process_origin() -> (std::time::Instant, u64) {
static ORIGIN: std::sync::OnceLock<(std::time::Instant, u64)> = std::sync::OnceLock::new();
*ORIGIN.get_or_init(|| (std::time::Instant::now(), get_origin_timestamp()))
}

impl VirtualMachine {
/// `VirtualMachine.init(opts)` — allocate + wire the per-thread VM.
///
Expand Down Expand Up @@ -2069,8 +2078,9 @@ impl VirtualMachine {
addr_of_mut!((*vm).pending_internal_promise_reported_at).write(u32::MAX);
addr_of_mut!((*vm).on_unhandled_rejection)
.write(VirtualMachine::default_on_unhandled_rejection);
addr_of_mut!((*vm).origin_timer).write(std::time::Instant::now());
addr_of_mut!((*vm).origin_timestamp).write(get_origin_timestamp());
let (origin_timer, origin_timestamp) = process_origin();
addr_of_mut!((*vm).origin_timer).write(origin_timer);
addr_of_mut!((*vm).origin_timestamp).write(origin_timestamp);
addr_of_mut!((*vm).smol).write(opts.smol);
// `Option<{CPU,Heap}ProfilerConfig>` are NOT zero-valid: each
// payload contains a `bool`, and rustc picks that field's invalid
Expand Down
4 changes: 3 additions & 1 deletion src/jsc/bindings/webcore/JSBroadcastChannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,9 @@ static inline JSC::EncodedJSValue jsBroadcastChannelPrototypeFunction_refBody(JS
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
auto& impl = castedThis->wrapped();
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.jsRef(lexicalGlobalObject); })));
impl.jsRef(lexicalGlobalObject);
// node's ref() returns the channel so it chains (lib/internal/worker/io.js).
RELEASE_AND_RETURN(throwScope, JSValue::encode(castedThis));
Comment thread
robobun marked this conversation as resolved.
}

JSC_DEFINE_HOST_FUNCTION(jsBroadcastChannelPrototypeFunction_ref, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
Expand Down
19 changes: 19 additions & 0 deletions src/jsc/bindings/webcore/Worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "Event.h"
#include "EventNames.h"
#include "StructuredSerializeOptions.h"
#include <JavaScriptCore/ErrorInstance.h>
#include <JavaScriptCore/IteratorOperations.h>
#include <JavaScriptCore/ScriptCallStack.h>
#include <wtf/TZoneMallocInlines.h>
Expand Down Expand Up @@ -539,6 +540,24 @@ bool Worker::dispatchErrorWithValue(Zig::GlobalObject* workerGlobalObject, JSVal

auto serialized = SerializedScriptValue::create(*workerGlobalObject, value, SerializationForStorage::No, SerializationErrorMode::NonThrowing);
CLEAR_IF_EXCEPTION(scope);
// Cloning an Error reads `stack`, so a throwing Error.prepareStackTrace takes
// the whole error down and the caller reports the pretty-printed text as the
// message instead. Node drops only the unreadable `stack`
// (lib/internal/error_serdes.js TryGetAllProperties); retry once with an own
// undefined `stack` so name/message/code still cross. Safe to mutate: the
// worker's own error event and the fallback message are both already
// materialized by the time we get here, and the thread is terminating.
if (!serialized && !scope.exception()) {
if (auto* errorInstance = dynamicDowncast<JSC::ErrorInstance>(value)) {
errorInstance->putDirect(vm, vm.propertyNames->stack, JSC::jsUndefined(), JSC::PropertyAttribute::DontEnum | 0);
// putDirect bypasses the ErrorInstance property overrides, so tell it
// not to re-materialize `stack` (and re-run prepareStackTrace) below.
errorInstance->setStackPropertyAlreadyMaterialized();
CLEAR_IF_EXCEPTION(scope);
serialized = SerializedScriptValue::create(*workerGlobalObject, value, SerializationForStorage::No, SerializationErrorMode::NonThrowing);
CLEAR_IF_EXCEPTION(scope);
}
}
if (!serialized)
return false;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { Worker } = require('worker_threads');
const dc = require('diagnostics_channel');

dc.subscribe('worker_threads', common.mustCall(({ worker }) => {
assert.strictEqual(worker instanceof Worker, true);
}));

new Worker('const a = 1;', { eval: true });
20 changes: 20 additions & 0 deletions test/js/node/test/parallel/test-perf-hooks-worker-timeorigin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const { Worker } = require('worker_threads');

const w = new Worker(`
require('worker_threads').parentPort.postMessage(performance.timeOrigin);
`, { eval: true });

w.on('message', common.mustCall((timeOrigin) => {
// PerformanceNodeTiming exposes process milestones so the
// `performance.timeOrigin` in the `worker_threads.Worker` must be the start
// time of the process.
assert.strictEqual(timeOrigin, performance.timeOrigin);
}));

w.on('exit', common.mustCall((code) => {
assert.strictEqual(code, 0);
}));
29 changes: 29 additions & 0 deletions test/js/node/test/parallel/test-worker-cleanup-handles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';
const common = require('../common');

const assert = require('assert');
const fs = require('fs');
const { Server } = require('net');
const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
const w = new Worker(__filename);
let fd = null;
w.on('message', common.mustCall((fd_) => {
assert.strictEqual(typeof fd_, 'number');
fd = fd_;
}));
w.on('exit', common.mustCall(() => {
if (fd === -1) {
// This happens when server sockets don’t have file descriptors,
// i.e. on Windows.
return;
}
assert.throws(() => fs.fstatSync(fd), { code: 'EBADF' });
}));
} else {
const server = new Server();
server.listen(0);
parentPort.postMessage(server._handle.fd);
server.unref();
}
8 changes: 8 additions & 0 deletions test/js/node/test/parallel/test-worker-dispose.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as common from '../common/index.mjs';
import { Worker } from 'node:worker_threads';

{
// Verifies that the worker is async disposable
await using worker = new Worker('for(;;) {}', { eval: true });
worker.on('exit', common.mustCall());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { Worker } = require('worker_threads');

const w = new Worker(
`const fn = (err) => {
if (err.message === 'fhqwhgads')
throw new Error('come on');
return 'This is my custom stack trace!';
};
Error.prepareStackTrace = fn;
throw new Error('fhqwhgads');
`,
{ eval: true }
);
w.on('message', common.mustNotCall());
w.on('error', common.mustCall((err) => {
assert.strictEqual(err.stack, undefined);
assert.strictEqual(err.message, 'fhqwhgads');
assert.strictEqual(err.name, 'Error');
}));
33 changes: 33 additions & 0 deletions test/js/node/test/parallel/test-worker-hasref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';
const common = require('../common');

const { Worker } = require('worker_threads');
const { createHook } = require('async_hooks');
const assert = require('assert');

let handle;

createHook({
init(asyncId, type, triggerAsyncId, resource) {
if (type === 'WORKER') {
handle = resource;
this.disable();
}
}
}).enable();

const w = new Worker('', { eval: true });

assert.strictEqual(handle.hasRef(), true);
w.unref();
assert.strictEqual(handle.hasRef(), false);
w.ref();
assert.strictEqual(handle.hasRef(), true);

w.on('exit', common.mustCall((exitCode) => {
assert.strictEqual(exitCode, 0);
assert.strictEqual(handle.hasRef(), true);
setTimeout(common.mustCall(() => {
assert.strictEqual(handle.hasRef(), undefined);
}), 0);
}));
63 changes: 63 additions & 0 deletions test/js/node/test/parallel/test-worker-http2-stream-terminate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const http2 = require('http2');
const { duplexPair } = require('stream');
const { parentPort, Worker } = require('worker_threads');

// This test ensures that workers can be terminated without error while
// stream activity is ongoing, in particular the C++ function
// ReportWritesToJSStreamListener::OnStreamAfterReqFinished.

const MAX_ITERATIONS = 5;
const MAX_THREADS = 6;

// Do not use isMainThread so that this test itself can be run inside a Worker.
if (!process.env.HAS_STARTED_WORKER) {
process.env.HAS_STARTED_WORKER = 1;

function spinWorker(iter) {
const w = new Worker(__filename);
w.on('message', common.mustCall((msg) => {
assert.strictEqual(msg, 'terminate');
w.terminate();
}));

w.on('exit', common.mustCall(() => {
if (iter < MAX_ITERATIONS)
spinWorker(++iter);
}));
}

for (let i = 0; i < MAX_THREADS; i++) {
spinWorker(0);
}
} else {
const server = http2.createServer();
let i = 0;
server.on('stream', (stream, headers) => {
if (i === 1) {
parentPort.postMessage('terminate');
}
i++;

stream.end('');
});

const [ clientSide, serverSide ] = duplexPair();
server.emit('connection', serverSide);

const client = http2.connect('http://localhost:80', {
createConnection: () => clientSide,
});

function makeRequests() {
for (let i = 0; i < 3; i++) {
client.request().end();
}
setImmediate(makeRequests);
}
makeRequests();
}
Loading