1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 08:39:09 -05:00

Add signal handlers (#3757)

This commit is contained in:
Yoshiya Hinosawa 2020-01-24 22:15:31 +09:00 committed by Ryan Dahl
parent a6a7253df9
commit bc89f04cbf
13 changed files with 596 additions and 2 deletions

View file

@ -101,6 +101,7 @@ export {
} from "./process.ts"; } from "./process.ts";
export { transpileOnly, compile, bundle } from "./compiler_api.ts"; export { transpileOnly, compile, bundle } from "./compiler_api.ts";
export { inspect } from "./console.ts"; export { inspect } from "./console.ts";
export { signal, signals, SignalStream } from "./signals.ts";
export { build, OperatingSystem, Arch } from "./build.ts"; export { build, OperatingSystem, Arch } from "./build.ts";
export { version } from "./version.ts"; export { version } from "./version.ts";
export const args: string[] = []; export const args: string[] = [];

View file

@ -74,6 +74,9 @@ export let OP_HOSTNAME: number;
export let OP_OPEN_PLUGIN: number; export let OP_OPEN_PLUGIN: number;
export let OP_COMPILE: number; export let OP_COMPILE: number;
export let OP_TRANSPILE: 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 /** **WARNING:** This is only available during the snapshotting process and is
* unavailable at runtime. */ * unavailable at runtime. */

View file

@ -2130,6 +2130,80 @@ declare namespace Deno {
*/ */
export const args: string[]; 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. /** UNSTABLE: new API. Maybe move EOF here.
* *
* Special Deno related symbols. * Special Deno related symbols.

View file

@ -296,7 +296,7 @@ enum MacOSSignal {
/** Signals numbers. This is platform dependent. /** Signals numbers. This is platform dependent.
*/ */
export const Signal = {}; export const Signal: { [key: string]: number } = {};
export function setSignals(): void { export function setSignals(): void {
if (build.os === "mac") { if (build.os === "mac") {

185
cli/js/signal_test.ts Normal file
View file

@ -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();
});
}

148
cli/js/signals.ts Normal file
View file

@ -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 });
}
}

View file

@ -42,6 +42,7 @@ import "./read_link_test.ts";
import "./rename_test.ts"; import "./rename_test.ts";
import "./request_test.ts"; import "./request_test.ts";
import "./resources_test.ts"; import "./resources_test.ts";
import "./signal_test.ts";
import "./stat_test.ts"; import "./stat_test.ts";
import "./symbols_test.ts"; import "./symbols_test.ts";
import "./symlink_test.ts"; import "./symlink_test.ts";

View file

@ -44,7 +44,7 @@ mod progress;
mod repl; mod repl;
pub mod resolve_addr; pub mod resolve_addr;
mod shell; mod shell;
mod signal; pub mod signal;
pub mod source_maps; pub mod source_maps;
mod startup_data; mod startup_data;
pub mod state; pub mod state;

View file

@ -22,6 +22,7 @@ pub mod random;
pub mod repl; pub mod repl;
pub mod resources; pub mod resources;
pub mod runtime_compiler; pub mod runtime_compiler;
pub mod signal;
pub mod timers; pub mod timers;
pub mod tls; pub mod tls;
pub mod web_worker; pub mod web_worker;

146
cli/ops/signal.rs Normal file
View file

