Skip to content
Merged
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
33 changes: 33 additions & 0 deletions mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2391,6 +2391,39 @@ Deno.test("should error creating a command signal", () => {
);
});

Deno.test("kill signal: a throwing listener doesn't stop dispatch to the remaining listeners", () => {
// capture the rethrows instead of letting them become uncaught errors
const scheduled: VoidFunction[] = [];
const originalQueueMicrotask = globalThis.queueMicrotask;
globalThis.queueMicrotask = (fn: VoidFunction) => scheduled.push(fn);
try {
const controller = new KillController();
controller.signal.addListener(() => {
throw new Error("listener error");
});
const received: Signal[] = [];
controller.signal.addListener((signal) => received.push(signal));
controller.kill("SIGTERM"); // must not throw
assertEquals(received, ["SIGTERM"]);
assertEquals(scheduled.length, 1);
assertThrows(() => scheduled[0](), Error, "listener error");
} finally {
globalThis.queueMicrotask = originalQueueMicrotask;
}
});

Deno.test("kill signal: a listener removing itself doesn't skip the next listener", () => {
const controller = new KillController();
const received: Signal[] = [];
const selfRemoving = () => {
controller.signal.removeListener(selfRemoving);
};
controller.signal.addListener(selfRemoving);
controller.signal.addListener((signal) => received.push(signal));
controller.kill("SIGTERM");
assertEquals(received, ["SIGTERM"]);
});

Deno.test("should receive signal when listening", { ignore: process.platform !== "linux" }, async () => {
const p =
$`deno eval 'Deno.addSignalListener("SIGINT", () => console.log("RECEIVED SIGINT")); console.log("started"); setTimeout(() => {}, 10_000)'`
Expand Down
14 changes: 12 additions & 2 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2039,8 +2039,18 @@ function sendSignalToState(state: KillSignalState, signal: Signal) {
if (code !== undefined) {
state.abortedCode = code;
}
for (const listener of state.listeners) {
listener(signal);
// copy in case a listener adds/removes listeners while being invoked
for (const listener of [...state.listeners]) {
try {
listener(signal);
} catch (err) {
// a throwing listener must not prevent the remaining listeners from
// receiving the signal (ex. the ones forwarding the kill to child
// processes), so surface it as an uncaught error instead
queueMicrotask(() => {
throw err;
});
}
}
}

Expand Down
Loading