/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/signals/lib.rs
340 строк
9 KB
Bartek Iwańczuk
fix(ext/signals): unregister handler when SignalStream is dropped (#35832)
07 июл 2026, 16:06
Не верифицирован
07 июл 2026, 16:06
7064c6b
Код
Авторство
О чём код?
// Copyright 2018-2026 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::*; use tokio::sync::watch; mod dict; pub use dict::*; #[cfg(windows)] static SIGHUP: i32 = 1; #[cfg(windows)] static SIGWINCH: i32 = 28; 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([SIGHUP, SIGTERM, SIGINT]).unwrap(); let handle = signals.handle(); std::thread::spawn(move || { for signal in signals.forever() { let handled = handle_signal(signal); if !handled { if signal == SIGHUP || signal == SIGTERM || signal == SIGINT { run_exit(); } 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 { windows_sys::Win32::System::Console::SetConsoleCtrlHandler(Some(handle), 1); } Handle } #[cfg(windows)] fn start_sigwinch_polling() { static STARTED: OnceLock<()> = OnceLock::new(); STARTED.get_or_init(|| { std::thread::spawn(|| { // SAFETY: Win32 calls to open CONOUT$ and poll console size unsafe { let conout_name: Vec<u16> = "CONOUT$".encode_utf16().chain(Some(0)).collect(); let handle = windows_sys::Win32::Storage::FileSystem::CreateFileW( conout_name.as_ptr(), windows_sys::Win32::Foundation::GENERIC_READ, windows_sys::Win32::Storage::FileSystem::FILE_SHARE_READ | windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE, std::ptr::null(), windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING, 0, std::ptr::null_mut(), ); if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { return; } let mut prev_cols: i32 = 0; let mut prev_rows: i32 = 0; // Read initial size let mut bufinfo: windows_sys::Win32::System::Console::CONSOLE_SCREEN_BUFFER_INFO = std::mem::zeroed(); if windows_sys::Win32::System::Console::GetConsoleScreenBufferInfo(handle, &mut bufinfo) != 0 { prev_cols = bufinfo.srWindow.Right as i32 - bufinfo.srWindow.Left as i32 + 1; prev_rows = bufinfo.srWindow.Bottom as i32 - bufinfo.srWindow.Top as i32 + 1; } loop { windows_sys::Win32::System::Threading::Sleep(250); let mut bufinfo: windows_sys::Win32::System::Console::CONSOLE_SCREEN_BUFFER_INFO = std::mem::zeroed(); if windows_sys::Win32::System::Console::GetConsoleScreenBufferInfo( handle, &mut bufinfo, ) == 0 { continue; } let cols = bufinfo.srWindow.Right as i32 - bufinfo.srWindow.Left as i32 + 1; let rows = bufinfo.srWindow.Bottom as i32 - bufinfo.srWindow.Top as i32 + 1; if cols != prev_cols || rows != prev_rows { prev_cols = cols; prev_rows = rows; handle_signal(SIGWINCH); } } } }); }); } pub fn register( signal: i32, prevent_default: bool, f: Box<dyn Fn() + Send>, ) -> Result<u32, std::io::Error> { if is_forbidden(signal) { return Err(std::io::Error::other(format!( "Refusing to register signal {signal}" ))); } 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).map_err(|e| { std::io::Error::other(format!( "Failed to register signal {signal}: {e}" )) })?; } #[cfg(windows)] { let _ = handle; if signal == SIGWINCH { start_sigwinch_polling(); } } } } Ok(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()) { 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 const SIGINT: i32 = 2; pub const SIGTERM: i32 = 15; /// Synthetically raise a signal, triggering all registered JS handlers. /// /// This does NOT use OS-level signal delivery — it directly invokes the /// handler functions under a mutex, making it safe to call from any async /// or sync context on all platforms (including Windows). /// /// Returns true if any handler prevented the default behavior. pub fn raise(signal: i32) -> bool { handle_signal(signal) } pub fn is_forbidden(signo: i32) -> bool { if FORBIDDEN.contains(&signo) { return true; } // On Windows, signal_hook's FORBIDDEN list doesn't include SIGKILL/SIGABRT // (they're Unix-specific in the crate). Add them here since listening for // uncatchable/fatal signals doesn't make sense on any platform. #[cfg(windows)] if signo == 9 || signo == 22 { // SIGKILL (9) and SIGABRT (22, Windows CRT value) return true; } false } pub struct SignalStream { signo: i32, id: u32, rx: watch::Receiver<()>, } impl SignalStream { pub async fn recv(&mut self) -> Option<()> { self.rx.changed().await.ok() } } impl Drop for SignalStream { fn drop(&mut self) { unregister(self.signo, self.id); } } pub fn signal_stream(signo: i32) -> Result<SignalStream, std::io::Error> { signal_stream_inner(signo, true) } /// Like [`signal_stream`], but does not prevent the default signal behavior. /// /// The stream only observes the signal: if no other registered handler /// (e.g. a JS signal listener) prevents the default, the default action /// still runs, terminating the process for signals like SIGINT. This is /// important for consumers that poll the stream from the main event loop, /// which may be blocked in synchronous JS execution and unable to react /// to the notification. pub fn signal_stream_allow_default( signo: i32, ) -> Result<SignalStream, std::io::Error> { signal_stream_inner(signo, false) } fn signal_stream_inner( signo: i32, prevent_default: bool, ) -> Result<SignalStream, std::io::Error> { let (tx, rx) = watch::channel(()); let cb = Box::new(move || { tx.send_replace(()); }); let id = register(signo, prevent_default, cb)?; Ok(SignalStream { signo, id, rx }) } pub async fn ctrl_c() -> std::io::Result<()> { ctrl_c_inner(signal_stream(libc::SIGINT)?).await } /// Like [`ctrl_c`], but does not prevent the default SIGINT behavior. /// See [`signal_stream_allow_default`]. pub async fn ctrl_c_allow_default() -> std::io::Result<()> { ctrl_c_inner(signal_stream_allow_default(libc::SIGINT)?).await } async fn ctrl_c_inner(mut stream: SignalStream) -> std::io::Result<()> { match stream.recv().await { Some(_) => Ok(()), None => Err(std::io::Error::other("failed to receive SIGINT signal")), } }