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);
16static ENABLED: AtomicBool = AtomicBool::new(false);
18static SIGS: Mutex<Vec<Signal>> = Mutex::new(Vec::new());
19static HND_WAKER: AtomicWaker = AtomicWaker::new();
20static PANIC_WAKER: OnceLock<mpsc::SyncSender<()>> = OnceLock::new();
23
24#[derive(Clone, Debug)]
26pub enum Signal {
27 Hup,
29 Int,
31 Term,
33 Quit,
35 Panic(PanicSource),
37}
38
39#[derive(Clone, Debug)]
41pub enum PanicSource {
42 Sig(&'static str),
47 App(Arc<str>, Backtrace),
49}
50
51pub 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
66pub fn is_enabled() -> bool {
68 CUR_SYS.lock().is_some()
69}
70
71type Registration = MutexGuard<'static, Option<System>>;
72
73fn 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
94fn 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
116fn handle_signal(sig: Signal) {
120 SIGS.lock().push(sig);
121 HND_WAKER.wake();
122}
123
124#[cfg(target_family = "unix")]
125static 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")]
132pub(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 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 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")]
226pub(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 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")]
244pub(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 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")]
272pub(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#[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 if libc::getpid() == *pid {
363 let _ = libc::write(write.as_raw_fd(), byte.as_ptr().cast(), 1);
364 }
365 }
366 }
367
368 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
380pub(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 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 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 async fn deliver(raise: impl Fn(), expected: impl Fn(&Signal) -> bool) -> usize {
466 for attempt in 1..=10 {
467 let rx = signal();
468 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 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 enable_panic_handling();
507
508 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 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 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 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 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 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 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 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 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}