Skip to content

Commit 10b7e3f

Browse files
MikeyJensenCopilot
andcommitted
fix: restore clearTimeout in shutdown, test belt-and-suspenders guard
- Restore clearTimeout(restartTimer) in shutdown() (primary guard) - Remove v8 ignore pragma from shuttingDown guard (backup guard) - Add monkeypatch test that neuters clearTimeout to exercise the backup guard, proving it catches the timer callback race - 69 tests, 100% line/branch/function coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d89425c commit 10b7e3f

File tree

2 files changed

+35
-10
lines changed

2 files changed

+35
-10
lines changed

embed-pool.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export function createEmbedPool(workerFactory, opts = {}) {
4141
workerReadyPromise = new Promise(resolve => { workerReadyResolve = resolve; });
4242
restartTimer = setTimeout(() => {
4343
restartTimer = null;
44+
if (shuttingDown) return;
4445
try {
4546
initWorker();
4647
currentRestartDelay = RESTART_DELAY_MS;
@@ -128,10 +129,7 @@ export function createEmbedPool(workerFactory, opts = {}) {
128129

129130
function shutdown() {
130131
shuttingDown = true;
131-
if (restartTimer) {
132-
clearTimeout(restartTimer);
133-
restartTimer = null;
134-
}
132+
clearTimeout(restartTimer);
135133
rejectAllPending("Pool shutting down");
136134
if (worker) {
137135
worker.terminate();

test.js

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -690,19 +690,19 @@ describe("createEmbedPool", () => {
690690

691691
it("BUG: shutdown during restart delay still spawns zombie worker", async () => {
692692
const factory = mockWorkerFactory();
693-
const pool = createEmbedPool(factory, { restartDelay: 200 });
693+
const pool = createEmbedPool(factory, { restartDelay: 50 });
694694
pool.initWorker();
695695

696-
// Worker exits — restart is scheduled with 200ms delay
696+
// Worker exits — restart is scheduled with 50ms delay
697697
factory.workers[0].emit("exit", 1);
698698

699-
// Shutdown immediately (before the 200ms restart fires)
699+
// Shutdown immediately (before the 50ms restart fires)
700700
pool.shutdown();
701701

702-
// Wait for the restart timer to fire
703-
await new Promise(r => setTimeout(r, 350));
702+
// Wait for the restart timer to fire (guard should catch it)
703+
await new Promise(r => setTimeout(r, 150));
704704

705-
// Should NOT have created a second worker — shutdown should cancel the restart
705+
// Should NOT have created a second worker — guard prevents it
706706
assert.equal(factory.workers.length, 1,
707707
`Expected 1 worker but got ${factory.workers.length} — zombie worker spawned after shutdown`);
708708
});
@@ -904,6 +904,33 @@ describe("createEmbedPool", () => {
904904
assert.equal(factory.workers.length, 1, "shutdown should cancel pending restart");
905905
});
906906

907+
it("belt-and-suspenders: shuttingDown guard catches callback when clearTimeout fails", async () => {
908+
const factory = mockWorkerFactory();
909+
const pool = createEmbedPool(factory, { restartDelay: 50 });
910+
pool.initWorker();
911+
912+
// Worker exits — restart timer is scheduled
913+
factory.workers[0].emit("exit", 1);
914+
915+
// Monkeypatch clearTimeout to a no-op, simulating a race where
916+
// the timer callback is already queued when shutdown tries to cancel it
917+
const realClearTimeout = globalThis.clearTimeout;
918+
globalThis.clearTimeout = () => {};
919+
920+
try {
921+
pool.shutdown();
922+
} finally {
923+
globalThis.clearTimeout = realClearTimeout;
924+
}
925+
926+
// Timer fires because clearTimeout was neutered — the shuttingDown guard catches it
927+
await new Promise(r => setTimeout(r, 150));
928+
929+
// No zombie worker spawned — guard did its job
930+
assert.equal(factory.workers.length, 1,
931+
"shuttingDown guard should prevent zombie worker when clearTimeout fails");
932+
});
933+
907934
it("uses default options when none provided", async () => {
908935
const factory = mockWorkerFactory();
909936
const pool = createEmbedPool(factory);

0 commit comments

Comments
 (0)