Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
06b6446
Bump WebKit: DeferTermination outside a VMEntryScope; wake Atomics.wa…
dylan-conway Aug 14, 2026
de02641
tests: reword the Atomics.wait test comment (not a released regression)
dylan-conway Aug 14, 2026
1f5ccf2
worker: act on terminate() that lands while draining work scheduled b…
dylan-conway Aug 14, 2026
ee97acc
node:path binding: check for an exception before storing each createP…
dylan-conway Aug 14, 2026
26b46fc
Keep JSC's termination-request flag set for as long as a stopped work…
dylan-conway Aug 14, 2026
35fe928
Merge remote-tracking branch 'origin/main' into claude/webkit-termina…
dylan-conway Aug 14, 2026
d3fc0de
test: clarify why the Atomics.wait test pauses before terminate()
dylan-conway Aug 14, 2026
70d049e
Merge remote-tracking branch 'origin/main' into claude/worker-thread-…
dylan-conway Aug 14, 2026
06fd21a
Merge remote-tracking branch 'origin/main' into claude/worker-thread-…
dylan-conway Aug 14, 2026
2affab3
Merge remote-tracking branch 'origin/main' into claude/webkit-termina…
dylan-conway Aug 14, 2026
89a8dad
An empty rejection value yields an inert promise; re-check the stop a…
dylan-conway Aug 14, 2026
08d3a8e
Merge remote-tracking branch 'origin/claude/webkit-termination-fixes'…
dylan-conway Aug 14, 2026
0f0d1b3
Bun::createError always yields an object, even with a worker's termin…
dylan-conway Aug 14, 2026
78e82b9
crypto: prime generation/checking gives up once its worker has been a…
dylan-conway Aug 14, 2026
06a36ca
Keep the termination request at every Rust exception-check boundary, …
dylan-conway Aug 14, 2026
dda2c50
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 14, 2026
0743d0e
on_before_exit: a stop requested during the drain also suppresses the…
dylan-conway Aug 14, 2026
6de3d1e
Merge remote-tracking branch 'origin/claude/worker-thread-fixes-2' in…
dylan-conway Aug 14, 2026
d1ad280
Subprocess: no pending-activity bookkeeping once the wrapper is final…
dylan-conway Aug 14, 2026
3f9e935
test: give the Atomics.wait terminate test the file's timeout
dylan-conway Aug 14, 2026
d3479d9
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 14, 2026
9fcb814
TopExceptionScope: the pure exception() query keeps the termination r…
dylan-conway Aug 14, 2026
8baa168
Merge remote-tracking branch 'origin/claude/worker-thread-fixes-2' in…
dylan-conway Aug 14, 2026
b876956
fetch: hold a counted ref on the response ByteStream source while pro…
dylan-conway Aug 14, 2026
7e57f3e
valkey: close() returns what a half-open socket's onclose left pendin…
dylan-conway Aug 14, 2026
023e042
valkey: close unconditionally before combining results (clippy or_fun…
dylan-conway Aug 14, 2026
e7ea225
test: the streaming-fetch worker-exit test proves each worker reached…
dylan-conway Aug 14, 2026
bd0bffc
fetch: drop ignore_remaining_response_body's now-meaningless from_fin…
dylan-conway Aug 14, 2026
af391d8
crypto: generatePrime's completion treats a pending exception as auth…
dylan-conway Aug 14, 2026
34dc046
Merge remote-tracking branch 'origin/main' into claude/worker-thread-…
dylan-conway Aug 14, 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
7 changes: 7 additions & 0 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1594,6 +1594,13 @@ impl VirtualMachine {
let mut dispatch = false;
loop {
while self.is_event_loop_alive() {
// A stop requested meanwhile (worker.terminate(), or process.exit()
// from a listener) ends the drain, as it ends the worker's main
// loop: what is still in flight is cancelled by teardown, and its
// completions would no longer be delivered to release the loop.
if !self.script_allowed() {
return;
}
self.tick();
self.auto_tick_active();
dispatch = true;
Expand Down
14 changes: 6 additions & 8 deletions src/jsc/bindings/Path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,13 @@ JSC::JSValue createNodePathBinding(Zig::GlobalObject* globalObject)
auto scope = DECLARE_THROW_SCOPE(vm);
auto binding = constructEmptyArray(globalObject, nullptr, 2);
RETURN_IF_EXCEPTION(scope, {});
binding->putDirectIndex(
globalObject,
(unsigned)0,
Zig::createPath(globalObject, false));
auto* posix = Zig::createPath(globalObject, false);
RETURN_IF_EXCEPTION(scope, {});
binding->putDirectIndex(
globalObject,
(unsigned)1,
Zig::createPath(globalObject, true));
binding->putDirectIndex(globalObject, (unsigned)0, posix);
RETURN_IF_EXCEPTION(scope, {});
auto* win32 = Zig::createPath(globalObject, true);
RETURN_IF_EXCEPTION(scope, {});
binding->putDirectIndex(globalObject, (unsigned)1, win32);
RETURN_IF_EXCEPTION(scope, {});
return binding;
}
Expand Down
37 changes: 37 additions & 0 deletions test/js/web/workers/worker-terminate-lifetime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,3 +683,40 @@ test(
},
timeout,
);

// worker.terminate() landing while the worker was re-running its event loop for
// process.on('beforeExit') listeners (they scheduled more work) was never acted
// on: that inner drain only watched for the loop to go idle, and with the stop
// requested the in-flight work's completion is no longer delivered, so the
// worker slept in its loop forever and terminate() never settled.
test("terminate() while the worker drains work scheduled by 'beforeExit' stops it", async () => {
Comment thread
dylan-conway marked this conversation as resolved.
Outdated
using server = Bun.serve({ port: 0, fetch: () => new Promise<Response>(() => {}) });
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const { Worker } = require("node:worker_threads");
const w = new Worker(
"const { parentPort } = require('node:worker_threads');" +
"process.on('beforeExit', () => { fetch(process.env.HANG_URL).catch(() => {}); parentPort.postMessage('draining'); });" +
"process.on('exit', (c) => parentPort.postMessage('exit ' + c));",
{ eval: true },
);
w.on("message", async (m) => {
if (m !== "draining") { console.log("unexpected", m); return; }
const code = await w.terminate();
console.log("terminated", code);
});
w.on("exit", (c) => console.log("exit", c));
`,
],
env: { ...bunEnv, HANG_URL: server.url.href },
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout.trim().split("\n").sort()).toEqual(["exit 1", "terminated 1"]);
expect(exitCode).toBe(0);
});