Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 9 additions & 2 deletions src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4132,8 +4132,15 @@ extern "C" void Zig__GlobalObject__destructOnExit(Zig::GlobalObject* globalObjec
gcUnprotect(globalObject);
globalObject = nullptr;
vm.heap.collectNow(JSC::Sync, JSC::CollectionScope::Full);
vm.derefSuppressingSaferCPPChecking();
vm.derefSuppressingSaferCPPChecking();
// Drop every remaining VM ref so ~VM -> Heap::lastChanceToFinalize()
// destroys cells collectNow left conservatively reachable (whose native
// m_ctx would otherwise leak). Two refs are the creation pair; any beyond
// that are RefPtr<VM> in JSLockHolders still on the stack (e.g.
// JSEventListener::handleEvent when process.exit() came from
// worker.onmessage). global_exit is noreturn so their dtors never run,
// and workers were already joined, so no other thread holds the VM.
for (unsigned refs = vm.refCount(); refs > 0; --refs)
vm.derefSuppressingSaferCPPChecking();
runLoop->threadWillExit();
}

Expand Down
25 changes: 25 additions & 0 deletions test/js/web/timers/timer-heap-exit-onmessage-fixture.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions test/js/web/timers/timer-heap-race.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,24 @@ it.skipIf(!isASAN)(
},
20_000,
);

it.skipIf(!isASAN)(
"process.exit() from worker.onmessage finalizes the JSC heap",
async () => {
// handleEvent's JSLockHolder leaves an extra RefPtr<VM> on the stack at
// destructOnExit, so dropping only the two creation refs left ~VM (and
// lastChanceToFinalize) unreached and conservatively-live m_ctx leaked.
const { stdout, stderr, signal, exitCode } = await runFixture("timer-heap-exit-onmessage-fixture.ts", {
BUN_DESTRUCT_VM_ON_EXIT: "1",
ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=1:abort_on_error=1",
LSAN_OPTIONS: `malloc_context_size=30:print_suppressions=0:suppressions=${path.join(import.meta.dir, "..", "..", "..", "leaksan.supp")}`,
});
expect({ stdout, stderr, signal, exitCode }).toEqual({
stdout: "OK\n",
stderr: expect.not.stringContaining("LeakSanitizer"),
signal: null,
exitCode: 0,
});
},
20_000,
);
Loading