diff options
Diffstat (limited to 'cli/js')
-rw-r--r-- | cli/js/deno.ts | 1 | ||||
-rw-r--r-- | cli/js/dispatch.ts | 3 | ||||
-rw-r--r-- | cli/js/lib.deno_runtime.d.ts | 74 | ||||
-rw-r--r-- | cli/js/process.ts | 2 | ||||
-rw-r--r-- | cli/js/signal_test.ts | 185 | ||||
-rw-r--r-- | cli/js/signals.ts | 148 | ||||
-rw-r--r-- | cli/js/unit_tests.ts | 1 |
7 files changed, 413 insertions, 1 deletions
diff --git a/cli/js/deno.ts b/cli/js/deno.ts index e53f9a63a..2d20ae811 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -101,6 +101,7 @@ export { } from "./process.ts"; export { transpileOnly, compile, bundle } from "./compiler_api.ts"; export { inspect } from "./console.ts"; +export { signal, signals, SignalStream } from "./signals.ts"; export { build, OperatingSystem, Arch } from "./build.ts"; export { version } from "./version.ts"; export const args: string[] = []; diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts index f5049cca8..aa6696fa2 100644 --- a/cli/js/dispatch.ts +++ b/cli/js/dispatch.ts @@ -74,6 +74,9 @@ export let OP_HOSTNAME: number; export let OP_OPEN_PLUGIN: number; export let OP_COMPILE: number; export let OP_TRANSPILE: number; +export let OP_SIGNAL_BIND: number; +export let OP_SIGNAL_UNBIND: number; +export let OP_SIGNAL_POLL: number; /** **WARNING:** This is only available during the snapshotting process and is * unavailable at runtime. */ diff --git a/cli/js/lib.deno_runtime.d.ts b/cli/js/lib.deno_runtime.d.ts index efdf06347..1dfa3209f 100644 --- a/cli/js/lib.deno_runtime.d.ts +++ b/cli/js/lib.deno_runtime.d.ts @@ -2130,6 +2130,80 @@ declare namespace Deno { */ export const args: string[]; + /** SignalStream represents the stream of signals, implements both + * AsyncIterator and PromiseLike */ + export class SignalStream implements AsyncIterator<void>, PromiseLike<void> { + constructor(signal: typeof Deno.Signal); + then<T, S>( + f: (v: void) => T | Promise<T>, + g?: (v: void) => S | Promise<S> + ): Promise<T | S>; + next(): Promise<IteratorResult<void>>; + [Symbol.asyncIterator](): AsyncIterator<void>; + dispose(): void; + } + /** + * Returns the stream of the given signal number. You can use it as an async + * iterator. + * + * for await (const _ of Deno.signal(Deno.Signal.SIGTERM)) { + * console.log("got SIGTERM!"); + * } + * + * You can also use it as a promise. In this case you can only receive the + * first one. + * + * await Deno.signal(Deno.Signal.SIGTERM); + * console.log("SIGTERM received!") + * + * If you want to stop receiving the signals, you can use .dispose() method + * of the signal stream object. + * + * const sig = Deno.signal(Deno.Signal.SIGTERM); + * setTimeout(() => { sig.dispose(); }, 5000); + * for await (const _ of sig) { + * console.log("SIGTERM!") + * } + * + * The above for-await loop exits after 5 seconds when sig.dispose() is called. + */ + export function signal(signo: number): SignalStream; + export const signals: { + /** Returns the stream of SIGALRM signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGALRM). */ + alarm: () => SignalStream; + /** Returns the stream of SIGCHLD signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGCHLD). */ + child: () => SignalStream; + /** Returns the stream of SIGHUP signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGHUP). */ + hungup: () => SignalStream; + /** Returns the stream of SIGINT signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGINT). */ + interrupt: () => SignalStream; + /** Returns the stream of SIGIO signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGIO). */ + io: () => SignalStream; + /** Returns the stream of SIGPIPE signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGPIPE). */ + pipe: () => SignalStream; + /** Returns the stream of SIGQUIT signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGQUIT). */ + quit: () => SignalStream; + /** Returns the stream of SIGTERM signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGTERM). */ + terminate: () => SignalStream; + /** Returns the stream of SIGUSR1 signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGUSR1). */ + userDefined1: () => SignalStream; + /** Returns the stream of SIGUSR2 signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGUSR2). */ + userDefined2: () => SignalStream; + /** Returns the stream of SIGWINCH signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGWINCH). */ + windowChange: () => SignalStream; + }; + /** UNSTABLE: new API. Maybe move EOF here. * * Special Deno related symbols. diff --git a/cli/js/process.ts b/cli/js/process.ts index 8ad6384b7..5267763c1 100644 --- a/cli/js/process.ts +++ b/cli/js/process.ts @@ -296,7 +296,7 @@ enum MacOSSignal { /** Signals numbers. This is platform dependent. */ -export const Signal = {}; +export const Signal: { [key: string]: number } = {}; export function setSignals(): void { if (build.os === "mac") { diff --git a/cli/js/signal_test.ts b/cli/js/signal_test.ts new file mode 100644 index 000000000..06457314c --- /dev/null +++ b/cli/js/signal_test.ts @@ -0,0 +1,185 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { + test, + testPerm, + assert, + assertEquals, + assertThrows +} from "./test_util.ts"; + +function defer(n: number): Promise<void> { + return new Promise((resolve, _) => { + setTimeout(resolve, n); + }); +} + +if (Deno.build.os === "win") { + test(async function signalsNotImplemented(): Promise<void> { + assertThrows( + () => { + Deno.signal(1); + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.alarm(); // for SIGALRM + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.child(); // for SIGCHLD + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.hungup(); // for SIGHUP + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.interrupt(); // for SIGINT + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.io(); // for SIGIO + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.pipe(); // for SIGPIPE + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.quit(); // for SIGQUIT + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.terminate(); // for SIGTERM + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.userDefined1(); // for SIGUSR1 + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.userDefined2(); // for SIGURS2 + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.windowChange(); // for SIGWINCH + }, + Error, + "not implemented" + ); + }); +} else { + testPerm({ run: true, net: true }, async function signalStreamTest(): Promise< + void + > { + // This prevents the program from exiting. + const t = setInterval(() => {}, 1000); + + let c = 0; + const sig = Deno.signal(Deno.Signal.SIGUSR1); + + setTimeout(async () => { + await defer(20); + for (const _ of Array(3)) { + // Sends SIGUSR1 3 times. + Deno.kill(Deno.pid, Deno.Signal.SIGUSR1); + await defer(20); + } + sig.dispose(); + }); + + for await (const _ of sig) { + c += 1; + } + + assertEquals(c, 3); + + clearTimeout(t); + }); + + testPerm( + { run: true, net: true }, + async function signalPromiseTest(): Promise<void> { + // This prevents the program from exiting. + const t = setInterval(() => {}, 1000); + + const sig = Deno.signal(Deno.Signal.SIGUSR1); + setTimeout(() => { + Deno.kill(Deno.pid, Deno.Signal.SIGUSR1); + }, 20); + await sig; + sig.dispose(); + + clearTimeout(t); + } + ); + + testPerm({ run: true }, async function signalShorthandsTest(): Promise<void> { + let s: Deno.SignalStream; + s = Deno.signals.alarm(); // for SIGALRM + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.child(); // for SIGCHLD + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.hungup(); // for SIGHUP + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.interrupt(); // for SIGINT + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.io(); // for SIGIO + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.pipe(); // for SIGPIPE + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.quit(); // for SIGQUIT + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.terminate(); // for SIGTERM + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.userDefined1(); // for SIGUSR1 + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.userDefined2(); // for SIGURS2 + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.windowChange(); // for SIGWINCH + assert(s instanceof Deno.SignalStream); + s.dispose(); + }); +} diff --git a/cli/js/signals.ts b/cli/js/signals.ts new file mode 100644 index 000000000..02d52bc2f --- /dev/null +++ b/cli/js/signals.ts @@ -0,0 +1,148 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { Signal } from "./process.ts"; +import * as dispatch from "./dispatch.ts"; +import { sendSync, sendAsync } from "./dispatch_json.ts"; +import { build } from "./build.ts"; + +/** + * Returns the stream of the given signal number. You can use it as an async + * iterator. + * + * for await (const _ of Deno.signal(Deno.Signal.SIGTERM)) { + * console.log("got SIGTERM!"); + * } + * + * You can also use it as a promise. In this case you can only receive the + * first one. + * + * await Deno.signal(Deno.Signal.SIGTERM); + * console.log("SIGTERM received!") + * + * If you want to stop receiving the signals, you can use .dispose() method + * of the signal stream object. + * + * const sig = Deno.signal(Deno.Signal.SIGTERM); + * setTimeout(() => { sig.dispose(); }, 5000); + * for await (const _ of sig) { + * console.log("SIGTERM!") + * } + * + * The above for-await loop exits after 5 seconds when sig.dispose() is called. + */ +export function signal(signo: number): SignalStream { + if (build.os === "win") { + throw new Error("not implemented!"); + } + return new SignalStream(signo); +} + +export const signals = { + /** Returns the stream of SIGALRM signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGALRM). */ + alarm(): SignalStream { + return signal(Signal.SIGALRM); + }, + /** Returns the stream of SIGCHLD signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGCHLD). */ + child(): SignalStream { + return signal(Signal.SIGCHLD); + }, + /** Returns the stream of SIGHUP signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGHUP). */ + hungup(): SignalStream { + return signal(Signal.SIGHUP); + }, + /** Returns the stream of SIGINT signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGINT). */ + interrupt(): SignalStream { + return signal(Signal.SIGINT); + }, + /** Returns the stream of SIGIO signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGIO). */ + io(): SignalStream { + return signal(Signal.SIGIO); + }, + /** Returns the stream of SIGPIPE signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGPIPE). */ + pipe(): SignalStream { + return signal(Signal.SIGPIPE); + }, + /** Returns the stream of SIGQUIT signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGQUIT). */ + quit(): SignalStream { + return signal(Signal.SIGQUIT); + }, + /** Returns the stream of SIGTERM signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGTERM). */ + terminate(): SignalStream { + return signal(Signal.SIGTERM); + }, + /** Returns the stream of SIGUSR1 signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGUSR1). */ + userDefined1(): SignalStream { + return signal(Signal.SIGUSR1); + }, + /** Returns the stream of SIGUSR2 signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGUSR2). */ + userDefined2(): SignalStream { + return signal(Signal.SIGUSR2); + }, + /** Returns the stream of SIGWINCH signals. + * This method is the shorthand for Deno.signal(Deno.Signal.SIGWINCH). */ + windowChange(): SignalStream { + return signal(Signal.SIGWINCH); + } +}; + +/** SignalStream represents the stream of signals, implements both + * AsyncIterator and PromiseLike */ +export class SignalStream implements AsyncIterator<void>, PromiseLike<void> { + private rid: number; + /** The promise of polling the signal, + * resolves with false when it receives signal, + * Resolves with true when the signal stream is disposed. */ + private pollingPromise: Promise<boolean> = Promise.resolve(false); + /** The flag, which is true when the stream is disposed. */ + private disposed = false; + constructor(signo: number) { + this.rid = sendSync(dispatch.OP_SIGNAL_BIND, { signo }).rid; + this.loop(); + } + + private async pollSignal(): Promise<boolean> { + return ( + await sendAsync(dispatch.OP_SIGNAL_POLL, { + rid: this.rid + }) + ).done; + } + + private async loop(): Promise<void> { + do { + this.pollingPromise = this.pollSignal(); + } while (!(await this.pollingPromise) && !this.disposed); + } + + then<T, S>( + f: (v: void) => T | Promise<T>, + g?: (v: Error) => S | Promise<S> + ): Promise<T | S> { + return this.pollingPromise.then((_): void => {}).then(f, g); + } + + async next(): Promise<IteratorResult<void>> { + return { done: await this.pollingPromise, value: undefined }; + } + + [Symbol.asyncIterator](): AsyncIterator<void> { + return this; + } + + dispose(): void { + if (this.disposed) { + throw new Error("The stream has already been disposed."); + } + this.disposed = true; + sendSync(dispatch.OP_SIGNAL_UNBIND, { rid: this.rid }); + } +} diff --git a/cli/js/unit_tests.ts b/cli/js/unit_tests.ts index 084661ab8..47ae06b19 100644 --- a/cli/js/unit_tests.ts +++ b/cli/js/unit_tests.ts @@ -42,6 +42,7 @@ import "./read_link_test.ts"; import "./rename_test.ts"; import "./request_test.ts"; import "./resources_test.ts"; +import "./signal_test.ts"; import "./stat_test.ts"; import "./symbols_test.ts"; import "./symlink_test.ts"; |