File: shutdown.rs

package info (click to toggle)
rust-signal-hook 0.3.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 344 kB
  • sloc: ansic: 46; sh: 14; makefile: 2
file content (81 lines) | stat: -rw-r--r-- 3,301 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//! Tests for the shutdown.
//!
//! The tests work like this:
//!
//! * The register an alarm, to fail if anything takes too long (which is very much possible here).
//! * A fork is done, with the child registering a signal with a NOP and cleanup operation (one or
//!   the other).
//! * The child puts some kind of infinite loop or sleep inside itself, so it never actually
//!   terminates on the first, but would terminate after the signal.

#![cfg(not(windows))] // Forks don't work on Windows, but windows has the same implementation.

use std::io::Error;
use std::ptr;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::thread;
use std::time::Duration;

use signal_hook::consts::signal::*;
use signal_hook::flag;
use signal_hook::low_level;

fn do_test<C: FnOnce()>(child: C) {
    unsafe {
        libc::alarm(10); // Time out the test after 10 seconds and get it killed.
        match libc::fork() {
            -1 => panic!("Fork failed: {}", Error::last_os_error()),
            0 => {
                child();
                loop {
                    thread::sleep(Duration::from_secs(1));
                }
            }
            pid => {
                // Give the child some time to register signals and stuff
                // We could actually signal that the child is ready by it eg. closing STDOUT, but
                // this is just a test so we don't really bother.
                thread::sleep(Duration::from_millis(250));
                libc::kill(pid, libc::SIGTERM);
                // Wait a small bit to make sure the signal got delivered.
                thread::sleep(Duration::from_millis(50));
                // The child is still running, because the first signal got "handled" by being
                // ignored.
                let terminated = libc::waitpid(pid, ptr::null_mut(), libc::WNOHANG);
                assert_eq!(0, terminated, "Process {} terminated prematurely", pid);
                // But it terminates on the second attempt (we do block on wait here).
                libc::kill(pid, libc::SIGTERM);
                let terminated = libc::waitpid(pid, ptr::null_mut(), 0);
                assert_eq!(pid, terminated);
            }
        }
    }
}

/// Use automatic cleanup inside the signal handler to get rid of old signals, the aggressive way.
#[test]
fn cleanup_inside_signal() {
    fn hook() {
        // Make sure we have some signal handler, not the default.
        unsafe { low_level::register(SIGTERM, || ()).unwrap() };
        let shutdown_cond = Arc::new(AtomicBool::new(false));
        // „disarmed“ shutdown
        flag::register_conditional_shutdown(SIGTERM, 0, Arc::clone(&shutdown_cond)).unwrap();
        // But arm at the first SIGTERM
        flag::register(SIGTERM, shutdown_cond).unwrap();
    }
    do_test(hook);
}

/// Manually remove the signal handler just after receiving the signal but before going into an
/// infinite loop.
#[cfg(feature = "iterator")]#[test]
fn cleanup_after_signal() {
    fn hook() {
        let mut signals = signal_hook::iterator::Signals::new(&[libc::SIGTERM]).unwrap();
        assert_eq!(Some(SIGTERM), signals.into_iter().next());
        flag::register_conditional_shutdown(SIGTERM, 0, Arc::new(AtomicBool::new(true))).unwrap();
    }
    do_test(hook);
}