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),
44 App(Arc<str>, Backtrace),
46}
47
48pub fn signal() -> oneshot::AsyncReceiver<Arc<[Signal]>> {
53 let (tx, rx) = oneshot::async_channel();
54 System::current().handle().spawn(async move {
55 HANDLERS.with(|handlers| {
56 handlers.borrow_mut().push(tx);
57 });
58 });
59
60 rx
61}
62
63pub fn is_enabled() -> bool {
65 CUR_SYS.lock().is_some()
66}
67
68type Registration = MutexGuard<'static, Option<System>>;
69
70fn register_system(sys: &System) -> Option<Registration> {
75 let mut cur = CUR_SYS.lock();
76 if cur.is_some() {
77 None
78 } else {
79 *cur = Some(sys.clone());
80 ENABLED.store(true, Ordering::Release);
81
82 let (tx, rx) = oneshot::async_channel();
83 sys.handle().spawn(signals(rx));
84 STOP.with(|stop| {
85 *stop.borrow_mut() = Some(tx);
86 });
87 Some(cur)
88 }
89}
90
91fn unregister_system(sys: &System) -> Option<Registration> {
96 let mut cur = CUR_SYS.lock();
97 if cur.as_ref().is_some_and(|cur| cur.id() == sys.id()) {
98 cur.take();
99 ENABLED.store(false, Ordering::Release);
100 sys.handle().spawn(async move {
101 STOP.with(|stop| {
102 if let Some(tx) = stop.borrow_mut().take() {
103 let _ = tx.send(());
104 }
105 });
106 });
107 Some(cur)
108 } else {
109 None
110 }
111}
112
113fn handle_signal(sig: Signal) {
117 SIGS.lock().push(sig);
118 HND_WAKER.wake();
119}
120
121#[cfg(target_family = "unix")]
122static SIG_HANDLERS: Mutex<(
124 Option<signal_hook::iterator::Handle>,
125 Option<signal_hook::SigId>,
126)> = Mutex::new((None, None));
127
128#[cfg(target_family = "unix")]
129pub(crate) fn start(sys: &System) -> bool {
133 static ONCE: std::sync::Once = std::sync::Once::new();
134
135 if let Some(_registration) = register_system(sys) {
136 use nix::sys::signal;
137 use signal_hook::consts::signal::{SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR2};
138 use signal_hook::{iterator::Signals, low_level::register};
139
140 ONCE.call_once(|| {
141 let buf = Vec::leak(vec![0u128; 4096]);
143 let stack = libc::stack_t {
144 ss_sp: buf.as_ptr() as *mut libc::c_void,
145 ss_flags: 0,
146 ss_size: std::mem::size_of_val(buf),
147 };
148 let mut old = libc::stack_t {
149 ss_sp: std::ptr::null_mut(),
150 ss_flags: 0,
151 ss_size: 0,
152 };
153 let result = unsafe { libc::sigaltstack(&raw const stack, &raw mut old) };
154 if result != 0 {
155 log::error!("Cannot set signal stack");
156 }
157
158 let sig_action = signal::SigAction::new(
159 signal::SigHandler::Handler(sig_segv),
160 signal::SaFlags::SA_NODEFER | signal::SaFlags::SA_ONSTACK,
161 signal::SigSet::empty(),
162 );
163 unsafe {
164 match signal::sigaction(signal::SIGSEGV, &sig_action) {
165 Ok(prev) => _ = PREV_SIGSEGV.set(prev),
166 Err(_) => log::error!("Cannot install signal handler for SIGSEGV"),
167 }
168 match signal::sigaction(signal::SIGABRT, &sig_action) {
169 Ok(prev) => _ = PREV_SIGABRT.set(prev),
170 Err(_) => log::error!("Cannot install signal handler for SIGABRT"),
171 }
172 }
173 });
174
175 let handle = match Signals::new([SIGHUP, SIGINT, SIGTERM, SIGQUIT]) {
178 Ok(mut signals) => {
179 let handle = signals.handle();
180 let result = std::thread::Builder::new()
181 .name("ntex-rt:signals".to_string())
182 .spawn(move || {
183 for sig in signals.forever() {
184 handle_signal(match sig {
185 SIGHUP => Signal::Hup,
186 SIGINT => Signal::Int,
187 SIGTERM => Signal::Term,
188 SIGQUIT => Signal::Quit,
189 _ => continue,
190 });
191 }
192 });
193 match result {
194 Ok(_) => Some(handle),
195 Err(e) => {
196 log::error!("Cannot start signal handling thread: {e:?}");
197 None
198 }
199 }
200 }
201 Err(e) => {
202 log::error!("Cannot install signal handlers: {e:?}");
203 None
204 }
205 };
206
207 let usr2 = unsafe { register(SIGUSR2, || crate::system::sig_usr2()) };
208 if usr2.is_err() {
209 log::error!("Cannot install signal handler for SIGUSR2");
210 }
211 *SIG_HANDLERS.lock() = (handle, usr2.ok());
212 true
213 } else {
214 false
215 }
216}
217
218#[cfg(target_family = "unix")]
219pub(crate) fn stop(sys: &System) {
221 if let Some(_registration) = unregister_system(sys) {
222 let (handle, usr2) = std::mem::take(&mut *SIG_HANDLERS.lock());
223 if let Some(handle) = handle {
224 handle.close();
226 }
227 if let Some(usr2) = usr2 {
228 signal_hook::low_level::unregister(usr2);
229 }
230 }
231}
232
233#[cfg(target_family = "windows")]
234static CTRLC_ENABLED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
235
236#[cfg(target_family = "windows")]
237pub(crate) fn start(sys: &System) -> bool {
242 use std::sync::atomic::Ordering;
243 static ONCE: std::sync::Once = std::sync::Once::new();
244
245 if let Some(_registration) = register_system(sys) {
246 ONCE.call_once(|| {
248 let result = ctrlc::set_handler(|| {
249 if CTRLC_ENABLED.load(Ordering::Acquire) {
250 handle_signal(Signal::Int);
251 }
252 });
253 if let Err(e) = result {
254 log::error!("Cannot install Ctrl-C handler: {e:?}");
255 }
256 });
257 CTRLC_ENABLED.store(true, Ordering::Release);
258 true
259 } else {
260 false
261 }
262}
263
264#[cfg(target_family = "windows")]
265pub(crate) fn stop(sys: &System) {
267 if let Some(_registration) = unregister_system(sys) {
268 CTRLC_ENABLED.store(false, std::sync::atomic::Ordering::Release);
269 log::info!("Signals handling is disabled");
270 }
271}
272
273async fn signals(rx: oneshot::AsyncReceiver<()>) {
274 let mut rx = std::pin::pin!(rx);
275
276 poll_fn(|cx| {
277 if rx.as_mut().poll(cx).is_ready() {
278 Poll::Ready(())
279 } else {
280 HND_WAKER.register(cx.waker());
281
282 let sigs = std::mem::take(&mut *SIGS.lock());
283 if !sigs.is_empty() {
284 let sigs: Arc<[Signal]> = Arc::from(sigs);
285
286 HANDLERS.with(|handlers| {
287 for tx in handlers.borrow_mut().drain(..) {
288 let _ = tx.send(sigs.clone());
289 }
290 });
291 }
292
293 Poll::Pending
294 }
295 })
296 .await;
297}
298
299#[cfg(target_family = "unix")]
300static PREV_SIGSEGV: std::sync::OnceLock<nix::sys::signal::SigAction> = std::sync::OnceLock::new();
301#[cfg(target_family = "unix")]
302static PREV_SIGABRT: std::sync::OnceLock<nix::sys::signal::SigAction> = std::sync::OnceLock::new();
303
304#[cfg(target_family = "unix")]
305extern "C" fn sig_segv(v: i32) {
306 use nix::sys::signal::{self, SaFlags, SigAction, SigHandler, SigSet};
307
308 let (sig, prev, name) = if v == libc::SIGABRT {
309 (signal::SIGABRT, &PREV_SIGABRT, "SIGABRT")
310 } else {
311 (signal::SIGSEGV, &PREV_SIGSEGV, "SIGSEGV")
312 };
313 eprintln!("{name} Received:\n{:?}", backtrace::Backtrace::new());
314 if let Some(mut sigs) = SIGS.try_lock() {
316 sigs.push(Signal::Panic(PanicSource::Sig(name)));
317 }
318
319 let prev = prev
322 .get()
323 .copied()
324 .unwrap_or_else(|| SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()));
325 unsafe {
326 let _ = signal::sigaction(sig, &prev);
327 }
328}
329
330pub(crate) fn enable_panic_handling() {
335 static ONCE: std::sync::Once = std::sync::Once::new();
336
337 ONCE.call_once(|| {
338 let (tx, rx) = mpsc::sync_channel::<()>(1);
339 let result = std::thread::Builder::new()
340 .name("ntex-rt:panics".to_string())
341 .spawn(move || {
342 for () in rx {
343 HND_WAKER.wake();
344 }
345 });
346 match result {
347 Ok(_) => _ = PANIC_WAKER.set(tx),
348 Err(e) => log::error!("Cannot start panic handling thread: {e:?}"),
349 }
350
351 let prev = panic::take_hook();
352 panic::set_hook(Box::new(move |panic_info| {
353 prev(panic_info);
354
355 if !ENABLED.load(Ordering::Acquire) {
356 return;
357 }
358
359 let info: Arc<str> = if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
360 Arc::from(s.to_string())
361 } else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
362 Arc::from(s.clone())
363 } else {
364 Arc::from("panic")
365 };
366 let bt = if let Some(loc) = panic_info.location() {
367 let s = Box::new(loc.file().to_string());
368 let filename = Box::leak(s);
369 Backtrace::with_filename(filename)
370 } else {
371 Backtrace::new(panic::Location::caller())
372 };
373
374 SIGS.lock().push(Signal::Panic(PanicSource::App(info, bt)));
375 if let Some(tx) = PANIC_WAKER.get() {
376 let _ = tx.try_send(());
378 }
379 }));
380 });
381}
382
383#[cfg(all(test, any(target_family = "windows", target_family = "unix")))]
384mod tests {
385 use crate::testing::TestRunner;
386
387 use super::*;
388
389 static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
391
392 async fn recv(rx: oneshot::AsyncReceiver<Arc<[Signal]>>) -> Option<Arc<[Signal]>> {
393 let mut rx = std::pin::pin!(rx);
394 let mut timeout =
395 std::pin::pin!(futures_timer::Delay::new(std::time::Duration::from_secs(5)));
396 poll_fn(|cx| {
397 if let Poll::Ready(res) = rx.as_mut().poll(cx) {
398 Poll::Ready(res.ok())
399 } else if timeout.as_mut().poll(cx).is_ready() {
400 Poll::Ready(None)
401 } else {
402 Poll::Pending
403 }
404 })
405 .await
406 }
407
408 async fn deliver(raise: impl Fn(), expected: impl Fn(&Signal) -> bool) -> usize {
416 for attempt in 1..=10 {
417 let rx = signal();
418 futures_timer::Delay::new(std::time::Duration::from_millis(50)).await;
420 raise();
421 if recv(rx)
422 .await
423 .is_some_and(|sigs| sigs.iter().any(&expected))
424 {
425 return attempt;
426 }
427 }
428 panic!("signal is not delivered");
429 }
430
431 fn is_app_panic(sig: &Signal, expected: &str) -> bool {
432 matches!(sig, Signal::Panic(PanicSource::App(msg, _)) if &**msg == expected)
433 }
434
435 #[test]
436 fn panic_hook_chains_and_follows_signals() {
437 use std::sync::atomic::AtomicUsize;
438
439 static CALLS: AtomicUsize = AtomicUsize::new(0);
440
441 let _lock = LOCK
442 .lock()
443 .unwrap_or_else(std::sync::PoisonError::into_inner);
444
445 let prev = panic::take_hook();
446 panic::set_hook(Box::new(move |info| {
447 let msg = info.payload().downcast_ref::<&str>();
449 if matches!(msg, Some(&("no signals" | "boom"))) {
450 CALLS.fetch_add(1, Ordering::Relaxed);
451 }
452 prev(info);
453 }));
454 enable_panic_handling();
455 enable_panic_handling();
457
458 SIGS.lock().clear();
460 let _ = panic::catch_unwind(|| panic!("no signals"));
461 assert_eq!(CALLS.load(Ordering::Relaxed), 1);
462 assert!(
463 !SIGS
464 .lock()
465 .iter()
466 .any(|sig| is_app_panic(sig, "no signals"))
467 );
468
469 let attempts = System::new("test", TestRunner).block_on(async {
470 let sys = System::current();
471 sys.enable_signals();
472
473 deliver(
474 || {
475 let _ = panic::catch_unwind(|| panic!("boom"));
476 },
477 |sig| is_app_panic(sig, "boom"),
478 )
479 .await
480 });
481 assert_eq!(CALLS.load(Ordering::Relaxed), 1 + attempts);
483 }
484
485 #[test]
486 fn panic_on_other_thread_delivered() {
487 let _lock = LOCK
488 .lock()
489 .unwrap_or_else(std::sync::PoisonError::into_inner);
490 enable_panic_handling();
491
492 System::new("test", TestRunner).block_on(async {
493 System::current().enable_signals();
494
495 deliver(
497 || {
498 let res = std::thread::spawn(|| panic!("thread boom")).join();
499 assert!(res.is_err());
500 },
501 |sig| is_app_panic(sig, "thread boom"),
502 )
503 .await;
504 });
505 }
506
507 #[test]
508 fn signals_released_when_future_panics() {
509 let _lock = LOCK
510 .lock()
511 .unwrap_or_else(std::sync::PoisonError::into_inner);
512
513 let res = panic::catch_unwind(|| {
514 System::new("block_on", TestRunner).block_on(async {
515 System::current().enable_signals();
516 assert!(is_enabled());
517 panic!("block_on fails");
518 });
519 });
520 assert!(res.is_err());
521 assert!(!is_enabled());
522
523 let res = panic::catch_unwind(|| {
524 System::build().signals(true).build(TestRunner).run(|| {
525 assert!(is_enabled());
526 panic!("run fails");
527 })
528 });
529 assert!(res.is_err());
530 assert!(!is_enabled());
531 }
532
533 #[test]
534 fn signals_released_when_system_stops() {
535 let _lock = LOCK
536 .lock()
537 .unwrap_or_else(std::sync::PoisonError::into_inner);
538
539 let first = System::new("first", TestRunner).block_on(async {
540 let sys = System::current();
541 sys.enable_signals();
542 assert!(sys.signals());
543 sys
544 });
545 assert!(!is_enabled());
546 assert!(!first.signals());
547
548 System::new("second", TestRunner).block_on(async {
549 let sys = System::current();
550 sys.enable_signals();
551 assert!(sys.signals());
552 assert!(is_enabled());
553
554 let other = std::thread::spawn(|| {
556 System::new("other", TestRunner).block_on(async {
557 let sys = System::current();
558 sys.enable_signals();
559 sys.signals()
560 })
561 })
562 .join()
563 .unwrap();
564 assert!(!other);
565 assert!(is_enabled());
566 });
567 assert!(!is_enabled());
568 }
569
570 #[test]
571 fn builder_signals() {
572 let _lock = LOCK
573 .lock()
574 .unwrap_or_else(std::sync::PoisonError::into_inner);
575
576 let stopper = std::thread::spawn(|| {
578 loop {
579 let sys = CUR_SYS.lock().clone().filter(|sys| sys.name() == "signals");
580 if let Some(sys) = sys {
581 sys.stop();
582 break;
583 }
584 std::thread::sleep(std::time::Duration::from_millis(1));
585 }
586 });
587 System::build()
588 .name("signals")
589 .enable_signals()
590 .build(TestRunner)
591 .run_until_stop()
592 .unwrap();
593 stopper.join().unwrap();
594 assert!(!is_enabled());
595
596 let err = System::build()
598 .signals(true)
599 .build(TestRunner)
600 .run(|| {
601 assert!(System::current().signals());
602 assert!(is_enabled());
603 Err(std::io::Error::other("failed"))
604 })
605 .unwrap_err();
606 assert_eq!(err.to_string(), "failed");
607 assert!(!is_enabled());
608 }
609
610 #[test]
611 fn signals_registered_by_one_system() {
612 let _lock = LOCK
613 .lock()
614 .unwrap_or_else(std::sync::PoisonError::into_inner);
615
616 let barrier = Arc::new(std::sync::Barrier::new(4));
617 let handles: Vec<_> = (0..4)
618 .map(|i| {
619 let barrier = barrier.clone();
620 std::thread::spawn(move || {
621 System::new(&format!("sys{i}"), TestRunner).block_on(async move {
622 let sys = System::current();
623 barrier.wait();
624 sys.enable_signals();
625 let enabled = sys.signals();
626 barrier.wait();
628 enabled
629 })
630 })
631 })
632 .collect();
633 let enabled = handles
634 .into_iter()
635 .map(|h| h.join().unwrap())
636 .filter(|enabled| *enabled)
637 .count();
638 assert_eq!(enabled, 1);
639 assert!(!is_enabled());
640 }
641
642 #[test]
643 fn signals_queued_from_many_threads() {
644 let _lock = LOCK
645 .lock()
646 .unwrap_or_else(std::sync::PoisonError::into_inner);
647 SIGS.lock().clear();
648
649 let handles: Vec<_> = (0..4)
650 .map(|_| {
651 std::thread::spawn(|| {
652 for _ in 0..25 {
653 handle_signal(Signal::Hup);
654 }
655 })
656 })
657 .collect();
658 let hups = || {
659 let sigs = std::mem::take(&mut *SIGS.lock());
660 sigs.iter().filter(|sig| matches!(sig, Signal::Hup)).count()
661 };
662 let mut received = 0;
663 while received < 100 && !handles.iter().all(std::thread::JoinHandle::is_finished) {
664 received += hups();
665 }
666 for h in handles {
667 h.join().unwrap();
668 }
669 received += hups();
670 assert_eq!(received, 100);
671 }
672
673 #[cfg(target_family = "unix")]
674 #[test]
675 fn os_signal_delivered() {
676 let _lock = LOCK
677 .lock()
678 .unwrap_or_else(std::sync::PoisonError::into_inner);
679
680 System::new("test", TestRunner).block_on(async {
681 let sys = System::current();
682 sys.enable_signals();
683 assert!(sys.signals());
684
685 deliver(
686 || unsafe {
687 libc::kill(libc::getpid(), libc::SIGHUP);
688 },
689 |sig| matches!(sig, Signal::Hup),
690 )
691 .await;
692 });
693 assert!(!is_enabled());
694 }
695
696 #[cfg(target_family = "windows")]
697 #[test]
698 fn reenable_signals() {
699 let _lock = LOCK
700 .lock()
701 .unwrap_or_else(std::sync::PoisonError::into_inner);
702
703 System::new("test", TestRunner).block_on(async {
704 let sys = System::current();
705 sys.enable_signals();
706 sys.disable_signals();
707 sys.enable_signals();
708 assert!(sys.signals());
709 sys.disable_signals();
710 assert!(!sys.signals());
711 });
712 }
713
714 #[cfg(target_os = "linux")]
715 #[test]
716 fn segv_terminates_process() {
717 use std::os::unix::process::ExitStatusExt;
718 use std::process::{Command, Stdio};
719 use std::time::{Duration, Instant};
720
721 if std::env::var_os("NTEX_SEGV_CHILD").is_some() {
722 System::new("test", TestRunner).block_on(async {
723 System::current().enable_signals();
724 unsafe {
725 let page = libc::mmap(
726 std::ptr::null_mut(),
727 4096,
728 libc::PROT_NONE,
729 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
730 -1,
731 0,
732 );
733 assert_ne!(page, libc::MAP_FAILED);
734 page.cast::<u8>().write_volatile(1);
735 }
736 });
737 return;
738 }
739
740 let mut child = Command::new(std::env::current_exe().unwrap())
741 .args(["--exact", "signals::tests::segv_terminates_process"])
742 .env("NTEX_SEGV_CHILD", "1")
743 .stdout(Stdio::null())
744 .stderr(Stdio::null())
745 .spawn()
746 .unwrap();
747 let start = Instant::now();
748 let status = loop {
749 if let Some(status) = child.try_wait().unwrap() {
750 break status;
751 }
752 if start.elapsed() > Duration::from_secs(30) {
753 let _ = child.kill();
754 panic!("process did not terminate after SIGSEGV");
755 }
756 std::thread::sleep(Duration::from_millis(50));
757 };
758 assert_eq!(status.signal(), Some(libc::SIGSEGV));
759 }
760}