Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add signal handlers #3757

Merged
merged 12 commits into from
Jan 24, 2020
1 change: 1 addition & 0 deletions cli/js/deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];
Expand Down
3 changes: 3 additions & 0 deletions cli/js/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
74 changes: 74 additions & 0 deletions cli/js/lib.deno_runtime.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
ry marked this conversation as resolved.
Show resolved Hide resolved
* 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.
Expand Down
2 changes: 1 addition & 1 deletion cli/js/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
185 changes: 185 additions & 0 deletions cli/js/signal_test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
}
Loading