mirror of
https://github.com/denoland/deno.git
synced 2025-09-26 20:29:11 +00:00
fix: new signal handling (#30029)
Some checks are pending
ci / pre-build (push) Waiting to run
ci / test debug linux-aarch64 (push) Blocked by required conditions
ci / test release linux-aarch64 (push) Blocked by required conditions
ci / test debug macos-aarch64 (push) Blocked by required conditions
ci / test release macos-aarch64 (push) Blocked by required conditions
ci / bench release linux-x86_64 (push) Blocked by required conditions
ci / lint debug linux-x86_64 (push) Blocked by required conditions
ci / lint debug macos-x86_64 (push) Blocked by required conditions
ci / lint debug windows-x86_64 (push) Blocked by required conditions
ci / test debug linux-x86_64 (push) Blocked by required conditions
ci / test release linux-x86_64 (push) Blocked by required conditions
ci / test debug macos-x86_64 (push) Blocked by required conditions
ci / test release macos-x86_64 (push) Blocked by required conditions
ci / test debug windows-x86_64 (push) Blocked by required conditions
ci / test release windows-x86_64 (push) Blocked by required conditions
ci / publish canary (push) Blocked by required conditions
ci / build libs (push) Blocked by required conditions
Some checks are pending
ci / pre-build (push) Waiting to run
ci / test debug linux-aarch64 (push) Blocked by required conditions
ci / test release linux-aarch64 (push) Blocked by required conditions
ci / test debug macos-aarch64 (push) Blocked by required conditions
ci / test release macos-aarch64 (push) Blocked by required conditions
ci / bench release linux-x86_64 (push) Blocked by required conditions
ci / lint debug linux-x86_64 (push) Blocked by required conditions
ci / lint debug macos-x86_64 (push) Blocked by required conditions
ci / lint debug windows-x86_64 (push) Blocked by required conditions
ci / test debug linux-x86_64 (push) Blocked by required conditions
ci / test release linux-x86_64 (push) Blocked by required conditions
ci / test debug macos-x86_64 (push) Blocked by required conditions
ci / test release macos-x86_64 (push) Blocked by required conditions
ci / test debug windows-x86_64 (push) Blocked by required conditions
ci / test release windows-x86_64 (push) Blocked by required conditions
ci / publish canary (push) Blocked by required conditions
ci / build libs (push) Blocked by required conditions
Set up a new centralized signal handling path, which emulates the default unless something is explicitly registered to prevent it. Fixes: https://github.com/denoland/deno/issues/29590 --------- Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
c65a48a488
commit
88f16236b1
13 changed files with 253 additions and 248 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -2366,6 +2366,7 @@ dependencies = [
|
||||||
"deno_error",
|
"deno_error",
|
||||||
"deno_features",
|
"deno_features",
|
||||||
"deno_permissions",
|
"deno_permissions",
|
||||||
|
"deno_signals",
|
||||||
"deno_tls",
|
"deno_tls",
|
||||||
"hickory-proto",
|
"hickory-proto",
|
||||||
"hickory-resolver",
|
"hickory-resolver",
|
||||||
|
@ -2609,14 +2610,12 @@ dependencies = [
|
||||||
"deno_error",
|
"deno_error",
|
||||||
"deno_path_util",
|
"deno_path_util",
|
||||||
"deno_permissions",
|
"deno_permissions",
|
||||||
"deno_telemetry",
|
"deno_signals",
|
||||||
"libc",
|
"libc",
|
||||||
"netif",
|
"netif",
|
||||||
"ntapi",
|
"ntapi",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
"signal-hook",
|
|
||||||
"signal-hook-registry",
|
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
@ -2842,6 +2841,14 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deno_signals"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"signal-hook",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_snapshots"
|
name = "deno_snapshots"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
|
@ -2888,6 +2895,7 @@ dependencies = [
|
||||||
"deno_core",
|
"deno_core",
|
||||||
"deno_error",
|
"deno_error",
|
||||||
"deno_net",
|
"deno_net",
|
||||||
|
"deno_signals",
|
||||||
"deno_tls",
|
"deno_tls",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
|
|
|
@ -25,6 +25,7 @@ members = [
|
||||||
"ext/net",
|
"ext/net",
|
||||||
"ext/node",
|
"ext/node",
|
||||||
"ext/rt_helper",
|
"ext/rt_helper",
|
||||||
|
"ext/signals",
|
||||||
"ext/telemetry",
|
"ext/telemetry",
|
||||||
"ext/url",
|
"ext/url",
|
||||||
"ext/web",
|
"ext/web",
|
||||||
|
@ -101,6 +102,7 @@ deno_net = { version = "0.202.0", path = "./ext/net" }
|
||||||
deno_node = { version = "0.148.0", path = "./ext/node" }
|
deno_node = { version = "0.148.0", path = "./ext/node" }
|
||||||
deno_os = { version = "0.27.0", path = "./ext/os" }
|
deno_os = { version = "0.27.0", path = "./ext/os" }
|
||||||
deno_process = { version = "0.25.0", path = "./ext/process" }
|
deno_process = { version = "0.25.0", path = "./ext/process" }
|
||||||
|
deno_signals = { version = "0.1.0", path = "./ext/signals" }
|
||||||
deno_telemetry = { version = "0.32.0", path = "./ext/telemetry" }
|
deno_telemetry = { version = "0.32.0", path = "./ext/telemetry" }
|
||||||
deno_tls = { version = "0.197.0", path = "./ext/tls" }
|
deno_tls = { version = "0.197.0", path = "./ext/tls" }
|
||||||
deno_url = { version = "0.210.0", path = "./ext/url" }
|
deno_url = { version = "0.210.0", path = "./ext/url" }
|
||||||
|
@ -242,6 +244,7 @@ serde-value = "0.7"
|
||||||
serde_bytes = "0.11"
|
serde_bytes = "0.11"
|
||||||
serde_json = "1.0.85"
|
serde_json = "1.0.85"
|
||||||
serde_repr = "=0.1.19"
|
serde_repr = "=0.1.19"
|
||||||
|
signal-hook = "0.3"
|
||||||
simd-json = "0.14.0"
|
simd-json = "0.14.0"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
smallvec = "1.8"
|
smallvec = "1.8"
|
||||||
|
|
|
@ -30,6 +30,7 @@ use deno_core::serde_v8::from_v8;
|
||||||
use deno_core::unsync::JoinHandle;
|
use deno_core::unsync::JoinHandle;
|
||||||
use deno_core::unsync::spawn;
|
use deno_core::unsync::spawn;
|
||||||
use deno_core::v8;
|
use deno_core::v8;
|
||||||
|
use deno_error::JsErrorClass;
|
||||||
use deno_net::ops_tls::TlsStream;
|
use deno_net::ops_tls::TlsStream;
|
||||||
use deno_net::raw::NetworkStream;
|
use deno_net::raw::NetworkStream;
|
||||||
use deno_websocket::ws_create_server_stream;
|
use deno_websocket::ws_create_server_stream;
|
||||||
|
@ -1299,6 +1300,23 @@ pub async fn op_http_wait(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let HttpNextError::Other(err) = &err {
|
||||||
|
if let Some(err) = err.as_any().downcast_ref::<std::io::Error>() {
|
||||||
|
if let Some(err) = err.get_ref() {
|
||||||
|
if let Some(err) =
|
||||||
|
err.downcast_ref::<deno_net::tunnel::quinn::ConnectionError>()
|
||||||
|
{
|
||||||
|
if matches!(
|
||||||
|
err,
|
||||||
|
deno_net::tunnel::quinn::ConnectionError::LocallyClosed
|
||||||
|
) {
|
||||||
|
return Ok(null());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ deno_core.workspace = true
|
||||||
deno_error.workspace = true
|
deno_error.workspace = true
|
||||||
deno_features.workspace = true
|
deno_features.workspace = true
|
||||||
deno_permissions.workspace = true
|
deno_permissions.workspace = true
|
||||||
|
deno_signals.workspace = true
|
||||||
deno_tls.workspace = true
|
deno_tls.workspace = true
|
||||||
hickory-proto.workspace = true
|
hickory-proto.workspace = true
|
||||||
hickory-resolver.workspace = true
|
hickory-resolver.workspace = true
|
||||||
|
|
|
@ -21,6 +21,7 @@ use deno_tls::SocketUse;
|
||||||
use deno_tls::TlsKeys;
|
use deno_tls::TlsKeys;
|
||||||
use deno_tls::create_client_config;
|
use deno_tls::create_client_config;
|
||||||
use deno_tls::rustls::RootCertStore;
|
use deno_tls::rustls::RootCertStore;
|
||||||
|
pub use quinn;
|
||||||
use quinn::ConnectionError;
|
use quinn::ConnectionError;
|
||||||
use quinn::crypto::rustls::QuicClientConfig;
|
use quinn::crypto::rustls::QuicClientConfig;
|
||||||
use tokio::io::AsyncRead;
|
use tokio::io::AsyncRead;
|
||||||
|
@ -61,7 +62,16 @@ static TUNNEL: OnceLock<crate::tunnel::TunnelListener> = OnceLock::new();
|
||||||
|
|
||||||
pub fn set_tunnel(tunnel: crate::tunnel::TunnelListener) {
|
pub fn set_tunnel(tunnel: crate::tunnel::TunnelListener) {
|
||||||
if TUNNEL.set(tunnel).is_ok() {
|
if TUNNEL.set(tunnel).is_ok() {
|
||||||
setup_signal_handlers();
|
deno_signals::before_exit(before_exit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn before_exit() {
|
||||||
|
if let Some(tunnel) = get_tunnel() {
|
||||||
|
tunnel.connection.close(1u32.into(), b"");
|
||||||
|
// stay alive long enough to actually send the close frame, since
|
||||||
|
// we can't rely on the linux kernel to close this like with tcp.
|
||||||
|
deno_core::futures::executor::block_on(tunnel.endpoint.wait_idle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,57 +79,6 @@ pub fn get_tunnel() -> Option<&'static crate::tunnel::TunnelListener> {
|
||||||
TUNNEL.get()
|
TUNNEL.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn setup_signal_handlers() {
|
|
||||||
use tokio::signal::unix::SignalKind;
|
|
||||||
|
|
||||||
let signals_to_handle = [
|
|
||||||
SignalKind::hangup(),
|
|
||||||
SignalKind::interrupt(),
|
|
||||||
SignalKind::terminate(),
|
|
||||||
];
|
|
||||||
|
|
||||||
for signal_kind in signals_to_handle {
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let Ok(mut signal_fut) = tokio::signal::unix::signal(signal_kind) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
signal_fut.recv().await;
|
|
||||||
if let Some(tunnel) = get_tunnel() {
|
|
||||||
tunnel.connection.close(1u32.into(), b"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn setup_signal_handlers() {
|
|
||||||
macro_rules! handle_signal {
|
|
||||||
($handler:expr) => {
|
|
||||||
tokio::spawn(async {
|
|
||||||
let Ok(mut signal_fut) = $handler() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
loop {
|
|
||||||
signal_fut.recv().await;
|
|
||||||
if let Some(tunnel) = get_tunnel() {
|
|
||||||
tunnel.connection.close(1u32.into(), b"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_signal!(tokio::signal::windows::ctrl_break);
|
|
||||||
handle_signal!(tokio::signal::windows::ctrl_c);
|
|
||||||
handle_signal!(tokio::signal::windows::ctrl_close);
|
|
||||||
handle_signal!(tokio::signal::windows::ctrl_logoff);
|
|
||||||
handle_signal!(tokio::signal::windows::ctrl_shutdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Essentially a SocketAddr, except we prefer a human
|
/// Essentially a SocketAddr, except we prefer a human
|
||||||
/// readable hostname to identify the remote endpoint.
|
/// readable hostname to identify the remote endpoint.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -18,7 +18,7 @@ deno_core.workspace = true
|
||||||
deno_error.workspace = true
|
deno_error.workspace = true
|
||||||
deno_path_util.workspace = true
|
deno_path_util.workspace = true
|
||||||
deno_permissions.workspace = true
|
deno_permissions.workspace = true
|
||||||
deno_telemetry.workspace = true
|
deno_signals.workspace = true
|
||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
netif.workspace = true
|
netif.workspace = true
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
|
@ -29,7 +29,3 @@ tokio.workspace = true
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { workspace = true, features = ["commapi", "knownfolders", "mswsock", "objbase", "psapi", "shlobj", "sysinfoapi", "tlhelp32", "winbase", "winerror", "winuser", "winsock2"] }
|
winapi = { workspace = true, features = ["commapi", "knownfolders", "mswsock", "objbase", "psapi", "shlobj", "sysinfoapi", "tlhelp32", "winbase", "winerror", "winuser", "winsock2"] }
|
||||||
ntapi = "0.4.0"
|
ntapi = "0.4.0"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
|
||||||
signal-hook = "0.3.17"
|
|
||||||
signal-hook-registry = "1.4.2"
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ impl ExitCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit(code: i32) -> ! {
|
pub fn exit(code: i32) -> ! {
|
||||||
deno_telemetry::flush();
|
deno_signals::run_exit();
|
||||||
#[allow(clippy::disallowed_methods)]
|
#[allow(clippy::disallowed_methods)]
|
||||||
std::process::exit(code);
|
std::process::exit(code);
|
||||||
}
|
}
|
||||||
|
@ -81,10 +81,6 @@ deno_core::extension!(
|
||||||
if let Some(exit_code) = options.exit_code {
|
if let Some(exit_code) = options.exit_code {
|
||||||
state.put::<ExitCode>(exit_code);
|
state.put::<ExitCode>(exit_code);
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
state.put(ops::signal::SignalState::default());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,35 +1,15 @@
|
||||||
// Copyright 2018-2025 the Deno authors. MIT license.
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
#[cfg(unix)]
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
#[cfg(unix)]
|
|
||||||
use std::sync::Arc;
|
|
||||||
#[cfg(unix)]
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
|
|
||||||
use deno_core::AsyncRefCell;
|
use deno_core::AsyncRefCell;
|
||||||
use deno_core::CancelFuture;
|
|
||||||
use deno_core::CancelHandle;
|
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
use deno_core::RcRef;
|
use deno_core::RcRef;
|
||||||
use deno_core::Resource;
|
use deno_core::Resource;
|
||||||
use deno_core::ResourceId;
|
use deno_core::ResourceId;
|
||||||
use deno_core::error::ResourceError;
|
use deno_core::error::ResourceError;
|
||||||
use deno_core::op2;
|
use deno_core::op2;
|
||||||
#[cfg(unix)]
|
|
||||||
use tokio::signal::unix::Signal;
|
|
||||||
#[cfg(unix)]
|
|
||||||
use tokio::signal::unix::SignalKind;
|
|
||||||
#[cfg(unix)]
|
|
||||||
use tokio::signal::unix::signal;
|
|
||||||
#[cfg(windows)]
|
|
||||||
use tokio::signal::windows::CtrlBreak;
|
|
||||||
#[cfg(windows)]
|
|
||||||
use tokio::signal::windows::CtrlC;
|
|
||||||
#[cfg(windows)]
|
|
||||||
use tokio::signal::windows::CtrlClose;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
||||||
pub enum SignalError {
|
pub enum SignalError {
|
||||||
|
@ -47,117 +27,22 @@ pub enum SignalError {
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct SignalState {
|
|
||||||
enable_default_handlers: BTreeMap<libc::c_int, Arc<AtomicBool>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
impl SignalState {
|
|
||||||
/// Disable the default signal handler for the given signal.
|
|
||||||
///
|
|
||||||
/// Returns the shared flag to enable the default handler later, and whether a default handler already existed.
|
|
||||||
fn disable_default_handler(
|
|
||||||
&mut self,
|
|
||||||
signo: libc::c_int,
|
|
||||||
) -> (Arc<AtomicBool>, bool) {
|
|
||||||
use std::collections::btree_map::Entry;
|
|
||||||
|
|
||||||
match self.enable_default_handlers.entry(signo) {
|
|
||||||
Entry::Occupied(entry) => {
|
|
||||||
let enable = entry.get();
|
|
||||||
enable.store(false, std::sync::atomic::Ordering::Release);
|
|
||||||
(enable.clone(), true)
|
|
||||||
}
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
let enable = Arc::new(AtomicBool::new(false));
|
|
||||||
entry.insert(enable.clone());
|
|
||||||
(enable, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
/// The resource for signal stream.
|
|
||||||
/// The second element is the waker of polling future.
|
|
||||||
struct SignalStreamResource {
|
struct SignalStreamResource {
|
||||||
signal: AsyncRefCell<Signal>,
|
signo: i32,
|
||||||
enable_default_handler: Arc<AtomicBool>,
|
id: u32,
|
||||||
cancel: CancelHandle,
|
rx: AsyncRefCell<tokio::sync::watch::Receiver<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
impl Resource for SignalStreamResource {
|
impl Resource for SignalStreamResource {
|
||||||
fn name(&self) -> Cow<str> {
|
fn name(&self) -> Cow<str> {
|
||||||
"signal".into()
|
"signal".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close(self: Rc<Self>) {
|
fn close(self: Rc<Self>) {
|
||||||
self.cancel.cancel();
|
deno_signals::unregister(self.signo, self.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: CtrlClose could be mapped to SIGHUP but that needs a
|
|
||||||
// tokio::windows::signal::CtrlClose type, or something from a different crate
|
|
||||||
#[cfg(windows)]
|
|
||||||
enum WindowsSignal {
|
|
||||||
Sigint(CtrlC),
|
|
||||||
Sigbreak(CtrlBreak),
|
|
||||||
Sighup(CtrlClose),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
impl From<CtrlC> for WindowsSignal {
|
|
||||||
fn from(ctrl_c: CtrlC) -> Self {
|
|
||||||
Self::Sigint(ctrl_c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
impl From<CtrlBreak> for WindowsSignal {
|
|
||||||
fn from(ctrl_break: CtrlBreak) -> Self {
|
|
||||||
Self::Sigbreak(ctrl_break)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
impl From<CtrlClose> for WindowsSignal {
|
|
||||||
fn from(ctrl_close: CtrlClose) -> Self {
|
|
||||||
Self::Sighup(ctrl_close)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
impl WindowsSignal {
|
|
||||||
pub async fn recv(&mut self) -> Option<()> {
|
|
||||||
match self {
|
|
||||||
Self::Sigint(ctrl_c) => ctrl_c.recv().await,
|
|
||||||
Self::Sigbreak(ctrl_break) => ctrl_break.recv().await,
|
|
||||||
Self::Sighup(ctrl_close) => ctrl_close.recv().await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
struct SignalStreamResource {
|
|
||||||
signal: AsyncRefCell<WindowsSignal>,
|
|
||||||
cancel: CancelHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
impl Resource for SignalStreamResource {
|
|
||||||
fn name(&self) -> Cow<str> {
|
|
||||||
"signal".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close(self: Rc<Self>) {
|
|
||||||
self.cancel.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
#[op2(fast)]
|
#[op2(fast)]
|
||||||
#[smi]
|
#[smi]
|
||||||
pub fn op_signal_bind(
|
pub fn op_signal_bind(
|
||||||
|
@ -165,60 +50,25 @@ pub fn op_signal_bind(
|
||||||
#[string] sig: &str,
|
#[string] sig: &str,
|
||||||
) -> Result<ResourceId, SignalError> {
|
) -> Result<ResourceId, SignalError> {
|
||||||
let signo = crate::signal::signal_str_to_int(sig)?;
|
let signo = crate::signal::signal_str_to_int(sig)?;
|
||||||
if signal_hook_registry::FORBIDDEN.contains(&signo) {
|
if deno_signals::is_forbidden(signo) {
|
||||||
return Err(SignalError::SignalNotAllowed(sig.to_string()));
|
return Err(SignalError::SignalNotAllowed(sig.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?);
|
let (tx, rx) = tokio::sync::watch::channel(());
|
||||||
|
let id = deno_signals::register(
|
||||||
let (enable_default_handler, has_default_handler) = state
|
|
||||||
.borrow_mut::<SignalState>()
|
|
||||||
.disable_default_handler(signo);
|
|
||||||
|
|
||||||
let resource = SignalStreamResource {
|
|
||||||
signal,
|
|
||||||
cancel: Default::default(),
|
|
||||||
enable_default_handler: enable_default_handler.clone(),
|
|
||||||
};
|
|
||||||
let rid = state.resource_table.add(resource);
|
|
||||||
|
|
||||||
if !has_default_handler {
|
|
||||||
// restore default signal handler when the signal is unbound
|
|
||||||
// this can error if the signal is not supported, if so let's just leave it as is
|
|
||||||
let _ = signal_hook::flag::register_conditional_default(
|
|
||||||
signo,
|
signo,
|
||||||
enable_default_handler,
|
true,
|
||||||
);
|
Box::new(move || {
|
||||||
}
|
let _ = tx.send(());
|
||||||
|
|
||||||
Ok(rid)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[op2(fast)]
|
|
||||||
#[smi]
|
|
||||||
pub fn op_signal_bind(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] sig: &str,
|
|
||||||
) -> Result<ResourceId, SignalError> {
|
|
||||||
use tokio::signal::windows::ctrl_break;
|
|
||||||
use tokio::signal::windows::ctrl_c;
|
|
||||||
use tokio::signal::windows::ctrl_close;
|
|
||||||
|
|
||||||
let resource = SignalStreamResource {
|
|
||||||
signal: AsyncRefCell::new(match sig {
|
|
||||||
"SIGHUP" => ctrl_close()?.into(),
|
|
||||||
"SIGINT" => ctrl_c()?.into(),
|
|
||||||
"SIGBREAK" => ctrl_break()?.into(),
|
|
||||||
_ => {
|
|
||||||
return Err(
|
|
||||||
crate::signal::InvalidSignalStrError(sig.to_string()).into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
cancel: Default::default(),
|
);
|
||||||
};
|
|
||||||
let rid = state.resource_table.add(resource);
|
let rid = state.resource_table.add(SignalStreamResource {
|
||||||
|
signo,
|
||||||
|
id,
|
||||||
|
rx: AsyncRefCell::new(rx),
|
||||||
|
});
|
||||||
|
|
||||||
Ok(rid)
|
Ok(rid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,13 +82,9 @@ pub async fn op_signal_poll(
|
||||||
.resource_table
|
.resource_table
|
||||||
.get::<SignalStreamResource>(rid)?;
|
.get::<SignalStreamResource>(rid)?;
|
||||||
|
|
||||||
let cancel = RcRef::map(&resource, |r| &r.cancel);
|
let mut rx = RcRef::map(&resource, |r| &r.rx).borrow_mut().await;
|
||||||
let mut signal = RcRef::map(&resource, |r| &r.signal).borrow_mut().await;
|
|
||||||
|
|
||||||
match signal.recv().or_cancel(cancel).await {
|
Ok(rx.changed().await.is_err())
|
||||||
Ok(result) => Ok(result.is_none()),
|
|
||||||
Err(_) => Ok(true),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2(fast)]
|
#[op2(fast)]
|
||||||
|
@ -247,14 +93,6 @@ pub fn op_signal_unbind(
|
||||||
#[smi] rid: ResourceId,
|
#[smi] rid: ResourceId,
|
||||||
) -> Result<(), ResourceError> {
|
) -> Result<(), ResourceError> {
|
||||||
let resource = state.resource_table.take::<SignalStreamResource>(rid)?;
|
let resource = state.resource_table.take::<SignalStreamResource>(rid)?;
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
resource
|
|
||||||
.enable_default_handler
|
|
||||||
.store(true, std::sync::atomic::Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
resource.close();
|
resource.close();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
18
ext/signals/Cargo.toml
Normal file
18
ext/signals/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "deno_signals"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
readme = "README.md"
|
||||||
|
repository.workspace = true
|
||||||
|
description = "Signals for Deno"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
signal-hook.workspace = true
|
||||||
|
winapi.workspace = true
|
1
ext/signals/README.md
Normal file
1
ext/signals/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Signal handling for Deno
|
164
ext/signals/lib.rs
Normal file
164
ext/signals/lib.rs
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use std::sync::atomic::AtomicU32;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
use signal_hook::consts::*;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
static SIGHUP: i32 = 1;
|
||||||
|
|
||||||
|
static COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
|
type Handler = Box<dyn Fn() + Send>;
|
||||||
|
type Handlers = HashMap<i32, Vec<(u32, bool, Handler)>>;
|
||||||
|
static HANDLERS: OnceLock<(Handle, Mutex<Handlers>)> = OnceLock::new();
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
struct Handle(signal_hook::iterator::Handle);
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
struct Handle;
|
||||||
|
|
||||||
|
fn handle_signal(signal: i32) -> bool {
|
||||||
|
let Some((_, handlers)) = HANDLERS.get() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let handlers = handlers.lock().unwrap();
|
||||||
|
let Some(handlers) = handlers.get(&signal) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut handled = false;
|
||||||
|
|
||||||
|
for (_, prevent_default, f) in handlers {
|
||||||
|
if *prevent_default {
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
|
||||||
|
handled
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn init() -> Handle {
|
||||||
|
use signal_hook::iterator::Signals;
|
||||||
|
|
||||||
|
let mut signals = Signals::new::<[i32; 0], i32>([]).unwrap();
|
||||||
|
let handle = signals.handle();
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
for signal in signals.forever() {
|
||||||
|
let handled = handle_signal(signal);
|
||||||
|
if !handled {
|
||||||
|
signal_hook::low_level::emulate_default_handler(signal).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Handle(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn init() -> Handle {
|
||||||
|
unsafe extern "system" fn handle(ctrl_type: u32) -> i32 {
|
||||||
|
let signal = match ctrl_type {
|
||||||
|
0 => SIGINT,
|
||||||
|
1 => SIGBREAK,
|
||||||
|
2 => SIGHUP,
|
||||||
|
5 => SIGTERM,
|
||||||
|
6 => SIGTERM,
|
||||||
|
_ => return 0,
|
||||||
|
};
|
||||||
|
let handled = handle_signal(signal);
|
||||||
|
handled as _
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Registering handler
|
||||||
|
unsafe {
|
||||||
|
winapi::um::consoleapi::SetConsoleCtrlHandler(Some(handle), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(
|
||||||
|
signal: i32,
|
||||||
|
prevent_default: bool,
|
||||||
|
f: Box<dyn Fn() + Send>,
|
||||||
|
) -> u32 {
|
||||||
|
let (handle, handlers) = HANDLERS.get_or_init(|| {
|
||||||
|
let handle = init();
|
||||||
|
|
||||||
|
let handlers = Mutex::new(HashMap::new());
|
||||||
|
|
||||||
|
(handle, handlers)
|
||||||
|
});
|
||||||
|
|
||||||
|
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let mut handlers = handlers.lock().unwrap();
|
||||||
|
match handlers.entry(signal) {
|
||||||
|
std::collections::hash_map::Entry::Occupied(mut v) => {
|
||||||
|
v.get_mut().push((id, prevent_default, f))
|
||||||
|
}
|
||||||
|
std::collections::hash_map::Entry::Vacant(v) => {
|
||||||
|
v.insert(vec![(id, prevent_default, f)]);
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
handle.0.add_signal(signal).unwrap();
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let _ = handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unregister(signal: i32, id: u32) {
|
||||||
|
let Some((_, handlers)) = HANDLERS.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut handlers = handlers.lock().unwrap();
|
||||||
|
let Some(handlers) = handlers.get_mut(&signal) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(index) = handlers.iter().position(|v| v.0 == id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _ = handlers.swap_remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BEFORE_EXIT: OnceLock<Mutex<Vec<Handler>>> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn before_exit(f: fn()) {
|
||||||
|
register(SIGHUP, false, Box::new(f));
|
||||||
|
register(SIGTERM, false, Box::new(f));
|
||||||
|
register(SIGINT, false, Box::new(f));
|
||||||
|
BEFORE_EXIT
|
||||||
|
.get_or_init(|| Mutex::new(vec![]))
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.push(Box::new(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_exit() {
|
||||||
|
if let Some(fns) = BEFORE_EXIT.get() {
|
||||||
|
let fns = fns.lock().unwrap();
|
||||||
|
for f in fns.iter() {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_forbidden(signo: i32) -> bool {
|
||||||
|
FORBIDDEN.contains(&signo)
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ async-trait.workspace = true
|
||||||
deno_core.workspace = true
|
deno_core.workspace = true
|
||||||
deno_error.workspace = true
|
deno_error.workspace = true
|
||||||
deno_net.workspace = true
|
deno_net.workspace = true
|
||||||
|
deno_signals.workspace = true
|
||||||
deno_tls.workspace = true
|
deno_tls.workspace = true
|
||||||
http-body-util.workspace = true
|
http-body-util.workspace = true
|
||||||
hyper.workspace = true
|
hyper.workspace = true
|
||||||
|
|
|
@ -927,6 +927,8 @@ pub fn init(
|
||||||
})
|
})
|
||||||
.map_err(|_| deno_core::anyhow::anyhow!("failed to set otel globals"))?;
|
.map_err(|_| deno_core::anyhow::anyhow!("failed to set otel globals"))?;
|
||||||
|
|
||||||
|
deno_signals::before_exit(flush);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue