tokio_shutdown/
lib.rs

1//! # Tokio Shutdown
2//!
3//! Tiny crate that allows to wait for a stop signal across multiple threads. Helpful mostly in
4//! server applications that run indefinitely and need a signal for graceful shutdowns.
5//!
6//! # Examples
7//!
8//! This example installs the global shutdown handler and will print a message once a single is
9//! received. For demonstration purposes it creates other tasks that wait for shutdown as well.
10//!
11//! ```no_run
12//! use tokio_shutdown::Shutdown;
13//!
14//! #[tokio::main(flavor = "current_thread")]
15//! async fn main() {
16//!     let shutdown = Shutdown::new().expect("shutdown creation works on first call");
17//!
18//!     // Pass a copy of the shutdown handler to another task.
19//!     // Clones of `Shutdown` are cheap.
20//!     let clone = shutdown.clone();
21//!     tokio::spawn(async move {
22//!         clone.handle().await;
23//!         println!("task 1 shutting down");
24//!     });
25//!
26//!     // Alternatively, pass a new handle to the new task.
27//!     // Both this and the above way work, choose whatever works best for your use case.
28//!     let handle = shutdown.handle();
29//!     tokio::spawn(async move {
30//!         handle.await;
31//!         println!("task 2 shutting down");
32//!     });
33//!
34//!     shutdown.handle().await;
35//!     println!("application shutting down");
36//! }
37//! ```
38//!
39//! Please have a look at the examples directory of this project for further usage instructions.
40
41#![forbid(unsafe_code)]
42#![deny(rust_2018_idioms, clippy::all, clippy::pedantic)]
43
44use std::{
45    error::Error,
46    fmt::{self, Display},
47    future::Future,
48    sync::atomic::{AtomicBool, Ordering},
49};
50
51use tokio::{signal, sync::watch};
52
53/// Error that occurs when the [`Shutdown::new`] function is called more than once in a process
54/// lifetime.
55#[derive(Debug, PartialEq, Eq)]
56pub struct AlreadyCreatedError;
57
58impl Error for AlreadyCreatedError {}
59
60impl Display for AlreadyCreatedError {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        f.write_str("shutdown handler already created")
63    }
64}
65
66static CREATED: AtomicBool = AtomicBool::new(false);
67
68/// The global shutdown handler for an application. It can be cloned cheaply wherever needed.
69///
70/// New handles can be created with the [`handle`](Self::handle) function, which creates futures
71/// that will complete once a shutdown signal is received.
72#[derive(Clone)]
73pub struct Shutdown {
74    receiver: watch::Receiver<()>,
75}
76
77impl Shutdown {
78    /// Create a new shutdown handle. This can only be called once per application instance.
79    ///
80    /// Signal handles can only be registered once for the duration of the entire process and
81    /// creating another shutdown handler would break the previous one without notice.
82    ///
83    /// # Errors
84    ///
85    /// If this function is called more than once during the lifetime of a process, an error will be
86    /// returned.
87    pub fn new() -> Result<Shutdown, AlreadyCreatedError> {
88        if (CREATED).swap(true, Ordering::SeqCst) {
89            return Err(AlreadyCreatedError);
90        }
91
92        let (tx, rx) = watch::channel(());
93        let handle = register_handlers();
94
95        tokio::spawn(async move {
96            handle.await;
97            tx.send(()).ok();
98        });
99
100        Ok(Self { receiver: rx })
101    }
102
103    /// Create a new handle that can be awaited on. The future will complete once a shutdown signal
104    /// is received.
105    pub fn handle(&self) -> impl Future<Output = ()> {
106        let mut rx = self.receiver.clone();
107
108        async move {
109            rx.changed().await.ok();
110        }
111    }
112}
113
114fn register_handlers() -> impl Future<Output = ()> {
115    let ctrl_c = async {
116        signal::ctrl_c()
117            .await
118            .expect("failed to install Ctrl+C handler");
119    };
120
121    #[cfg(unix)]
122    let terminate = async {
123        signal::unix::signal(signal::unix::SignalKind::terminate())
124            .expect("failed to install signal handler")
125            .recv()
126            .await;
127    };
128
129    #[cfg(not(unix))]
130    let terminate = std::future::pending::<()>();
131
132    async {
133        tokio::select! {
134            () = ctrl_c => {},
135            () = terminate => {},
136        }
137
138        #[cfg(feature = "tracing")]
139        tracing::info!("shutdown signal received");
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[tokio::test]
148    async fn fail_create_two_instances() {
149        assert!(Shutdown::new().is_ok());
150        assert_eq!(Some(AlreadyCreatedError), Shutdown::new().err());
151    }
152}