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    Sig(&'static str),
44    /// An application panic and its captured backtrace.
45    App(Arc<str>, Backtrace),
46}
47
48/// Registers interest in the next batch of signals.
49///
50/// The returned one-shot receiver handles one notification. Call this function
51/// again after each notification to continue receiving signals.
52pub 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
63/// Returns whether signal handling is enabled.
64pub fn is_enabled() -> bool {
65    CUR_SYS.lock().is_some()
66}
67
68type Registration = MutexGuard<'static, Option<System>>;
69
70/// Registers the system as the signal handler.
71///
72/// Returns the lock guard, so handlers are installed before other systems
73/// can register or unregister.
74fn 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
91/// Unregisters the system if it handles signals.
92///
93/// Returns the lock guard, so handlers are removed before other systems
94/// can register.
95fn 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
113/// Queue signal and wake the system.
114///
115/// Must not be called from a signal handler.
116fn handle_signal(sig: Signal) {
117    SIGS.lock().push(sig);
118    HND_WAKER.wake();
119}
120
121#[cfg(target_family = "unix")]
122/// Signal delivery thread handle and `SIGUSR2` handler id.
123static 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")]
129/// Register signal handler.
130///
131/// Returns `false` if signals are handled by another system.
132pub(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            // Use u128 for alignment.
142            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        // signal handlers only write to a self-pipe, signals are
176        // dispatched from a regular thread
177        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")]
219/// Unregister signal handler.
220pub(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            // the thread exits and unregisters handlers
225            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")]
237/// Register signal handler.
238///
239/// Signals are handled by oneshots, you have to re-register
240/// after each signal. Returns `false` if signals are handled by another system.
241pub(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        // the handler cannot be removed, it ignores signals while disabled
247        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")]
265/// Unregister signal handler.
266pub(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    // best effort, waking the system is not signal-safe
315    if let Some(mut sigs) = SIGS.try_lock() {
316        sigs.push(Signal::Panic(PanicSource::Sig(name)));
317    }
318
319    // restore the previous handler, it handles the signal raised again by
320    // the faulting instruction or by `abort()`, otherwise the process never exits
321    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
330/// Installs the panic hook, once per process.
331///
332/// The previous hook is called first. Panics are delivered as
333/// `Signal::Panic` only while signal handling is enabled.
334pub(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                // a pending wake covers this panic too
377                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    // signal handling is global
390    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    /// Raises a signal with `raise` until a batch with a signal matching
409    /// `expected` is delivered, returns the number of attempts.
410    ///
411    /// The panic hook is process-wide, panics of tests running in parallel are
412    /// delivered too. A batch without the expected signal consumes the
413    /// handler, and the expected signal may be dropped before the handler is
414    /// registered again, so it is raised again.
415    async fn deliver(raise: impl Fn(), expected: impl Fn(&Signal) -> bool) -> usize {
416        for attempt in 1..=10 {
417            let rx = signal();
418            // let the registration task run
419            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            // panics of other tests reach the hook too
448            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        // installed once
456        enable_panic_handling();
457
458        // previous hook is called, nothing is queued without signal handling
459        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        // the hook is installed once and chains the previous one
482        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            // compio aborts the process if the task is woken on a panicking thread
496            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            // signals are handled by one system at a time
555            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        // stopped by another thread
577        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        // signals are released if `f` fails
597        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                        // keep signals registered until all systems tried
627                        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}