diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/signal-hook/src/flag.rs | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip |
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/signal-hook/src/flag.rs')
-rw-r--r-- | vendor/signal-hook/src/flag.rs | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/vendor/signal-hook/src/flag.rs b/vendor/signal-hook/src/flag.rs new file mode 100644 index 000000000..8573fcd7f --- /dev/null +++ b/vendor/signal-hook/src/flag.rs @@ -0,0 +1,283 @@ +//! Module for actions setting flags. +//! +//! This contains helper functions to set flags whenever a signal happens. The flags are atomic +//! bools or numbers and the library manipulates them with the `SeqCst` ordering, in case someone +//! cares about relative order to some *other* atomic variables. If you don't care about the +//! relative order, you are free to use `Ordering::Relaxed` when reading and resetting the flags. +//! +//! # When to use +//! +//! The flags in this module allow for polling if a signal arrived since the previous poll. The do +//! not allow blocking until something arrives. +//! +//! Therefore, the natural way to use them is in applications that have some kind of iterative work +//! with both some upper and lower time limit on one iteration. If one iteration could block for +//! arbitrary time, the handling of the signal would be postponed for a long time. If the iteration +//! didn't block at all, the checking for the signal would turn into a busy-loop. +//! +//! If what you need is blocking until a signal comes, you might find better tools in the +//! [`pipe`][crate::low_level::pipe] and [`iterator`][crate::iterator] modules. +//! +//! # Examples +//! +//! Doing something until terminated. This also knows by which signal it was terminated. In case +//! multiple termination signals arrive before it is handled, it recognizes the last one. +//! +//! ```rust +//! use std::io::Error; +//! use std::sync::Arc; +//! use std::sync::atomic::{AtomicUsize, Ordering}; +//! +//! use signal_hook::consts::signal::*; +//! use signal_hook::flag as signal_flag; +//! +//! fn main() -> Result<(), Error> { +//! let term = Arc::new(AtomicUsize::new(0)); +//! const SIGTERM_U: usize = SIGTERM as usize; +//! const SIGINT_U: usize = SIGINT as usize; +//! # #[cfg(not(windows))] +//! const SIGQUIT_U: usize = SIGQUIT as usize; +//! signal_flag::register_usize(SIGTERM, Arc::clone(&term), SIGTERM_U)?; +//! signal_flag::register_usize(SIGINT, Arc::clone(&term), SIGINT_U)?; +//! # #[cfg(not(windows))] +//! signal_flag::register_usize(SIGQUIT, Arc::clone(&term), SIGQUIT_U)?; +//! +//! # // Hack to terminate the example when run as a doc-test. +//! # term.store(SIGTERM_U, Ordering::Relaxed); +//! loop { +//! match term.load(Ordering::Relaxed) { +//! 0 => { +//! // Do some useful stuff here +//! } +//! SIGTERM_U => { +//! eprintln!("Terminating on the TERM signal"); +//! break; +//! } +//! SIGINT_U => { +//! eprintln!("Terminating on the INT signal"); +//! break; +//! } +//! # #[cfg(not(windows))] +//! SIGQUIT_U => { +//! eprintln!("Terminating on the QUIT signal"); +//! break; +//! } +//! _ => unreachable!(), +//! } +//! } +//! +//! Ok(()) +//! } +//! ``` +//! +//! Sending a signal to self and seeing it arrived (not of a practical usage on itself): +//! +//! ```rust +//! use std::io::Error; +//! use std::sync::Arc; +//! use std::sync::atomic::{AtomicBool, Ordering}; +//! use std::thread; +//! use std::time::Duration; +//! +//! use signal_hook::consts::signal::*; +//! use signal_hook::low_level::raise; +//! +//! fn main() -> Result<(), Error> { +//! let got = Arc::new(AtomicBool::new(false)); +//! # #[cfg(not(windows))] +//! signal_hook::flag::register(SIGUSR1, Arc::clone(&got))?; +//! # #[cfg(windows)] +//! # signal_hook::flag::register(SIGTERM, Arc::clone(&got))?; +//! # #[cfg(not(windows))] +//! raise(SIGUSR1).unwrap(); +//! # #[cfg(windows)] +//! # raise(SIGTERM).unwrap(); +//! // A sleep here, because it could run the signal handler in another thread and we may not +//! // see the flag right away. This is still a hack and not guaranteed to work, it is just an +//! // example! +//! thread::sleep(Duration::from_secs(1)); +//! assert!(got.load(Ordering::Relaxed)); +//! Ok(()) +//! } +//! ``` +//! +//! Reloading a configuration on `SIGHUP` (which is a common behaviour of many UNIX daemons, +//! together with reopening the log file). +//! +//! ```rust +//! use std::io::Error; +//! use std::sync::Arc; +//! use std::sync::atomic::{AtomicBool, Ordering}; +//! +//! use signal_hook::consts::signal::*; +//! use signal_hook::flag as signal_flag; +//! +//! fn main() -> Result<(), Error> { +//! // We start with true, to load the configuration in the very first iteration too. +//! let reload = Arc::new(AtomicBool::new(true)); +//! let term = Arc::new(AtomicBool::new(false)); +//! # #[cfg(not(windows))] +//! signal_flag::register(SIGHUP, Arc::clone(&reload))?; +//! signal_flag::register(SIGINT, Arc::clone(&term))?; +//! signal_flag::register(SIGTERM, Arc::clone(&term))?; +//! # #[cfg(not(windows))] +//! signal_flag::register(SIGQUIT, Arc::clone(&term))?; +//! while !term.load(Ordering::Relaxed) { +//! // Using swap here, not load, to reset it back to false once it is reloaded. +//! if reload.swap(false, Ordering::Relaxed) { +//! // Reload the config here +//! # +//! # // Hiden hack to make the example terminate when run as doc-test. Not part of the +//! # // real code. +//! # term.store(true, Ordering::Relaxed); +//! } +//! // Serve one request +//! } +//! Ok(()) +//! } +//! ``` + +use std::io::Error; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; + +use libc::{c_int, EINVAL}; + +use crate::{low_level, SigId}; + +/// Registers an action to set the flag to `true` whenever the given signal arrives. +/// +/// # Panics +/// +/// If the signal is one of the forbidden. +pub fn register(signal: c_int, flag: Arc<AtomicBool>) -> Result<SigId, Error> { + // We use SeqCst for two reasons: + // * Signals should not come very often, so the performance does not really matter. + // * We promise the order of actions, but setting different atomics with Relaxed or similar + // would not guarantee the effective order. + unsafe { low_level::register(signal, move || flag.store(true, Ordering::SeqCst)) } +} + +/// Registers an action to set the flag to the given value whenever the signal arrives. +pub fn register_usize(signal: c_int, flag: Arc<AtomicUsize>, value: usize) -> Result<SigId, Error> { + unsafe { low_level::register(signal, move || flag.store(value, Ordering::SeqCst)) } +} + +/// Terminate the application on a signal if the given condition is true. +/// +/// This can be used for different use cases. One of them (with the condition being always true) is +/// just unconditionally terminate on the given signal. +/// +/// Another is being able to turn on and off the behaviour by the shared flag. +/// +/// The last one is handling double CTRL+C ‒ if the user presses CTRL+C, we would like to start a +/// graceful shutdown. But if anything ever gets stuck in the shutdown, second CTRL+C (or other +/// such termination signal) should terminate the application without further delay. +/// +/// To do that, one can combine this with [`register`]. On the first run, the flag is `false` and +/// this doesn't terminate. But then the flag is set to true during the first run and „arms“ the +/// shutdown on the second run. Note that it matters in which order the actions are registered (the +/// shutdown must go first). And yes, this also allows asking the user „Do you want to terminate“ +/// and disarming the abrupt shutdown if the user answers „No“. +/// +/// # Panics +/// +/// If the signal is one of the forbidden. +pub fn register_conditional_shutdown( + signal: c_int, + status: c_int, + condition: Arc<AtomicBool>, +) -> Result<SigId, Error> { + let action = move || { + if condition.load(Ordering::SeqCst) { + low_level::exit(status); + } + }; + unsafe { low_level::register(signal, action) } +} + +/// Conditionally runs an emulation of the default action on the given signal. +/// +/// If the provided condition is true at the time of invoking the signal handler, the equivalent of +/// the default action of the given signal is run. It is a bit similar to +/// [`register_conditional_shutdown`], except that it doesn't terminate for non-termination +/// signals, it runs their default handler. +/// +/// # Panics +/// +/// If the signal is one of the forbidden +/// +/// # Errors +/// +/// Similarly to the [`emulate_default_handler`][low_level::emulate_default_handler] function, this +/// one looks the signal up in a table. If it is unknown, an error is returned. +/// +/// Additionally to that, any errors that can be caused by a registration of a handler can happen +/// too. +pub fn register_conditional_default( + signal: c_int, + condition: Arc<AtomicBool>, +) -> Result<SigId, Error> { + // Verify we know about this particular signal. + low_level::signal_name(signal).ok_or_else(|| Error::from_raw_os_error(EINVAL))?; + let action = move || { + if condition.load(Ordering::SeqCst) { + let _ = low_level::emulate_default_handler(signal); + } + }; + unsafe { low_level::register(signal, action) } +} + +#[cfg(test)] +mod tests { + use std::sync::atomic; + use std::time::{Duration, Instant}; + + use super::*; + use crate::consts::signal::*; + + fn self_signal() { + #[cfg(not(windows))] + const SIG: c_int = SIGUSR1; + #[cfg(windows)] + const SIG: c_int = SIGTERM; + crate::low_level::raise(SIG).unwrap(); + } + + fn wait_flag(flag: &AtomicBool) -> bool { + let start = Instant::now(); + while !flag.load(Ordering::Relaxed) { + // Replaced by hint::spin_loop, but we want to support older compiler + #[allow(deprecated)] + atomic::spin_loop_hint(); + if Instant::now() - start > Duration::from_secs(1) { + // We reached a timeout and nothing happened yet. + // In theory, using timeouts for thread-synchronization tests is wrong, but a + // second should be enough in practice. + return false; + } + } + true + } + + #[test] + fn register_unregister() { + // When we register the action, it is active. + let flag = Arc::new(AtomicBool::new(false)); + #[cfg(not(windows))] + let signal = register(SIGUSR1, Arc::clone(&flag)).unwrap(); + #[cfg(windows)] + let signal = register(crate::SIGTERM, Arc::clone(&flag)).unwrap(); + self_signal(); + assert!(wait_flag(&flag)); + // But stops working after it is unregistered. + assert!(crate::low_level::unregister(signal)); + flag.store(false, Ordering::Relaxed); + self_signal(); + assert!(!wait_flag(&flag)); + // And the unregistration actually dropped its copy of the Arc + assert_eq!(1, Arc::strong_count(&flag)); + } + + // The shutdown is tested in tests/shutdown.rs +} |