mirror of
https://github.com/denoland/deno.git
synced 2025-09-26 12:19:12 +00:00
fix: use 'deno_signals' crate for signal handling (#30204)
Follow up to https://github.com/denoland/deno/pull/30029. Definition of signal numbers/names were moved from `ext/os` to `ext/signals`. All occurrences of `tokio::signal` API were replaced with helpers from `deno_signals` helpers. Additionally clippy lints were added to ensure `tokio::signal` is not used by accident. Closes https://github.com/denoland/deno/issues/30223 Co-authored-by: snek <the@snek.dev>
This commit is contained in:
parent
5a84806e9e
commit
22d1d98af3
14 changed files with 89 additions and 26 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -1576,6 +1576,7 @@ dependencies = [
|
||||||
"deno_resolver",
|
"deno_resolver",
|
||||||
"deno_runtime",
|
"deno_runtime",
|
||||||
"deno_semver",
|
"deno_semver",
|
||||||
|
"deno_signals",
|
||||||
"deno_snapshots",
|
"deno_snapshots",
|
||||||
"deno_subprocess_windows",
|
"deno_subprocess_windows",
|
||||||
"deno_task_shell",
|
"deno_task_shell",
|
||||||
|
@ -2715,6 +2716,7 @@ dependencies = [
|
||||||
"deno_os",
|
"deno_os",
|
||||||
"deno_path_util 0.5.2",
|
"deno_path_util 0.5.2",
|
||||||
"deno_permissions",
|
"deno_permissions",
|
||||||
|
"deno_signals",
|
||||||
"deno_subprocess_windows",
|
"deno_subprocess_windows",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
@ -2805,6 +2807,7 @@ dependencies = [
|
||||||
"deno_permissions",
|
"deno_permissions",
|
||||||
"deno_process",
|
"deno_process",
|
||||||
"deno_resolver",
|
"deno_resolver",
|
||||||
|
"deno_signals",
|
||||||
"deno_telemetry",
|
"deno_telemetry",
|
||||||
"deno_terminal 0.2.2",
|
"deno_terminal 0.2.2",
|
||||||
"deno_tls",
|
"deno_tls",
|
||||||
|
@ -2862,7 +2865,11 @@ dependencies = [
|
||||||
name = "deno_signals"
|
name = "deno_signals"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"deno_error",
|
||||||
|
"libc",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"tokio",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,7 @@ deno_path_util.workspace = true
|
||||||
deno_resolver = { workspace = true, features = ["deno_ast", "graph", "sync"] }
|
deno_resolver = { workspace = true, features = ["deno_ast", "graph", "sync"] }
|
||||||
deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
||||||
deno_semver.workspace = true
|
deno_semver.workspace = true
|
||||||
|
deno_signals.workspace = true
|
||||||
deno_snapshots.workspace = true
|
deno_snapshots.workspace = true
|
||||||
deno_task_shell.workspace = true
|
deno_task_shell.workspace = true
|
||||||
deno_telemetry.workspace = true
|
deno_telemetry.workspace = true
|
||||||
|
|
|
@ -2,10 +2,24 @@ disallowed-methods = [
|
||||||
{ path = "reqwest::Client::new", reason = "create an HttpClient via an HttpClientProvider instead" },
|
{ path = "reqwest::Client::new", reason = "create an HttpClient via an HttpClientProvider instead" },
|
||||||
{ path = "std::process::exit", reason = "use deno_runtime::exit instead" },
|
{ path = "std::process::exit", reason = "use deno_runtime::exit instead" },
|
||||||
{ path = "clap::Arg::env", reason = "ensure environment variables are resolved after loading the .env file instead" },
|
{ path = "clap::Arg::env", reason = "ensure environment variables are resolved after loading the .env file instead" },
|
||||||
|
{ path = "tokio::signal::ctrl_c", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::unix::signal", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::windows::ctrl_break", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::windows::ctrl_c", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::windows::ctrl_close", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::windows::ctrl_logoff", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::windows::ctrl_shutdown", reason = "use deno_signals crate instead" },
|
||||||
]
|
]
|
||||||
disallowed-types = [
|
disallowed-types = [
|
||||||
{ path = "reqwest::Client", reason = "use crate::http_util::HttpClient instead" },
|
{ path = "reqwest::Client", reason = "use crate::http_util::HttpClient instead" },
|
||||||
{ path = "sys_traits::impls::RealSys", reason = "use crate::sys::CliSys instead" },
|
{ path = "sys_traits::impls::RealSys", reason = "use crate::sys::CliSys instead" },
|
||||||
|
{ path = "tokio::signal::unix::Signal", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::unix::SignalKind", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::windows::CtrlBreak", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::windows::CtrlC", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::windows::CtrlClose", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::windows::CtrlLogoff", reason = "use deno_signals crate instead" },
|
||||||
|
{ path = "tokio::signal::windows::CtrlShutdown", reason = "use deno_signals crate instead" },
|
||||||
]
|
]
|
||||||
ignore-interior-mutability = [
|
ignore-interior-mutability = [
|
||||||
"lsp_types::Uri",
|
"lsp_types::Uri",
|
||||||
|
|
|
@ -36,6 +36,7 @@ pub fn get_script_with_args(script: &str, argv: &[String]) -> String {
|
||||||
.map(|a| format!("\"{}\"", a.replace('"', "\\\"").replace('$', "\\$")))
|
.map(|a| format!("\"{}\"", a.replace('"', "\\\"").replace('$', "\\$")))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
let script = format!("{script} {additional_args}");
|
let script = format!("{script} {additional_args}");
|
||||||
script.trim().to_owned()
|
script.trim().to_owned()
|
||||||
}
|
}
|
||||||
|
@ -632,7 +633,7 @@ pub async fn run_future_forwarding_signals<TOutput>(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn listen_ctrl_c(kill_signal: KillSignal) {
|
async fn listen_ctrl_c(kill_signal: KillSignal) {
|
||||||
while let Ok(()) = tokio::signal::ctrl_c().await {
|
while let Ok(()) = deno_signals::ctrl_c().await {
|
||||||
// On windows, ctrl+c is sent to the process group, so the signal would
|
// On windows, ctrl+c is sent to the process group, so the signal would
|
||||||
// have already been sent to the child process. We still want to listen
|
// have already been sent to the child process. We still want to listen
|
||||||
// for ctrl+c here to keep the process alive when receiving it, but no
|
// for ctrl+c here to keep the process alive when receiving it, but no
|
||||||
|
@ -646,7 +647,7 @@ async fn listen_ctrl_c(kill_signal: KillSignal) {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
async fn listen_and_forward_all_signals(kill_signal: KillSignal) {
|
async fn listen_and_forward_all_signals(kill_signal: KillSignal) {
|
||||||
use deno_core::futures::FutureExt;
|
use deno_core::futures::FutureExt;
|
||||||
use deno_runtime::deno_os::signal::SIGNAL_NUMS;
|
use deno_signals::SIGNAL_NUMS;
|
||||||
|
|
||||||
// listen and forward every signal we support
|
// listen and forward every signal we support
|
||||||
let mut futures = Vec::with_capacity(SIGNAL_NUMS.len());
|
let mut futures = Vec::with_capacity(SIGNAL_NUMS.len());
|
||||||
|
@ -658,9 +659,7 @@ async fn listen_and_forward_all_signals(kill_signal: KillSignal) {
|
||||||
let kill_signal = kill_signal.clone();
|
let kill_signal = kill_signal.clone();
|
||||||
futures.push(
|
futures.push(
|
||||||
async move {
|
async move {
|
||||||
let Ok(mut stream) = tokio::signal::unix::signal(
|
let Ok(mut stream) = deno_signals::signal_stream(signo) else {
|
||||||
tokio::signal::unix::SignalKind::from_raw(signo),
|
|
||||||
) else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let signal_kind: deno_task_shell::SignalKind = signo.into();
|
let signal_kind: deno_task_shell::SignalKind = signo.into();
|
||||||
|
|
|
@ -66,7 +66,6 @@ use rand::rngs::SmallRng;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::signal;
|
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use crate::args::CliOptions;
|
use crate::args::CliOptions;
|
||||||
|
@ -1282,7 +1281,7 @@ async fn test_specifiers(
|
||||||
|
|
||||||
let mut cancel_sender = test_event_sender_factory.weak_sender();
|
let mut cancel_sender = test_event_sender_factory.weak_sender();
|
||||||
let sigint_handler_handle = spawn(async move {
|
let sigint_handler_handle = spawn(async move {
|
||||||
signal::ctrl_c().await.unwrap();
|
deno_signals::ctrl_c().await.unwrap();
|
||||||
cancel_sender.send(TestEvent::Sigint).ok();
|
cancel_sender.send(TestEvent::Sigint).ok();
|
||||||
});
|
});
|
||||||
HAS_TEST_RUN_SIGINT_HANDLER.store(true, Ordering::Relaxed);
|
HAS_TEST_RUN_SIGINT_HANDLER.store(true, Ordering::Relaxed);
|
||||||
|
@ -1734,7 +1733,7 @@ pub async fn run_tests_with_watch(
|
||||||
// once a user adds one.
|
// once a user adds one.
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
signal::ctrl_c().await.unwrap();
|
deno_signals::ctrl_c().await.unwrap();
|
||||||
if !HAS_TEST_RUN_SIGINT_HANDLER.load(Ordering::Relaxed) {
|
if !HAS_TEST_RUN_SIGINT_HANDLER.load(Ordering::Relaxed) {
|
||||||
#[allow(clippy::disallowed_methods)]
|
#[allow(clippy::disallowed_methods)]
|
||||||
std::process::exit(130);
|
std::process::exit(130);
|
||||||
|
|
|
@ -18,7 +18,6 @@ use once_cell::sync::Lazy;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
mod ops;
|
mod ops;
|
||||||
pub mod signal;
|
|
||||||
pub mod sys_info;
|
pub mod sys_info;
|
||||||
|
|
||||||
pub use ops::signal::SignalError;
|
pub use ops::signal::SignalError;
|
||||||
|
|
|
@ -15,10 +15,10 @@ use deno_core::op2;
|
||||||
pub enum SignalError {
|
pub enum SignalError {
|
||||||
#[class(type)]
|
#[class(type)]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidSignalStr(#[from] crate::signal::InvalidSignalStrError),
|
InvalidSignalStr(#[from] deno_signals::InvalidSignalStrError),
|
||||||
#[class(type)]
|
#[class(type)]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidSignalInt(#[from] crate::signal::InvalidSignalIntError),
|
InvalidSignalInt(#[from] deno_signals::InvalidSignalIntError),
|
||||||
#[class(type)]
|
#[class(type)]
|
||||||
#[error("Binding to signal '{0}' is not allowed")]
|
#[error("Binding to signal '{0}' is not allowed")]
|
||||||
SignalNotAllowed(String),
|
SignalNotAllowed(String),
|
||||||
|
@ -49,7 +49,7 @@ pub fn op_signal_bind(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
#[string] sig: &str,
|
#[string] sig: &str,
|
||||||
) -> Result<ResourceId, SignalError> {
|
) -> Result<ResourceId, SignalError> {
|
||||||
let signo = crate::signal::signal_str_to_int(sig)?;
|
let signo = deno_signals::signal_str_to_int(sig)?;
|
||||||
if deno_signals::is_forbidden(signo) {
|
if deno_signals::is_forbidden(signo) {
|
||||||
return Err(SignalError::SignalNotAllowed(sig.to_string()));
|
return Err(SignalError::SignalNotAllowed(sig.to_string()));
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ pub fn op_signal_bind(
|
||||||
Box::new(move || {
|
Box::new(move || {
|
||||||
let _ = tx.send(());
|
let _ = tx.send(());
|
||||||
}),
|
}),
|
||||||
);
|
)?;
|
||||||
|
|
||||||
let rid = state.resource_table.add(SignalStreamResource {
|
let rid = state.resource_table.add(SignalStreamResource {
|
||||||
signo,
|
signo,
|
||||||
|
|
|
@ -21,6 +21,7 @@ deno_io.workspace = true
|
||||||
deno_os.workspace = true
|
deno_os.workspace = true
|
||||||
deno_path_util.workspace = true
|
deno_path_util.workspace = true
|
||||||
deno_permissions.workspace = true
|
deno_permissions.workspace = true
|
||||||
|
deno_signals.workspace = true
|
||||||
deno_subprocess_windows.workspace = true
|
deno_subprocess_windows.workspace = true
|
||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
|
@ -313,7 +313,7 @@ impl TryFrom<ExitStatus> for ChildStatus {
|
||||||
success: false,
|
success: false,
|
||||||
code: 128 + signal,
|
code: 128 + signal,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
signal: Some(deno_os::signal::signal_int_to_str(signal)?.to_string()),
|
signal: Some(deno_signals::signal_int_to_str(signal)?.to_string()),
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
signal: None,
|
signal: None,
|
||||||
}
|
}
|
||||||
|
@ -1263,7 +1263,7 @@ mod deprecated {
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> {
|
pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> {
|
||||||
let signo = deno_os::signal::signal_str_to_int(signal)
|
let signo = deno_signals::signal_str_to_int(signal)
|
||||||
.map_err(SignalError::InvalidSignalStr)?;
|
.map_err(SignalError::InvalidSignalStr)?;
|
||||||
use nix::sys::signal::Signal;
|
use nix::sys::signal::Signal;
|
||||||
use nix::sys::signal::kill as unix_kill;
|
use nix::sys::signal::kill as unix_kill;
|
||||||
|
@ -1291,7 +1291,7 @@ mod deprecated {
|
||||||
|
|
||||||
if !matches!(signal, "SIGKILL" | "SIGTERM") {
|
if !matches!(signal, "SIGKILL" | "SIGTERM") {
|
||||||
Err(
|
Err(
|
||||||
SignalError::InvalidSignalStr(deno_os::signal::InvalidSignalStrError(
|
SignalError::InvalidSignalStr(deno_signals::InvalidSignalStrError(
|
||||||
signal.to_string(),
|
signal.to_string(),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
|
@ -14,5 +14,9 @@ description = "Signals for Deno"
|
||||||
path = "lib.rs"
|
path = "lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
deno_error.workspace = true
|
||||||
|
libc.workspace = true
|
||||||
signal-hook.workspace = true
|
signal-hook.workspace = true
|
||||||
winapi.workspace = true
|
thiserror.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
winapi = { workspace = true, features = ["consoleapi"] }
|
||||||
|
|
|
@ -7,6 +7,10 @@ use std::sync::atomic::AtomicU32;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use signal_hook::consts::*;
|
use signal_hook::consts::*;
|
||||||
|
use tokio::sync::watch;
|
||||||
|
|
||||||
|
mod dict;
|
||||||
|
pub use dict::*;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
static SIGHUP: i32 = 1;
|
static SIGHUP: i32 = 1;
|
||||||
|
@ -91,7 +95,14 @@ pub fn register(
|
||||||
signal: i32,
|
signal: i32,
|
||||||
prevent_default: bool,
|
prevent_default: bool,
|
||||||
f: Box<dyn Fn() + Send>,
|
f: Box<dyn Fn() + Send>,
|
||||||
) -> u32 {
|
) -> Result<u32, std::io::Error> {
|
||||||
|
if is_forbidden(signal) {
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
format!("Refusing to register signal {signal}"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let (handle, handlers) = HANDLERS.get_or_init(|| {
|
let (handle, handlers) = HANDLERS.get_or_init(|| {
|
||||||
let handle = init();
|
let handle = init();
|
||||||
|
|
||||||
|
@ -120,7 +131,7 @@ pub fn register(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
id
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unregister(signal: i32, id: u32) {
|
pub fn unregister(signal: i32, id: u32) {
|
||||||
|
@ -140,9 +151,9 @@ pub fn unregister(signal: i32, id: u32) {
|
||||||
static BEFORE_EXIT: OnceLock<Mutex<Vec<Handler>>> = OnceLock::new();
|
static BEFORE_EXIT: OnceLock<Mutex<Vec<Handler>>> = OnceLock::new();
|
||||||
|
|
||||||
pub fn before_exit(f: fn()) {
|
pub fn before_exit(f: fn()) {
|
||||||
register(SIGHUP, false, Box::new(f));
|
register(SIGHUP, false, Box::new(f)).unwrap();
|
||||||
register(SIGTERM, false, Box::new(f));
|
register(SIGTERM, false, Box::new(f)).unwrap();
|
||||||
register(SIGINT, false, Box::new(f));
|
register(SIGINT, false, Box::new(f)).unwrap();
|
||||||
BEFORE_EXIT
|
BEFORE_EXIT
|
||||||
.get_or_init(|| Mutex::new(vec![]))
|
.get_or_init(|| Mutex::new(vec![]))
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -162,3 +173,33 @@ pub fn run_exit() {
|
||||||
pub fn is_forbidden(signo: i32) -> bool {
|
pub fn is_forbidden(signo: i32) -> bool {
|
||||||
FORBIDDEN.contains(&signo)
|
FORBIDDEN.contains(&signo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SignalStream {
|
||||||
|
rx: watch::Receiver<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignalStream {
|
||||||
|
pub async fn recv(&mut self) -> Option<()> {
|
||||||
|
self.rx.changed().await.ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signal_stream(signo: i32) -> Result<SignalStream, std::io::Error> {
|
||||||
|
let (tx, rx) = watch::channel(());
|
||||||
|
let cb = Box::new(move || {
|
||||||
|
tx.send_replace(());
|
||||||
|
});
|
||||||
|
register(signo, true, cb)?;
|
||||||
|
Ok(SignalStream { rx })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ctrl_c() -> std::io::Result<()> {
|
||||||
|
let mut stream = signal_stream(libc::SIGINT)?;
|
||||||
|
match stream.recv().await {
|
||||||
|
Some(_) => Ok(()),
|
||||||
|
None => Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
"failed to receive SIGINT signal",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ deno_path_util.workspace = true
|
||||||
deno_permissions.workspace = true
|
deno_permissions.workspace = true
|
||||||
deno_process.workspace = true
|
deno_process.workspace = true
|
||||||
deno_resolver = { workspace = true, features = ["sync"] }
|
deno_resolver = { workspace = true, features = ["sync"] }
|
||||||
|
deno_signals.workspace = true
|
||||||
deno_telemetry.workspace = true
|
deno_telemetry.workspace = true
|
||||||
deno_terminal.workspace = true
|
deno_terminal.workspace = true
|
||||||
deno_tls.workspace = true
|
deno_tls.workspace = true
|
||||||
|
|
|
@ -71,10 +71,7 @@ pub(crate) static SIGUSR2_RX: LazyLock<tokio::sync::watch::Receiver<()>> =
|
||||||
let (tx, rx) = tokio::sync::watch::channel(());
|
let (tx, rx) = tokio::sync::watch::channel(());
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut sigusr2 = tokio::signal::unix::signal(
|
let mut sigusr2 = deno_signals::signal_stream(libc::SIGUSR2).unwrap();
|
||||||
tokio::signal::unix::SignalKind::user_defined2(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
sigusr2.recv().await;
|
sigusr2.recv().await;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue