Skip to main content

ntex_rt/
signals.rs

1use std::sync::{Arc, OnceLock, atomic::AtomicBool, atomic::Ordering, mpsc};
2use std::{cell::RefCell, future::poll_fn, panic, task::Poll};
3
4use atomic_waker::AtomicWaker;
5use ntex_error::Backtrace;
6use parking_lot::{Mutex, MutexGuard};
7
8use crate::System;
9
10thread_local! {
11    static STOP: RefCell<Option<oneshot::Sender<()>>> = const { RefCell::new(None) };
12    static HANDLERS: RefCell<Vec<oneshot::Sender<Arc<[Signal]>>>> = RefCell::default();
13}
14
15static CUR_SYS: Mutex<Option<System>> = Mutex::new(None);
16// Mirrors `CUR_SYS`, the panic hook must not lock it.
17static ENABLED: AtomicBool = AtomicBool::new(false);
18static SIGS: Mutex<Vec<Signal>> = Mutex::new(Vec::new());
19static HND_WAKER: AtomicWaker = AtomicWaker::new();
20// The panic hook runs on the panicking thread, some runtimes (compio) abort
21// the process if a task is woken there. Panics are woken from a dedicated thread.
22static PANIC_WAKER: OnceLock<mpsc::SyncSender<()>> = OnceLock::new();
23
24/// Process and application signals delivered to the runtime.
25#[derive(Clone, Debug)]
26pub enum Signal {
27    /// SIGHUP
28    Hup,
29    /// SIGINT
30    Int,
31    /// SIGTERM
32    Term,
33    /// SIGQUIT
34    Quit,
35    /// Application panic
36    Panic(PanicSource),
37}
38
39/// Source of a panic signal.
40#[derive(Clone, Debug)]
41pub enum PanicSource {
42    /// A `SIGSEGV` or `SIGABRT` signal was received.
43    ///
44    /// Delivery is best effort; a fatal fault can terminate the process before
45    /// the runtime or server handles this notification.
46    Sig(&'static str),
47    /// An application panic and its captured backtrace.
48    App(Arc<str>, Backtrace),
49}
50
51/// Registers interest in the next batch of signals.
52///
53/// The returned one-shot receiver handles one notification. Call this function
54/// again after each notification to continue receiving signals.
55pub fn signal() -> oneshot::AsyncReceiver<Arc<[Signal]>> {
56    let (tx, rx) = oneshot::async_channel();
57    System::current().handle().spawn_detached(async move {
58        HANDLERS.with(|handlers| {
59            handlers.borrow_mut().push(tx);
60        });
61    });
62
63    rx
64}
65
66/// Returns whether signal handling is enabled.
67pub fn is_enabled() -> bool {
68    CUR_SYS.lock().is_some()
69}
70
71type Registration = MutexGuard<'static, Option<System>>;
72
73/// Registers the system as the signal handler.
74///
75/// Returns the lock guard, so handlers are installed before other systems
76/// can register or unregister.
77fn register_system(sys: &System) -> Option<Registration> {
78    let mut cur = CUR_SYS.lock();
79    if cur.is_some() {
80        None
81    } else {
82        *cur = Some(sys.clone());
83        ENABLED.store(true, Ordering::Release);
84
85        let (tx, rx) = oneshot::async_channel();
86        sys.handle().spawn_detached(signals(rx));
87        STOP.with(|stop| {
88            *stop.borrow_mut() = Some(tx);
89        });
90        Some(cur)
91    }
92}
93
94/// Unregisters the system if it handles signals.
95///
96/// Returns the lock guard, so handlers are removed before other systems
97/// can register.
98fn unregister_system(sys: &System) -> Option<Registration> {
99    let mut cur = CUR_SYS.lock();
100    if cur.as_ref().is_some_and(|cur| cur.id() == sys.id()) {
101        cur.take();
102        ENABLED.store(false, Ordering::Release);
103        sys.handle().spawn_detached(async move {
104            STOP.with(|stop| {
105                if let Some(tx) = stop.borrow_mut().take() {
106                    let _ = tx.send(());
107                }
108            });
109        });
110        Some(cur)
111    } else {
112        None
113    }
114}
115
116/// Queue signal and wake the system.
117///
118/// Must not be called from a signal handler.
119fn handle_signal(sig: Signal) {
120    SIGS.lock().push(sig);
121    HND_WAKER.wake();
122}
123
124#[cfg(target_family = "unix")]
125/// Signal delivery thread handle and `SIGUSR2` handler id.
126static SIG_HANDLERS: Mutex<(
127    Option<signal_hook::iterator::Handle>,
128    Option<signal_hook::SigId>,
129)> = Mutex::new((None, None));
130
131#[cfg(target_family = "unix")]
132/// Register signal handler.
133///
134/// Returns `false` if signals are handled by another system.
135pub(crate) fn start(sys: &System) -> bool {
136    static ONCE: std::sync::Once = std::sync::Once::new();
137
138    if let Some(_registration) = register_system(sys) {
139        use nix::sys::signal;
140        use signal_hook::consts::signal::{SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR2};
141        use signal_hook::{iterator::Signals, low_level::register};
142
143        ONCE.call_once(|| {
144            if let Err(e) = start_fatal_signal_thread() {
145                log::error!("Cannot start fatal signal handling thread: {e:?}");
146            }
147
148            // Use u128 for alignment.
149            let buf = Vec::leak(vec![0u128; 4096]);
150            let stack = libc::stack_t {
151                ss_sp: buf.as_ptr() as *mut libc::c_void,
152                ss_flags: 0,
153                ss_size: std::mem::size_of_val(buf),
154            };
155            let mut old = libc::stack_t {
156                ss_sp: std::ptr::null_mut(),
157                ss_flags: 0,
158                ss_size: 0,
159            };
160            let result = unsafe { libc::sigaltstack(&raw const stack, &raw mut old) };
161            if result != 0 {
162                log::error!("Cannot set signal stack");
163            }
164
165            let sig_action = signal::SigAction::new(
166                signal::SigHandler::Handler(sig_segv),
167                signal::SaFlags::SA_ONSTACK,
168                signal::SigSet::empty(),
169            );
170            unsafe {
171                match signal::sigaction(signal::SIGSEGV, &sig_action) {
172                    Ok(prev) => _ = PREV_SIGSEGV.set(prev),
173                    Err(_) => log::error!("Cannot install signal handler for SIGSEGV"),
174                }
175                match signal::sigaction(signal::SIGABRT, &sig_action) {
176                    Ok(prev) => _ = PREV_SIGABRT.set(prev),
177                    Err(_) => log::error!("Cannot install signal handler for SIGABRT"),
178                }
179            }
180        });
181
182        // signal handlers only write to a self-pipe, signals are
183        // dispatched from a regular thread
184        let handle = match Signals::new([SIGHUP, SIGINT, SIGTERM, SIGQUIT]) {
185            Ok(mut signals) => {
186                let handle = signals.handle();
187                let result = std::thread::Builder::new()
188                    .name("ntex-rt:signals".to_string())
189                    .spawn(move || {
190                        for sig in signals.forever() {
191                            handle_signal(match sig {
192                                SIGHUP => Signal::Hup,
193                                SIGINT => Signal::Int,
194                                SIGTERM => Signal::Term,
195                                SIGQUIT => Signal::Quit,
196                                _ => continue,
197                            });
198                        }
199                    });
200                match result {
201                    Ok(_) => Some(handle),
202                    Err(e) => {
203                        log::error!("Cannot start signal handling thread: {e:?}");
204                        None
205                    }
206                }
207            }
208            Err(e) => {
209                log::error!("Cannot install signal handlers: {e:?}");
210                None
211            }
212        };
213
214        let usr2 = unsafe { register(SIGUSR2, || crate::watchdog::sig_usr2()) };
215        if usr2.is_err() {
216            log::error!("Cannot install signal handler for SIGUSR2");
217        }
218        *SIG_HANDLERS.lock() = (handle, usr2.ok());
219        true
220    } else {
221        false
222    }
223}
224
225#[cfg(target_family = "unix")]
226/// Unregister signal handler.
227pub(crate) fn stop(sys: &System) {
228    if let Some(_registration) = unregister_system(sys) {
229        let (handle, usr2) = std::mem::take(&mut *SIG_HANDLERS.lock());
230        if let Some(handle) = handle {
231            // the thread exits and unregisters handlers
232            handle.close();
233        }
234        if let Some(usr2) = usr2 {
235            signal_hook::low_level::unregister(usr2);
236        }
237    }
238}
239
240#[cfg(target_family = "windows")]
241static CTRLC_ENABLED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
242
243#[cfg(target_family = "windows")]
244/// Register signal handler.
245///
246/// Signals are handled by oneshots, you have to re-register
247/// after each signal. Returns `false` if signals are handled by another system.
248pub(crate) fn start(sys: &System) -> bool {
249    use std::sync::atomic::Ordering;
250    static ONCE: std::sync::Once = std::sync::Once::new();
251
252    if let Some(_registration) = register_system(sys) {
253        // the handler cannot be removed, it ignores signals while disabled
254        ONCE.call_once(|| {
255            let result = ctrlc::set_handler(|| {
256                if CTRLC_ENABLED.load(Ordering::Acquire) {
257                    handle_signal(Signal::Int);
258                }
259            });
260            if let Err(e) = result {
261                log::error!("Cannot install Ctrl-C handler: {e:?}");
262            }
263        });
264        CTRLC_ENABLED.store(true, Ordering::Release);
265        true
266    } else {
267        false
268    }
269}
270
271#[cfg(target_family = "windows")]
272/// Unregister signal handler.
273pub(crate) fn stop(sys: &System) {
274    if let Some(_registration) = unregister_system(sys) {
275        CTRLC_ENABLED.store(false, std::sync::atomic::Ordering::Release);
276        log::info!("Signals handling is disabled");
277    }
278}
279
280async fn signals(rx: oneshot::AsyncReceiver<()>) {
281    let mut rx = std::pin::pin!(rx);
282
283    poll_fn(|cx| {
284        if rx.as_mut().poll(cx).is_ready() {
285            Poll::Ready(())
286        } else {
287            HND_WAKER.register(cx.waker());
288
289            let sigs = std::mem::take(&mut *SIGS.lock());
290            if !sigs.is_empty() {
291                let sigs: Arc<[Signal]> = Arc::from(sigs);
292
293                HANDLERS.with(|handlers| {
294                    for tx in handlers.borrow_mut().drain(..) {
295                        let _ = tx.send(sigs.clone());
296                    }
297                });
298            }
299
300            Poll::Pending
301        }
302    })
303    .await;
304}
305
306#[cfg(target_family = "unix")]
307static PREV_SIGSEGV: std::sync::OnceLock<nix::sys::signal::SigAction> = std::sync::OnceLock::new();
308#[cfg(target_family = "unix")]
309static PREV_SIGABRT: std::sync::OnceLock<nix::sys::signal::SigAction> = std::sync::OnceLock::new();
310
311// Both ends stay open for the process lifetime: a handler must never write to a
312// recycled descriptor or raise SIGPIPE if the delivery thread exits.
313#[cfg(target_family = "unix")]
314static FATAL_PIPE: OnceLock<(
315    libc::pid_t,
316    std::os::unix::net::UnixStream,
317    std::os::unix::net::UnixStream,
318)> = OnceLock::new();
319
320#[cfg(target_family = "unix")]
321fn start_fatal_signal_thread() -> std::io::Result<()> {
322    use std::{io::Read, os::unix::net::UnixStream};
323
324    let (read, write) = UnixStream::pair()?;
325    write.set_nonblocking(true)?;
326    let (_, read, _) = FATAL_PIPE.get_or_init(|| (unsafe { libc::getpid() }, read, write));
327    std::thread::Builder::new()
328        .name("ntex-rt:crashes".to_string())
329        .spawn(move || {
330            let mut read = read;
331            let mut byte = [0];
332            while read.read_exact(&mut byte).is_ok() {
333                let name = match byte[0] {
334                    b'A' => "SIGABRT",
335                    b'S' => "SIGSEGV",
336                    _ => continue,
337                };
338                if ENABLED.load(Ordering::Acquire) {
339                    handle_signal(Signal::Panic(PanicSource::Sig(name)));
340                }
341            }
342        })?;
343    Ok(())
344}
345
346#[cfg(target_family = "unix")]
347extern "C" fn sig_segv(v: i32) {
348    use std::os::fd::AsRawFd;
349
350    use nix::errno::Errno;
351    use nix::sys::signal::{self, SaFlags, SigAction, SigHandler, SigSet};
352
353    let errno = Errno::last_raw();
354    let (sig, prev, byte) = if v == libc::SIGABRT {
355        (signal::SIGABRT, &PREV_SIGABRT, b"A")
356    } else {
357        (signal::SIGSEGV, &PREV_SIGSEGV, b"S")
358    };
359    if let Some((pid, _, write)) = FATAL_PIPE.get() {
360        unsafe {
361            // A forked child must not shut down its parent's server.
362            if libc::getpid() == *pid {
363                let _ = libc::write(write.as_raw_fd(), byte.as_ptr().cast(), 1);
364            }
365        }
366    }
367
368    // restore the previous handler, it handles the signal raised again by
369    // the faulting instruction or by `abort()`, otherwise the process never exits
370    let prev = prev
371        .get()
372        .copied()
373        .unwrap_or_else(|| SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()));
374    unsafe {
375        let _ = signal::sigaction(sig, &prev);
376    }
377    Errno::set_raw(errno);
378}
379
380/// Installs the panic hook, once per process.
381///
382/// The previous hook is called first. Panics are delivered as
383/// `Signal::Panic` only while signal handling is enabled.
384pub(crate) fn enable_panic_handling() {
385    static ONCE: std::sync::Once = std::sync::Once::new();
386
387    ONCE.call_once(|| {
388        let (tx, rx) = mpsc::sync_channel::<()>(1);
389        let result = std::thread::Builder::new()
390            .name("ntex-rt:panics".to_string())
391            .spawn(move || {
392                for () in rx {
393                    HND_WAKER.wake();
394                }
395            });
396        match result {
397            Ok(_) => _ = PANIC_WAKER.set(tx),
398            Err(e) => log::error!("Cannot start panic handling thread: {e:?}"),
399        }
400
401        let prev = panic::take_hook();
402        panic::set_hook(Box::new(move |panic_info| {
403            prev(panic_info);
404
405            if !ENABLED.load(Ordering::Acquire) {
406                return;
407            }
408
409            let info: Arc<str> = if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
410                Arc::from(s.to_string())
411            } else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
412                Arc::from(s.clone())
413            } else {
414                Arc::from("panic")
415            };
416            let bt = if let Some(loc) = panic_info.location() {
417                let s = Box::new(loc.file().to_string());
418                let filename = Box::leak(s);
419                Backtrace::with_filename(filename)
420            } else {
421                Backtrace::new(panic::Location::caller())
422            };
423
424            SIGS.lock().push(Signal::Panic(PanicSource::App(info, bt)));
425            if let Some(tx) = PANIC_WAKER.get() {
426                // a pending wake covers this panic too
427                let _ = tx.try_send(());
428            }
429        }));
430    });
431}
432
433#[cfg(all(test, any(target_family = "windows", target_family = "unix")))]
434mod tests {
435    use crate::testing::TestRunner;
436
437    use super::*;
438
439    // signal handling is global
440    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
441
442    async fn recv(rx: oneshot::AsyncReceiver<Arc<[Signal]>>) -> Option<Arc<[Signal]>> {
443        let mut rx = std::pin::pin!(rx);
444        let mut timeout =
445            std::pin::pin!(futures_timer::Delay::new(std::time::Duration::from_secs(5)));
446        poll_fn(|cx| {
447            if let Poll::Ready(res) = rx.as_mut().poll(cx) {
448                Poll::Ready(res.ok())
449            } else if timeout.as_mut().poll(cx).is_ready() {
450                Poll::Ready(None)
451            } else {
452                Poll::Pending
453            }
454        })
455        .await
456    }
457
458    /// Raises a signal with `raise` until a batch with a signal matching
459    /// `expected` is delivered, returns the number of attempts.
460    ///
461    /// The panic hook is process-wide, panics of tests running in parallel are
462    /// delivered too. A batch without the expected signal consumes the
463    /// handler, and the expected signal may be dropped before the handler is
464    /// registered again, so it is raised again.
465    async fn deliver(raise: impl Fn(), expected: impl Fn(&Signal) -> bool) -> usize {
466        for attempt in 1..=10 {
467            let rx = signal();
468            // let the registration task run
469            futures_timer::Delay::new(std::time::Duration::from_millis(50)).await;
470            raise();
471            if recv(rx)
472                .await
473                .is_some_and(|sigs| sigs.iter().any(&expected))
474            {
475                return attempt;
476            }
477        }
478        panic!("signal is not delivered");
479    }
480
481    fn is_app_panic(sig: &Signal, expected: &str) -> bool {
482        matches!(sig, Signal::Panic(PanicSource::App(msg, _)) if &**msg == expected)
483    }
484
485    #[test]
486    fn panic_hook_chains_and_follows_signals() {
487        use std::sync::atomic::AtomicUsize;
488
489        static CALLS: AtomicUsize = AtomicUsize::new(0);
490
491        let _lock = LOCK
492            .lock()
493            .unwrap_or_else(std::sync::PoisonError::into_inner);
494
495        let prev = panic::take_hook();
496        panic::set_hook(Box::new(move |info| {
497            // panics of other tests reach the hook too
498            let msg = info.payload().downcast_ref::<&str>();
499            if matches!(msg, Some(&("no signals" | "boom"))) {
500                CALLS.fetch_add(1, Ordering::Relaxed);
501            }
502            prev(info);
503        }));
504        enable_panic_handling();
505        // installed once
506        enable_panic_handling();
507
508        // previous hook is called, nothing is queued without signal handling
509        SIGS.lock().clear();
510        let _ = panic::catch_unwind(|| panic!("no signals"));
511        assert_eq!(CALLS.load(Ordering::Relaxed), 1);
512        assert!(
513            !SIGS
514                .lock()
515                .iter()
516                .any(|sig| is_app_panic(sig, "no signals"))
517        );
518
519        let attempts = System::new("test", TestRunner).block_on(async {
520            let sys = System::current();
521            sys.enable_signals();
522
523            deliver(
524                || {
525                    let _ = panic::catch_unwind(|| panic!("boom"));
526                },
527                |sig| is_app_panic(sig, "boom"),
528            )
529            .await
530        });
531        // the hook is installed once and chains the previous one
532        assert_eq!(CALLS.load(Ordering::Relaxed), 1 + attempts);
533    }
534
535    #[test]
536    fn panic_on_other_thread_delivered() {
537        let _lock = LOCK
538            .lock()
539            .unwrap_or_else(std::sync::PoisonError::into_inner);
540        enable_panic_handling();
541
542        System::new("test", TestRunner).block_on(async {
543            System::current().enable_signals();
544
545            // compio aborts the process if the task is woken on a panicking thread
546            deliver(
547                || {
548                    let res = std::thread::spawn(|| panic!("thread boom")).join();
549                    assert!(res.is_err());
550                },
551                |sig| is_app_panic(sig, "thread boom"),
552            )
553            .await;
554        });
555    }
556
557    #[test]
558    fn signals_released_when_future_panics() {
559        let _lock = LOCK
560            .lock()
561            .unwrap_or_else(std::sync::PoisonError::into_inner);
562
563        let res = panic::catch_unwind(|| {
564            System::new("block_on", TestRunner).block_on(async {
565                System::current().enable_signals();
566                assert!(is_enabled());
567                panic!("block_on fails");
568            });
569        });
570        assert!(res.is_err());
571        assert!(!is_enabled());
572
573        let res = panic::catch_unwind(|| {
574            System::build().signals(true).build(TestRunner).run(|| {
575                assert!(is_enabled());
576                panic!("run fails");
577            })
578        });
579        assert!(res.is_err());
580        assert!(!is_enabled());
581    }
582
583    #[test]
584    fn signals_released_when_system_stops() {
585        let _lock = LOCK
586            .lock()
587            .unwrap_or_else(std::sync::PoisonError::into_inner);
588
589        let first = System::new("first", TestRunner).block_on(async {
590            let sys = System::current();
591            sys.enable_signals();
592            assert!(sys.signals());
593            sys
594        });
595        assert!(!is_enabled());
596        assert!(!first.signals());
597
598        System::new("second", TestRunner).block_on(async {
599            let sys = System::current();
600            sys.enable_signals();
601            assert!(sys.signals());
602            assert!(is_enabled());
603
604            // signals are handled by one system at a time
605            let other = std::thread::spawn(|| {
606                System::new("other", TestRunner).block_on(async {
607                    let sys = System::current();
608                    sys.enable_signals();
609                    sys.signals()
610                })
611            })
612            .join()
613            .unwrap();
614            assert!(!other);
615            assert!(is_enabled());
616        });
617        assert!(!is_enabled());
618    }
619
620    #[test]
621    fn builder_signals() {
622        let _lock = LOCK
623            .lock()
624            .unwrap_or_else(std::sync::PoisonError::into_inner);
625
626        // stopped by another thread
627        let stopper = std::thread::spawn(|| {
628            loop {
629                let sys = CUR_SYS.lock().clone().filter(|sys| sys.name() == "signals");
630                if let Some(sys) = sys {
631                    sys.stop();
632                    break;
633                }
634                std::thread::sleep(std::time::Duration::from_millis(1));
635            }
636        });
637        System::build()
638            .name("signals")
639            .enable_signals()
640            .build(TestRunner)
641            .run_until_stop()
642            .unwrap();
643        stopper.join().unwrap();
644        assert!(!is_enabled());
645
646        // signals are released if `f` fails
647        let err = System::build()
648            .signals(true)
649            .build(TestRunner)
650            .run(|| {
651                assert!(System::current().signals());
652                assert!(is_enabled());
653                Err(std::io::Error::other("failed"))
654            })
655            .unwrap_err();
656        assert_eq!(err.to_string(), "failed");
657        assert!(!is_enabled());
658    }
659
660    #[test]
661    fn builder_panic_handling() {
662        let _lock = LOCK
663            .lock()
664            .unwrap_or_else(std::sync::PoisonError::into_inner);
665
666        System::build()
667            .signals(true)
668            .panic_handling(true)
669            .build(TestRunner)
670            .run(|| {
671                crate::spawn_detached(async {
672                    deliver(
673                        || {
674                            // a `String` payload, like a formatted message
675                            let res =
676                                std::thread::spawn(|| panic::panic_any("run".to_string())).join();
677                            assert!(res.is_err());
678                        },
679                        |sig| is_app_panic(sig, "run"),
680                    )
681                    .await;
682                    System::current().stop();
683                });
684                Ok(())
685            })
686            .unwrap();
687        assert!(!is_enabled());
688
689        System::build()
690            .signals(true)
691            .panic_handling(true)
692            .build(TestRunner)
693            .block_on(async {
694                assert!(is_enabled());
695                deliver(
696                    || {
697                        let res = std::thread::spawn(|| panic!("block_on")).join();
698                        assert!(res.is_err());
699                    },
700                    |sig| is_app_panic(sig, "block_on"),
701                )
702                .await;
703            });
704        assert!(!is_enabled());
705    }
706
707    #[test]
708    fn signals_registered_by_one_system() {
709        let _lock = LOCK
710            .lock()
711            .unwrap_or_else(std::sync::PoisonError::into_inner);
712
713        let barrier = Arc::new(std::sync::Barrier::new(4));
714        let handles: Vec<_> = (0..4)
715            .map(|i| {
716                let barrier = barrier.clone();
717                std::thread::spawn(move || {
718                    System::new(&format!("sys{i}"), TestRunner).block_on(async move {
719                        let sys = System::current();
720                        barrier.wait();
721                        sys.enable_signals();
722                        let enabled = sys.signals();
723                        // keep signals registered until all systems tried
724                        barrier.wait();
725                        enabled
726                    })
727                })
728            })
729            .collect();
730        let enabled = handles
731            .into_iter()
732            .map(|h| h.join().unwrap())
733            .filter(|enabled| *enabled)
734            .count();
735        assert_eq!(enabled, 1);
736        assert!(!is_enabled());
737    }
738
739    #[test]
740    fn signals_queued_from_many_threads() {
741        let _lock = LOCK
742            .lock()
743            .unwrap_or_else(std::sync::PoisonError::into_inner);
744        SIGS.lock().clear();
745
746        let handles: Vec<_> = (0..4)
747            .map(|_| {
748                std::thread::spawn(|| {
749                    for _ in 0..25 {
750                        handle_signal(Signal::Hup);
751                    }
752                })
753            })
754            .collect();
755        let hups = || {
756            let sigs = std::mem::take(&mut *SIGS.lock());
757            sigs.iter().filter(|sig| matches!(sig, Signal::Hup)).count()
758        };
759        let mut received = 0;
760        while received < 100 && !handles.iter().all(std::thread::JoinHandle::is_finished) {
761            received += hups();
762        }
763        for h in handles {
764            h.join().unwrap();
765        }
766        received += hups();
767        assert_eq!(received, 100);
768    }
769
770    #[cfg(target_family = "unix")]
771    #[test]
772    fn os_signal_delivered() {
773        let _lock = LOCK
774            .lock()
775            .unwrap_or_else(std::sync::PoisonError::into_inner);
776
777        System::new("test", TestRunner).block_on(async {
778            let sys = System::current();
779            sys.enable_signals();
780            assert!(sys.signals());
781
782            deliver(
783                || unsafe {
784                    libc::kill(libc::getpid(), libc::SIGHUP);
785                },
786                |sig| matches!(sig, Signal::Hup),
787            )
788            .await;
789        });
790        assert!(!is_enabled());
791    }
792
793    #[cfg(target_family = "windows")]
794    #[test]
795    fn reenable_signals() {
796        let _lock = LOCK
797            .lock()
798            .unwrap_or_else(std::sync::PoisonError::into_inner);
799
800        System::new("test", TestRunner).block_on(async {
801            let sys = System::current();
802            sys.enable_signals();
803            sys.disable_signals();
804            sys.enable_signals();
805            assert!(sys.signals());
806            sys.disable_signals();
807            assert!(!sys.signals());
808        });
809    }
810
811    #[cfg(target_os = "linux")]
812    fn signal_child_mode() -> Option<String> {
813        let mode = std::env::var("NTEX_FATAL_SIGNAL_CHILD").ok()?;
814        for (resource, limit) in [(libc::RLIMIT_CORE, 0), (libc::RLIMIT_CPU, 10)] {
815            let limits = libc::rlimit {
816                rlim_cur: limit,
817                rlim_max: limit,
818            };
819            assert_eq!(unsafe { libc::setrlimit(resource, &raw const limits) }, 0);
820        }
821        Some(mode)
822    }
823
824    #[cfg(target_os = "linux")]
825    fn signal_child(test: &str, mode: &str) -> std::process::ExitStatus {
826        use std::process::{Command, Stdio};
827        use std::time::{Duration, Instant};
828
829        let mut child = Command::new(std::env::current_exe().unwrap())
830            .args(["--exact", &format!("signals::tests::{test}")])
831            .env("NTEX_FATAL_SIGNAL_CHILD", mode)
832            .stdout(Stdio::null())
833            .stderr(Stdio::null())
834            .spawn()
835            .unwrap();
836        let start = Instant::now();
837        loop {
838            if let Some(status) = child.try_wait().unwrap() {
839                return status;
840            }
841            if start.elapsed() > Duration::from_secs(10) {
842                child.kill().unwrap();
843                child.wait().unwrap();
844                panic!("{test} ({mode}) did not terminate");
845            }
846            std::thread::sleep(Duration::from_millis(10));
847        }
848    }
849
850    #[cfg(target_os = "linux")]
851    #[test]
852    fn fatal_signals_terminate_process() {
853        use std::os::unix::process::ExitStatusExt;
854
855        if let Some(mode) = signal_child_mode() {
856            System::new("test", TestRunner).block_on(async {
857                System::current().enable_signals();
858                let other_thread = mode.starts_with("thread-");
859                let crash = move || {
860                    if mode.ends_with("abort") {
861                        std::process::abort();
862                    }
863                    unsafe {
864                        let page = libc::mmap(
865                            std::ptr::null_mut(),
866                            4096,
867                            libc::PROT_NONE,
868                            libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
869                            -1,
870                            0,
871                        );
872                        assert_ne!(page, libc::MAP_FAILED);
873                        page.cast::<u8>().write_volatile(1);
874                    }
875                };
876                if other_thread {
877                    let _stderr = std::io::stderr().lock();
878                    let _sigs = SIGS.lock();
879                    std::thread::spawn(crash).join().unwrap();
880                } else {
881                    crash();
882                }
883            });
884            return;
885        }
886
887        for mode in ["abort", "segv", "thread-abort", "thread-segv"] {
888            let status = signal_child("fatal_signals_terminate_process", mode);
889            let expected = if mode.ends_with("abort") {
890                libc::SIGABRT
891            } else {
892                libc::SIGSEGV
893            };
894            assert_eq!(status.signal(), Some(expected), "{mode}: {status}");
895        }
896    }
897
898    #[cfg(target_os = "linux")]
899    async fn wait_for_signal_handler() {
900        poll_fn(|cx| {
901            if HANDLERS.with(|handlers| handlers.borrow().is_empty()) {
902                cx.waker().wake_by_ref();
903                Poll::Pending
904            } else {
905                Poll::Ready(())
906            }
907        })
908        .await;
909    }
910
911    #[cfg(target_os = "linux")]
912    fn raise_fatal_signal(sig: i32) {
913        std::thread::spawn(move || {
914            nix::errno::Errno::EBUSY.set();
915            assert_eq!(unsafe { libc::raise(sig) }, 0);
916            assert_eq!(nix::errno::Errno::last(), nix::errno::Errno::EBUSY);
917        })
918        .join()
919        .unwrap();
920    }
921
922    #[cfg(target_os = "linux")]
923    #[test]
924    fn fatal_signal_notifications() {
925        use std::os::fd::AsRawFd;
926
927        if let Some(mode) = signal_child_mode() {
928            System::new("test", TestRunner).block_on(async move {
929                System::current().enable_signals();
930                if mode == "full" {
931                    // Block the receiver and fill the pipe: the signal handler
932                    // must still return without waiting or changing errno.
933                    let _sigs = SIGS.lock();
934                    let (_, _, write) = FATAL_PIPE.get().unwrap();
935                    let fd = write.as_raw_fd();
936                    assert_eq!(unsafe { libc::write(fd, b"A".as_ptr().cast(), 1) }, 1);
937                    let bytes = [0u8; 1];
938                    while unsafe { libc::write(fd, bytes.as_ptr().cast(), bytes.len()) } >= 0 {}
939                    assert_eq!(nix::errno::Errno::last(), nix::errno::Errno::EAGAIN);
940                    raise_fatal_signal(libc::SIGABRT);
941                    return;
942                }
943
944                if mode == "fork" {
945                    let rx = signal();
946                    wait_for_signal_handler().await;
947                    let pid = unsafe { libc::fork() };
948                    assert!(pid >= 0);
949                    if pid == 0 {
950                        unsafe {
951                            libc::alarm(2);
952                            libc::raise(libc::SIGABRT);
953                            libc::_exit(0);
954                        }
955                    }
956                    let mut status = 0;
957                    assert_eq!(unsafe { libc::waitpid(pid, &raw mut status, 0) }, pid);
958                    assert_eq!(status, 0);
959                    raise_fatal_signal(libc::SIGSEGV);
960                    let sigs = recv(rx).await.unwrap();
961                    assert!(
962                        sigs.iter()
963                            .all(|sig| matches!(sig, Signal::Panic(PanicSource::Sig("SIGSEGV"))))
964                    );
965                    return;
966                }
967
968                for (sig, name) in [(libc::SIGABRT, "SIGABRT"), (libc::SIGSEGV, "SIGSEGV")] {
969                    let rx = signal();
970                    wait_for_signal_handler().await;
971                    {
972                        let _stderr = std::io::stderr().lock();
973                        let _sigs = SIGS.lock();
974                        raise_fatal_signal(sig);
975                    }
976                    let sigs = recv(rx).await.expect("fatal signal was not delivered");
977                    assert!(sigs.iter().any(
978                        |sig| matches!(sig, Signal::Panic(PanicSource::Sig(s)) if *s == name)
979                    ));
980                }
981            });
982            return;
983        }
984
985        for mode in ["notify", "full", "fork"] {
986            let status = signal_child("fatal_signal_notifications", mode);
987            assert!(status.success(), "{mode}: {status}");
988        }
989    }
990
991    #[cfg(target_os = "linux")]
992    #[test]
993    fn fatal_signal_stops_server() {
994        if let Some(mode) = signal_child_mode() {
995            ntex::rt::System::new("test", ntex::rt::DefaultRuntime).block_on(async move {
996                let srv = ntex::server::build()
997                    .workers(1)
998                    .graceful_shutdown()
999                    .bind(
1000                        "test",
1001                        "127.0.0.1:0",
1002                        ntex::service::cfg::SharedCfg::default(),
1003                        async |()| async |_| Ok::<_, ()>(()),
1004                    )
1005                    .unwrap()
1006                    .run();
1007                while srv.status() != ntex::server::ServerStatus::Ready {
1008                    srv.status_changed().await;
1009                }
1010                futures_timer::Delay::new(std::time::Duration::from_millis(50)).await;
1011                raise_fatal_signal(if mode == "abort" {
1012                    libc::SIGABRT
1013                } else {
1014                    libc::SIGSEGV
1015                });
1016                let stopped = srv.clone();
1017                srv.await.unwrap();
1018                assert_eq!(stopped.status(), ntex::server::ServerStatus::Stopped);
1019            });
1020            return;
1021        }
1022
1023        for mode in ["abort", "segv"] {
1024            let status = signal_child("fatal_signal_stops_server", mode);
1025            assert!(status.success(), "{mode}: {status}");
1026        }
1027    }
1028}