Skip to main content

ullun/
shutdown.rs

1//! Shutdown signal and background tasks
2//!
3//! When the server is asked to stop (CTRL+C, SIGTERM, or the future given to
4//! `App::serve_with_shutdown` completes) it stops accepting connections, tells
5//! idle keep-alive connections to close, lets in-flight requests finish, and
6//! waits for [background tasks](crate::app::App::background), each at most
7//! `App::shutdown_grace` (default 10 s). Long-lived work such as SSE streams,
8//! WebSocket handlers and periodic jobs should watch [`Shutdown`] to end
9//! promptly instead of running into that limit.
10
11use std::time::Duration;
12use tokio::sync::watch;
13
14/// Resolves when the server begins shutting down
15#[derive(Clone)]
16pub struct Shutdown {
17    rx: watch::Receiver<bool>,
18    drain: watch::Receiver<bool>,
19}
20
21impl Shutdown {
22    pub(crate) fn new(rx: watch::Receiver<bool>) -> Self {
23        // no separate drain phase: draining begins together with shutdown
24        let (tx, drain) = watch::channel(false);
25        std::mem::forget(tx);
26        Self { rx, drain }
27    }
28
29    pub(crate) fn with_drain(rx: watch::Receiver<bool>, drain: watch::Receiver<bool>) -> Self {
30        Self { rx, drain }
31    }
32
33    /// A signal that never fires (requests built outside a running server)
34    pub fn never() -> Self {
35        let (tx, rx) = watch::channel(false);
36        // keep the sender alive for the process lifetime so `wait` pends instead of erroring
37        std::mem::forget(tx);
38        Self::new(rx)
39    }
40
41    /// True once the server was told to stop, which is up to
42    /// `App::shutdown_delay` before [`is_triggered`](Shutdown::is_triggered).
43    /// Readiness checks report "not ready" from here on so load balancers stop
44    /// sending traffic while the server still answers.
45    pub fn is_draining(&self) -> bool {
46        *self.drain.borrow() || self.is_triggered()
47    }
48
49    /// True once shutdown has begun
50    pub fn is_triggered(&self) -> bool {
51        *self.rx.borrow()
52    }
53
54    /// Completes when shutdown begins (immediately if it already has)
55    pub async fn wait(&self) {
56        let mut rx = self.rx.clone();
57        loop {
58            if *rx.borrow_and_update() {
59                return;
60            }
61            if rx.changed().await.is_err() {
62                // sender gone without a signal: the server is being torn down
63                return;
64            }
65        }
66    }
67
68    /// Sleep for `duration`; returns `false` if shutdown began first
69    ///
70    /// Designed for loops: `while shutdown.sleep(Duration::from_secs(30)).await { work().await }`.
71    pub async fn sleep(&self, duration: Duration) -> bool {
72        tokio::select! {
73            _ = tokio::time::sleep(duration) => !self.is_triggered(),
74            _ = self.wait() => false,
75        }
76    }
77}
78
79/// Handed to every task registered with `App::background`; cheap to clone into
80/// sub-tasks
81#[derive(Clone)]
82pub struct Context {
83    pub(crate) state: std::sync::Arc<crate::request::StateMap>,
84    shutdown: Shutdown,
85}
86
87impl Context {
88    pub(crate) fn new(state: std::sync::Arc<crate::request::StateMap>, shutdown: Shutdown) -> Self {
89        Self { state, shutdown }
90    }
91
92    /// Shared state registered with `App::state`
93    pub fn state<T: Send + Sync + 'static>(&self) -> crate::error::Result<&T> {
94        self.state.get::<T>().ok_or_else(|| {
95            crate::error::Error::internal(format!(
96                "State of type {} is not registered",
97                std::any::type_name::<T>()
98            ))
99        })
100    }
101
102    /// The shutdown signal; clone it into spawned sub-tasks
103    pub fn shutdown(&self) -> Shutdown {
104        self.shutdown.clone()
105    }
106
107    /// Shorthand for `shutdown().sleep(duration)`
108    pub async fn sleep(&self, duration: Duration) -> bool {
109        self.shutdown.sleep(duration).await
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[tokio::test]
118    async fn wait_sleep_and_trigger() {
119        let (tx, rx) = watch::channel(false);
120        let s = Shutdown::new(rx);
121        assert!(!s.is_triggered());
122        assert!(s.sleep(Duration::from_millis(5)).await);
123
124        let s2 = s.clone();
125        let waiter = tokio::spawn(async move { s2.wait().await });
126        tx.send(true).unwrap();
127        waiter.await.unwrap();
128        assert!(s.is_triggered());
129        // already triggered: returns at once, sleep reports the interruption
130        s.wait().await;
131        assert!(!s.sleep(Duration::from_secs(60)).await);
132    }
133
134    #[tokio::test]
135    async fn sleep_is_interrupted_by_the_signal() {
136        let (tx, rx) = watch::channel(false);
137        let s = Shutdown::new(rx);
138        let sleeper = tokio::spawn({
139            let s = s.clone();
140            async move { s.sleep(Duration::from_secs(60)).await }
141        });
142        tokio::time::sleep(Duration::from_millis(20)).await;
143        tx.send(true).unwrap();
144        assert!(!tokio::time::timeout(Duration::from_secs(5), sleeper)
145            .await
146            .unwrap()
147            .unwrap());
148    }
149
150    #[test]
151    fn draining_precedes_or_equals_shutdown() {
152        let (tx, rx) = watch::channel(false);
153        let (dtx, drx) = watch::channel(false);
154        let s = Shutdown::with_drain(rx, drx);
155        assert!(!s.is_draining());
156        dtx.send(true).unwrap();
157        assert!(s.is_draining() && !s.is_triggered());
158        tx.send(true).unwrap();
159        assert!(s.is_triggered());
160
161        // without a drain phase, draining starts with shutdown
162        let (tx, rx) = watch::channel(false);
163        let s = Shutdown::new(rx);
164        assert!(!s.is_draining());
165        tx.send(true).unwrap();
166        assert!(s.is_draining());
167    }
168
169    #[tokio::test]
170    async fn never_does_not_fire() {
171        let s = Shutdown::never();
172        assert!(!s.is_triggered());
173        assert!(tokio::time::timeout(Duration::from_millis(30), s.wait())
174            .await
175            .is_err());
176    }
177}