@ -0,0 +1,146 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use super::dispatch_json::{JsonOp, Value};
use crate::ops::json_op;
use crate::state::ThreadSafeState;
use deno_core::*;
#[cfg(unix)]
use super::dispatch_json::Deserialize;
#[cfg(unix)]
use crate::deno_error::bad_resource;
#[cfg(unix)]
use deno_core::Resource;
#[cfg(unix)]
use futures::future::{poll_fn, FutureExt};
#[cfg(unix)]
use serde_json;
#[cfg(unix)]
use std::task::Waker;
#[cfg(unix)]
use tokio::signal::unix::{signal, Signal, SignalKind};
pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
i.register_op(
"signal_bind",
s.core_op(json_op(s.stateful_op(op_signal_bind))),
);
i.register_op(
"signal_unbind",
s.core_op(json_op(s.stateful_op(op_signal_unbind))),
);
i.register_op(
"signal_poll",
s.core_op(json_op(s.stateful_op(op_signal_poll))),
);
}
#[cfg(unix)]
/// The resource for signal stream.
/// The second element is the waker of polling future.
pub struct SignalStreamResource(pub Signal, pub Option<Waker>);
#[cfg(unix)]
impl Resource for SignalStreamResource {}
#[cfg(unix)]
#[derive(Deserialize)]
struct BindSignalArgs {
signo: i32,
}
#[cfg(unix)]
#[derive(Deserialize)]
struct SignalArgs {
rid: i32,
}
#[cfg(unix)]
fn op_signal_bind(
state: &ThreadSafeState,
args: Value,
_zero_copy: Option<PinnedBuf>,
) -> Result<JsonOp, ErrBox> {
let args: BindSignalArgs = serde_json::from_value(args)?;
let mut table = state.lock_resource_table();
let rid = table.add(
"signal",
Box::new(SignalStreamResource(
signal(SignalKind::from_raw(args.signo)).expect(""),
None,
)),
);
Ok(JsonOp::Sync(json!({
"rid": rid,
})))
}
#[cfg(unix)]
fn op_signal_poll(
state: &ThreadSafeState,
args: Value,
_zero_copy: Option<PinnedBuf>,
) -> Result<JsonOp, ErrBox> {
let args: SignalArgs = serde_json::from_value(args)?;
let rid = args.rid as u32;
let state_ = state.clone();
let future = poll_fn(move |cx| {
let mut table = state_.lock_resource_table();
if let Some(mut signal) = table.get_mut::<SignalStreamResource>(rid) {
signal.1 = Some(cx.waker().clone());
return signal.0.poll_recv(cx);
}
std::task::Poll::Ready(None)
})
.then(|result| async move { Ok(json!({ "done": result.is_none() })) });
Ok(JsonOp::AsyncUnref(future.boxed()))
}
#[cfg(unix)]
pub fn op_signal_unbind(
state: &ThreadSafeState,
args: Value,
_zero_copy: Option<PinnedBuf>,
) -> Result<JsonOp, ErrBox> {
let args: SignalArgs = serde_json::from_value(args)?;
let rid = args.rid as u32;
let mut table = state.lock_resource_table();
let resource = table.get::<SignalStreamResource>(rid);
if let Some(signal) = resource {
if let Some(waker) = &signal.1 {
// Wakes up the pending poll if exists.
// This prevents the poll future from getting stuck forever.
waker.clone().wake();
}
}
table.close(rid).ok_or_else(bad_resource)?;
Ok(JsonOp::Sync(json!({})))
}
#[cfg(not(unix))]
pub fn op_signal_bind(
_state: &ThreadSafeState,
_args: Value,
_zero_copy: Option<PinnedBuf>,
) -> Result<JsonOp, ErrBox> {
unimplemented!();
}
#[cfg(not(unix))]
fn op_signal_unbind(
_state: &ThreadSafeState,
_args: Value,
_zero_copy: Option<PinnedBuf>,
) -> Result<JsonOp, ErrBox> {
unimplemented!();
}
#[cfg(not(unix))]
fn op_signal_poll(
_state: &ThreadSafeState,
_args: Value,
_zero_copy: Option<PinnedBuf>,
) -> Result<JsonOp, ErrBox> {
unimplemented!();
}

View file

@ -1,3 +1,4 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use deno_core::ErrBox; use deno_core::ErrBox;
#[cfg(unix)] #[cfg(unix)]

View file

@ -200,6 +200,7 @@ impl MainWorker {
ops::random::init(&mut isolate, &state); ops::random::init(&mut isolate, &state);
ops::repl::init(&mut isolate, &state); ops::repl::init(&mut isolate, &state);
ops::resources::init(&mut isolate, &state); ops::resources::init(&mut isolate, &state);
ops::signal::init(&mut isolate, &state);
ops::timers::init(&mut isolate, &state); ops::timers::init(&mut isolate, &state);
ops::worker_host::init(&mut isolate, &state); ops::worker_host::init(&mut isolate, &state);
ops::web_worker::init(&mut isolate, &state); ops::web_worker::init(&mut isolate, &state);

View file

@ -428,6 +428,39 @@ Uncaught NotFound: No such file or directory (os error 2)
at handleAsyncMsgFromRust (deno/js/dispatch.ts:27:17) at handleAsyncMsgFromRust (deno/js/dispatch.ts:27:17)
``` ```
### Handle OS Signals
[API Reference](https://deno.land/typedoc/index.html#signal)
You can use `Deno.signal()` function for handling OS signals.
```
for await (const _ of Deno.signal(Deno.Signal.SIGINT)) {
console.log("interrupted!");
}
```
`Deno.signal()` also works as a promise.
```
await Deno.signal(Deno.Singal.SIGINT);
console.log("interrupted!");
```
If you want to stop watching the signal, you can use `dispose()` method of the
signal object.
```
const sig = Deno.signal(Deno.Signal.SIGINT);
setTimeout(() => { sig.dispose(); }, 5000);
for await (const _ of sig) {
console.log("interrupted");
}
```
The above for-await loop exits after 5 seconds when sig.dispose() is called.
### Linking to third party code ### Linking to third party code
In the above examples, we saw that Deno could execute scripts from URLs. Like In the above examples, we saw that Deno could execute scripts from URLs. Like