Skip to main content

actix_server/
shutdown.rs

1use std::future::pending;
2
3use tokio::sync::watch;
4
5/// Notification that the server has started a graceful shutdown.
6///
7/// The signal remains notified after graceful shutdown starts. A listener that calls
8/// [`notified`](Self::notified) or clones this signal after that point is notified immediately.
9/// A forced shutdown does not notify this signal.
10///
11/// # Server State
12///
13/// When this signal is notified, the [`Server`](crate::Server) has accepted a graceful shutdown
14/// command, but shutdown is not complete. The server sends this notification before it tells the
15/// accept loop and workers to stop. Therefore, a listener can briefly overlap with connection
16/// acceptance and normal worker operation.
17///
18/// Very shortly after notification, the server stops accepting connections and tells each worker to
19/// stop its services. Active service futures can continue until they finish or the
20/// [`ServerBuilder::shutdown_timeout`](crate::ServerBuilder::shutdown_timeout) expires.
21///
22/// # Use Cases
23///
24/// Connection-oriented services can listen to this signal to coordinate their own drain process.
25/// For example, a protocol dispatcher can:
26///
27/// - close an idle persistent connection;
28/// - stop accepting new logical requests on an active connection;
29/// - let the current request or protocol operation finish; and
30/// - start a protocol-specific close handshake or flush buffered data.
31///
32/// This signal does not replace the worker shutdown timeout. A listener must still finish its
33/// service future for the worker to complete a graceful shutdown.
34#[derive(Clone, Debug)]
35pub struct GracefulShutdownSignal {
36    /// Receiver retained at the pre-shutdown version so each clone observes the shutdown update.
37    rx: watch::Receiver<()>,
38}
39
40impl GracefulShutdownSignal {
41    pub(crate) fn new(rx: watch::Receiver<()>) -> Self {
42        Self { rx }
43    }
44
45    /// Resolves when the server starts a graceful shutdown, or immediately if graceful shutdown
46    /// has already started.
47    pub async fn notified(&self) {
48        let mut rx = self.rx.clone();
49
50        if rx.changed().await.is_err() {
51            pending::<()>().await;
52        }
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use std::time::Duration;
59
60    use actix_rt::time::timeout;
61
62    use super::GracefulShutdownSignal;
63
64    #[actix_rt::test]
65    async fn set_signal_notifies_listener() {
66        let (tx, rx) = tokio::sync::watch::channel(());
67
68        let signal = GracefulShutdownSignal::new(rx);
69
70        timeout(Duration::from_millis(100), signal.notified())
71            .await
72            .expect_err("signal notified listener before shutdown");
73
74        tx.send_replace(());
75
76        timeout(Duration::from_millis(100), signal.notified())
77            .await
78            .expect("set signal did not notify listener");
79        timeout(Duration::from_millis(100), signal.notified())
80            .await
81            .expect("set signal did not notify later listener");
82    }
83
84    #[actix_rt::test]
85    async fn closed_unset_signal_does_not_notify_listener() {
86        let (tx, rx) = tokio::sync::watch::channel(());
87        let signal = GracefulShutdownSignal::new(rx);
88
89        drop(tx);
90
91        assert!(timeout(Duration::from_millis(10), signal.notified())
92            .await
93            .is_err());
94    }
95}