From 41c2f808be651186ab3d4763db256f8ef0649833 Mon Sep 17 00:00:00 2001 From: Chris Knight Date: Fri, 10 Apr 2020 14:32:01 +0100 Subject: [PATCH 1/2] feature: syntatic sugar for listening to signal events --- std/signal/mod.ts | 43 ++++++++++++++++++++++++++++++++++++++++++- std/signal/test.ts | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/std/signal/mod.ts b/std/signal/mod.ts index 0f2b475342093a..c8aa62eef23226 100644 --- a/std/signal/mod.ts +++ b/std/signal/mod.ts @@ -1,8 +1,25 @@ import { MuxAsyncIterator } from "../util/async.ts"; +export type Disposable = { dispose: () => void }; + +/** + * Generates an AsyncIterable which can be awaited on for one or more signals. + * `dispose()` can be called when you are finished waiting on the events. + * + * Example: + * + * const sig = signal(Deno.Signal.SIGUSR1, Deno.Signal.SIGINT); + * setTimeout(() => {}, 5000); // Prevents exiting immediately + * + * for await (const _ of sig) { + * console.log("interrupt or usr1 signal received"); + * } + * + * @param signos - one or more `Deno.Signal`s to await on + */ export function signal( ...signos: [number, ...number[]] -): AsyncIterable & { dispose: () => void } { +): AsyncIterable & Disposable { const mux = new MuxAsyncIterator(); if (signos.length < 1) { @@ -26,3 +43,27 @@ export function signal( return Object.assign(mux, { dispose }); } + +/** + * Registers a callback function to be called on triggering of a signal event. + * + * const handle = onSignal(Deno.Signal.SIGINT, () => { + * console.log('Received SIGINT'); + * handle.dispose(); // de-register from receiving further events + * }); + * + * @param signo One of Deno.Signal (e.g. Deno.Signal.SIGINT) + * @param callback Callback function triggered upon signal event + */ +export function onSignal(signo: number, callback: () => void): Disposable { + const sig = signal(signo); + + //setTimeout allows `sig` to be returned before blocking on the await + setTimeout(async () => { + for await (const _ of sig) { + callback(); + } + }, 0); + + return sig; +} diff --git a/std/signal/test.ts b/std/signal/test.ts index 16d1458a2313a2..e36a697a53bf2e 100644 --- a/std/signal/test.ts +++ b/std/signal/test.ts @@ -1,7 +1,7 @@ const { test } = Deno; import { assertEquals, assertThrows } from "../testing/asserts.ts"; import { delay } from "../util/async.ts"; -import { signal } from "./mod.ts"; +import { signal, onSignal } from "./mod.ts"; if (Deno.build.os !== "win") { test("signal() throws when called with empty signals", (): void => { @@ -58,4 +58,38 @@ if (Deno.build.os !== "win") { await delay(10); }, }); + + test({ + name: "onSignal() registers and disposes of event handler", + async fn() { + // This prevents the program from exiting. + const t = setInterval(() => {}, 1000); + + let calledCount = 0; + const handle = onSignal(Deno.Signal.SIGINT, () => { + calledCount++; + }); + + await delay(20); + Deno.kill(Deno.pid, Deno.Signal.SIGINT); + await delay(20); + Deno.kill(Deno.pid, Deno.Signal.SIGINT); + await delay(20); + Deno.kill(Deno.pid, Deno.Signal.SIGUSR2); + await delay(20); + handle.dispose(); // stop monitoring SIGINT + await delay(20); + Deno.kill(Deno.pid, Deno.Signal.SIGUSR1); + await delay(20); + Deno.kill(Deno.pid, Deno.Signal.SIGINT); + await delay(20); + assertEquals(calledCount, 2); + + clearTimeout(t); + // Clear timeout clears interval, but interval promise is not + // yet resolved, delay to next turn of event loop otherwise, + // we'll be leaking resources. + await delay(10); + }, + }); } From 3f40bb6c3c5ca12d571fb0f74c7775f65b4130b5 Mon Sep 17 00:00:00 2001 From: Chris Knight Date: Fri, 10 Apr 2020 14:38:59 +0100 Subject: [PATCH 2/2] doc: add signal.dispose() example --- std/signal/mod.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/std/signal/mod.ts b/std/signal/mod.ts index c8aa62eef23226..a463142e6e611d 100644 --- a/std/signal/mod.ts +++ b/std/signal/mod.ts @@ -15,6 +15,9 @@ export type Disposable = { dispose: () => void }; * console.log("interrupt or usr1 signal received"); * } * + * // At some other point in your code when finished listening: + * sig.dispose(); + * * @param signos - one or more `Deno.Signal`s to await on */ export function signal(