Skip to main content

running_process_platform_internal/platform_linux/
shutdown_request.rs

1//! Hearing the host ask this process to stop (POSIX signals).
2
3use std::io;
4use std::sync::atomic::{AtomicBool, Ordering};
5
6use crate::platform::process::ShutdownRequest;
7
8/// Set by the signal handler, read by whoever asked for it.
9///
10/// `static` because a signal handler has no way to receive context: the
11/// kernel calls a bare function pointer, so the only thing it can reach is
12/// something at a fixed address.
13static REQUESTED: AtomicBool = AtomicBool::new(false);
14
15/// The whole handler. One relaxed atomic store, nothing else.
16///
17/// A signal can arrive on any thread, between any two instructions --
18/// including inside the allocator or while a lock is held. Allocating,
19/// logging, or locking here can deadlock the process against itself, so this
20/// does the one thing that is async-signal-safe and lets the caller act later.
21extern "C" fn record_shutdown_request(_signal: libc::c_int) {
22    REQUESTED.store(true, Ordering::Relaxed);
23}
24
25/// Ask this host to report shutdown requests.
26///
27/// `SIGTERM` is what a supervisor or `kill` sends; `SIGINT` is Ctrl-C. Both
28/// mean the same thing to a daemon, and both default to terminating it
29/// outright -- which is what installing a handler replaces with a request the
30/// process can act on.
31pub fn install_shutdown_request_handler() -> io::Result<ShutdownRequest> {
32    REQUESTED.store(false, Ordering::Relaxed);
33    for signal in [libc::SIGTERM, libc::SIGINT] {
34        // SAFETY: `record_shutdown_request` has C ABI, lives for the process
35        // lifetime, and performs only an atomic store.
36        let previous = unsafe {
37            libc::signal(
38                signal,
39                record_shutdown_request as *const () as libc::sighandler_t,
40            )
41        };
42        if previous == libc::SIG_ERR {
43            return Err(io::Error::last_os_error());
44        }
45    }
46    Ok(ShutdownRequest::watching(&REQUESTED))
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    /// Installing reports "not asked yet", and a delivered signal is seen.
54    ///
55    /// The signal is raised in this process, which is the only way to test the
56    /// real delivery path rather than a stand-in for it.
57    #[test]
58    fn a_delivered_signal_is_observed() {
59        let request = install_shutdown_request_handler().expect("install");
60        assert!(!request.requested(), "nothing has asked yet");
61
62        // SAFETY: raising a signal this process has just installed a handler
63        // for; the handler only stores an atomic.
64        assert_eq!(unsafe { libc::raise(libc::SIGTERM) }, 0);
65        assert!(request.requested(), "a delivered SIGTERM must be observed");
66    }
67
68    /// The answer latches, so a caller cannot miss a request by checking late.
69    #[test]
70    fn the_request_latches() {
71        let request = install_shutdown_request_handler().expect("install");
72        // SAFETY: see above.
73        assert_eq!(unsafe { libc::raise(libc::SIGINT) }, 0);
74        assert!(request.requested());
75        assert!(request.requested(), "and stays asked");
76    }
77}