Skip to main content

tokio_graceful_shutdown/
lib.rs

1//! This crate provides utility functions to perform a graceful shutdown on tokio-rs based services.
2//!
3//! Specifically, it provides:
4//!
5//! - Listening for shutdown requests from within subsystems
6//! - Manual shutdown initiation from within subsystems
7//! - Automatic shutdown on
8//!     - SIGINT/SIGTERM/Ctrl+C
9//!     - Subsystem failure
10//!     - Subsystem panic
11//! - Clean shutdown procedure with timeout and error propagation
12//! - Subsystem nesting
13//! - Partial shutdown of a selected subsystem tree
14//!
15//! # Example
16//!
17//! This example shows a minimal example of how to launch an asynchronous subsystem with the help of this crate.
18//!
19//! It contains a countdown subsystem that will end the program after 5 seconds.
20//! During the countdown, the program will react to Ctrl-C/SIGINT/SIGTERM and will cancel the countdown task accordingly.
21//!
22//! ```
23//! use miette::Result;
24//! use tokio_graceful_shutdown::{SubsystemBuilder, SubsystemHandle, Toplevel};
25//! use tokio::time::{sleep, Duration};
26//!
27//! async fn countdown() {
28//!     for i in (1..=5).rev() {
29//!         tracing::info!("Shutting down in: {}", i);
30//!         sleep(Duration::from_millis(1000)).await;
31//!     }
32//! }
33//!
34//! async fn countdown_subsystem(subsys: &mut SubsystemHandle) -> Result<()> {
35//!     tokio::select! {
36//!         _ = subsys.on_shutdown_requested() => {
37//!             tracing::info!("Countdown cancelled.");
38//!         },
39//!         _ = countdown() => {
40//!             subsys.request_shutdown();
41//!         }
42//!     };
43//!
44//!     Ok(())
45//! }
46//!
47//! #[tokio::main]
48//! async fn main() -> Result<()> {
49//!     // Init logging
50//!     tracing_subscriber::fmt()
51//!         .with_max_level(tracing::Level::TRACE)
52//!         .init();
53//!
54//!     // Setup and execute subsystem tree
55//!     Toplevel::new(async |s: &mut SubsystemHandle| {
56//!         s.start(SubsystemBuilder::new("Countdown", countdown_subsystem));
57//!     })
58//!     .catch_signals()
59//!     .handle_shutdown_requests(Duration::from_millis(1000))
60//!     .await
61//!     .map_err(Into::into)
62//! }
63//! ```
64//!
65//!
66//! The [`Toplevel`] object represents the root object of the subsystem tree
67//! and is the main entry point of how to interact with this crate.
68//! Creating a [`Toplevel`] object initially spawns a simple subsystem, which can then
69//! spawn further subsystems recursively.
70//!
71//! The [`catch_signals()`](Toplevel::catch_signals) method signals the `Toplevel` object to listen for SIGINT/SIGTERM/Ctrl+C and initiate a shutdown thereafter.
72//!
73//! [`handle_shutdown_requests()`](Toplevel::handle_shutdown_requests) is the final and most important method of `Toplevel`. It idles until the program enters the shutdown mode. Then, it collects all the return values of the subsystems, determines the global error state and makes sure the shutdown happens within the given timeout.
74//! Lastly, it returns an error value that can be directly used as a return code for `main()`.
75//!
76//! Further, the way to register and start a new submodule is to provide
77//! a submodule function/lambda to [`SubsystemHandle::start`].
78//! If additional arguments shall to be provided to the submodule, it is necessary to create
79//! a submodule `struct`. Further details can be seen in the `examples` directory of the repository.
80//!
81//! Finally, you can see the [`SubsystemHandle`] object that gets provided to the subsystem.
82//! It is the main way of the subsystem to communicate with this crate.
83//! It enables the subsystem to start nested subsystems, to react to shutdown requests or
84//! to initiate a shutdown.
85//!
86
87#![deny(unreachable_pub)]
88#![deny(missing_docs)]
89#![doc(
90    issue_tracker_base_url = "https://github.com/Finomnis/tokio-graceful-shutdown/issues",
91    test(no_crate_inject, attr(deny(warnings))),
92    test(attr(allow(dead_code)))
93)]
94
95type BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;
96
97/// A collection of traits a custom error has to fulfill in order to be
98/// usable as the `ErrType` of [Toplevel].
99pub trait ErrTypeTraits:
100    std::fmt::Debug + std::fmt::Display + 'static + Send + Sync + Sized
101{
102}
103impl<T> ErrTypeTraits for T where
104    T: std::fmt::Debug + std::fmt::Display + 'static + Send + Sync + Sized
105{
106}
107
108/// An async function that can be used as a subsystem.
109///
110/// Note: External users should not implement this trait directly.
111/// Prefer passing `async fn` or async closures; this trait exists to
112/// model those in the public API and may evolve.
113pub trait AsyncSubsysFn<A, O>: Send + FnOnce(A) -> Self::Fut {
114    /// The produced subsystem future
115    type Fut: Future<Output = O> + Send;
116}
117
118// This trick allows us to generate a “FnOnce”-like bound with only one lifetime parameter,
119// so that functions which capture the input argument’s lifetime in their output
120// (i.e., async functions) can meet the bound.
121impl<A, O, Out, F> AsyncSubsysFn<A, O> for F
122where
123    Out: Future<Output = O> + Send,
124    F: Send + FnOnce(A) -> Out,
125{
126    type Fut = Out;
127}
128
129pub mod errors;
130
131mod error_action;
132mod future_ext;
133mod into_subsystem;
134mod runner;
135mod signal_handling;
136mod subsystem;
137mod tokio_task;
138mod toplevel;
139mod utils;
140
141pub use error_action::ErrorAction;
142pub use future_ext::FutureExt;
143pub use into_subsystem::IntoSubsystem;
144pub use subsystem::NestedSubsystem;
145pub use subsystem::SubsystemBuilder;
146pub use subsystem::SubsystemFinishedFuture;
147pub use subsystem::SubsystemHandle;
148pub use toplevel::Toplevel;