Skip to main content

hyper_util/server/
graceful.rs

1//! Utility to gracefully shutdown a server.
2//!
3//! This module provides a [`GracefulShutdown`] type,
4//! which can be used to gracefully shutdown a server.
5//!
6//! See <https://github.com/hyperium/hyper-util/blob/master/examples/server_graceful.rs>
7//! for an example of how to use this.
8
9use std::{
10    fmt::{self, Debug},
11    pin::Pin,
12    task::{self, Poll},
13};
14
15use pin_project_lite::pin_project;
16use tokio::sync::watch;
17
18/// A graceful shutdown utility
19// Purposefully not `Clone`, see `watcher()` method for why.
20pub struct GracefulShutdown {
21    tx: watch::Sender<()>,
22}
23
24/// A watcher side of the graceful shutdown.
25///
26/// This type can only watch a connection, it cannot trigger a shutdown.
27///
28/// Call [`GracefulShutdown::watcher()`] to construct one of these.
29pub struct Watcher {
30    rx: watch::Receiver<()>,
31}
32
33impl GracefulShutdown {
34    /// Create a new graceful shutdown helper.
35    pub fn new() -> Self {
36        let (tx, _) = watch::channel(());
37        Self { tx }
38    }
39
40    /// Wrap a future for graceful shutdown watching.
41    pub fn watch<C: GracefulConnection>(
42        &self,
43        conn: C,
44    ) -> impl Future<Output = C::Output> + use<C> {
45        self.watcher().watch(conn)
46    }
47
48    /// Create an owned type that can watch a connection.
49    ///
50    /// This method allows created an owned type that can be sent onto another
51    /// task before calling [`Watcher::watch()`].
52    // Internal: this function exists because `Clone` allows footguns.
53    // If the `tx` were cloned (or the `rx`), race conditions can happens where
54    // one task starting a shutdown is scheduled and interwined with a task
55    // starting to watch a connection, and the "watch version" is one behind.
56    pub fn watcher(&self) -> Watcher {
57        let rx = self.tx.subscribe();
58        Watcher { rx }
59    }
60
61    /// Signal shutdown for all watched connections.
62    ///
63    /// This returns a `Future` which will complete once all watched
64    /// connections have shutdown.
65    pub async fn shutdown(self) {
66        let Self { tx } = self;
67
68        // signal all the watched futures about the change
69        let _ = tx.send(());
70        // and then wait for all of them to complete
71        tx.closed().await;
72    }
73
74    /// Returns the number of the watching connections.
75    pub fn count(&self) -> usize {
76        self.tx.receiver_count()
77    }
78}
79
80impl Debug for GracefulShutdown {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        f.debug_struct("GracefulShutdown").finish()
83    }
84}
85
86impl Default for GracefulShutdown {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92impl Watcher {
93    /// Wrap a future for graceful shutdown watching.
94    pub fn watch<C: GracefulConnection>(self, conn: C) -> impl Future<Output = C::Output> {
95        let Watcher { mut rx } = self;
96        GracefulConnectionFuture::new(conn, async move {
97            let _ = rx.changed().await;
98            // hold onto the rx until the watched future is completed
99            rx
100        })
101    }
102}
103
104impl Debug for Watcher {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        f.debug_struct("GracefulWatcher").finish()
107    }
108}
109
110pin_project! {
111    struct GracefulConnectionFuture<C, F: Future> {
112        #[pin]
113        conn: C,
114        #[pin]
115        cancel: F,
116        #[pin]
117        // If cancelled, this is held until the inner conn is done.
118        cancelled_guard: Option<F::Output>,
119    }
120}
121
122impl<C, F: Future> GracefulConnectionFuture<C, F> {
123    fn new(conn: C, cancel: F) -> Self {
124        Self {
125            conn,
126            cancel,
127            cancelled_guard: None,
128        }
129    }
130}
131
132impl<C, F: Future> Debug for GracefulConnectionFuture<C, F> {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        f.debug_struct("GracefulConnectionFuture").finish()
135    }
136}
137
138impl<C, F> Future for GracefulConnectionFuture<C, F>
139where
140    C: GracefulConnection,
141    F: Future,
142{
143    type Output = C::Output;
144
145    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
146        let mut this = self.project();
147        if this.cancelled_guard.is_none() {
148            if let Poll::Ready(guard) = this.cancel.poll(cx) {
149                this.cancelled_guard.set(Some(guard));
150                this.conn.as_mut().graceful_shutdown();
151            }
152        }
153        this.conn.poll(cx)
154    }
155}
156
157/// An internal utility trait as an umbrella target for all (hyper) connection
158/// types that the [`GracefulShutdown`] can watch.
159pub trait GracefulConnection: Future<Output = Result<(), Self::Error>> + private::Sealed {
160    /// The error type returned by the connection when used as a future.
161    type Error;
162
163    /// Start a graceful shutdown process for this connection.
164    fn graceful_shutdown(self: Pin<&mut Self>);
165}
166
167#[cfg(feature = "http1")]
168impl<I, B, S> GracefulConnection for hyper::server::conn::http1::Connection<I, S>
169where
170    S: hyper::service::HttpService<hyper::body::Incoming, ResBody = B>,
171    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
172    I: hyper::rt::Read + hyper::rt::Write + Unpin + 'static,
173    B: hyper::body::Body + 'static,
174    B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
175{
176    type Error = hyper::Error;
177
178    fn graceful_shutdown(self: Pin<&mut Self>) {
179        hyper::server::conn::http1::Connection::graceful_shutdown(self);
180    }
181}
182
183#[cfg(feature = "http2")]
184impl<I, B, S, E> GracefulConnection for hyper::server::conn::http2::Connection<I, S, E>
185where
186    S: hyper::service::HttpService<hyper::body::Incoming, ResBody = B>,
187    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
188    I: hyper::rt::Read + hyper::rt::Write + Unpin + 'static,
189    B: hyper::body::Body + 'static,
190    B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
191    E: hyper::rt::bounds::Http2ServerConnExec<S::Future, B>,
192{
193    type Error = hyper::Error;
194
195    fn graceful_shutdown(self: Pin<&mut Self>) {
196        hyper::server::conn::http2::Connection::graceful_shutdown(self);
197    }
198}
199
200#[cfg(feature = "server-auto")]
201impl<I, B, S, E> GracefulConnection for crate::server::conn::auto::Connection<'_, I, S, E>
202where
203    S: hyper::service::Service<http::Request<hyper::body::Incoming>, Response = http::Response<B>>,
204    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
205    S::Future: 'static,
206    I: hyper::rt::Read + hyper::rt::Write + Unpin + 'static,
207    B: hyper::body::Body + 'static,
208    B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
209    E: hyper::rt::bounds::Http2ServerConnExec<S::Future, B>,
210{
211    type Error = Box<dyn std::error::Error + Send + Sync>;
212
213    fn graceful_shutdown(self: Pin<&mut Self>) {
214        crate::server::conn::auto::Connection::graceful_shutdown(self);
215    }
216}
217
218#[cfg(feature = "server-auto")]
219impl<I, B, S, E> GracefulConnection
220    for crate::server::conn::auto::UpgradeableConnection<'_, I, S, E>
221where
222    S: hyper::service::Service<http::Request<hyper::body::Incoming>, Response = http::Response<B>>,
223    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
224    S::Future: 'static,
225    I: hyper::rt::Read + hyper::rt::Write + Unpin + Send + 'static,
226    B: hyper::body::Body + 'static,
227    B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
228    E: hyper::rt::bounds::Http2ServerConnExec<S::Future, B>,
229{
230    type Error = Box<dyn std::error::Error + Send + Sync>;
231
232    fn graceful_shutdown(self: Pin<&mut Self>) {
233        crate::server::conn::auto::UpgradeableConnection::graceful_shutdown(self);
234    }
235}
236
237mod private {
238    pub trait Sealed {}
239
240    #[cfg(feature = "http1")]
241    impl<I, B, S> Sealed for hyper::server::conn::http1::Connection<I, S>
242    where
243        S: hyper::service::HttpService<hyper::body::Incoming, ResBody = B>,
244        S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
245        I: hyper::rt::Read + hyper::rt::Write + Unpin + 'static,
246        B: hyper::body::Body + 'static,
247        B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
248    {
249    }
250
251    #[cfg(feature = "http1")]
252    impl<I, B, S> Sealed for hyper::server::conn::http1::UpgradeableConnection<I, S>
253    where
254        S: hyper::service::HttpService<hyper::body::Incoming, ResBody = B>,
255        S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
256        I: hyper::rt::Read + hyper::rt::Write + Unpin + 'static,
257        B: hyper::body::Body + 'static,
258        B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
259    {
260    }
261
262    #[cfg(feature = "http2")]
263    impl<I, B, S, E> Sealed for hyper::server::conn::http2::Connection<I, S, E>
264    where
265        S: hyper::service::HttpService<hyper::body::Incoming, ResBody = B>,
266        S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
267        I: hyper::rt::Read + hyper::rt::Write + Unpin + 'static,
268        B: hyper::body::Body + 'static,
269        B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
270        E: hyper::rt::bounds::Http2ServerConnExec<S::Future, B>,
271    {
272    }
273
274    #[cfg(feature = "server-auto")]
275    impl<I, B, S, E> Sealed for crate::server::conn::auto::Connection<'_, I, S, E>
276    where
277        S: hyper::service::Service<
278                http::Request<hyper::body::Incoming>,
279                Response = http::Response<B>,
280            >,
281        S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
282        S::Future: 'static,
283        I: hyper::rt::Read + hyper::rt::Write + Unpin + 'static,
284        B: hyper::body::Body + 'static,
285        B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
286        E: hyper::rt::bounds::Http2ServerConnExec<S::Future, B>,
287    {
288    }
289
290    #[cfg(feature = "server-auto")]
291    impl<I, B, S, E> Sealed for crate::server::conn::auto::UpgradeableConnection<'_, I, S, E>
292    where
293        S: hyper::service::Service<
294                http::Request<hyper::body::Incoming>,
295                Response = http::Response<B>,
296            >,
297        S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
298        S::Future: 'static,
299        I: hyper::rt::Read + hyper::rt::Write + Unpin + Send + 'static,
300        B: hyper::body::Body + 'static,
301        B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
302        E: hyper::rt::bounds::Http2ServerConnExec<S::Future, B>,
303    {
304    }
305}
306
307#[cfg(test)]
308mod test {
309    use super::*;
310    use pin_project_lite::pin_project;
311    use std::sync::Arc;
312    use std::sync::atomic::{AtomicUsize, Ordering};
313
314    pin_project! {
315        #[derive(Debug)]
316        struct DummyConnection<F> {
317            #[pin]
318            future: F,
319            shutdown_counter: Arc<AtomicUsize>,
320        }
321    }
322
323    impl<F> private::Sealed for DummyConnection<F> {}
324
325    impl<F: Future> GracefulConnection for DummyConnection<F> {
326        type Error = ();
327
328        fn graceful_shutdown(self: Pin<&mut Self>) {
329            self.shutdown_counter.fetch_add(1, Ordering::SeqCst);
330        }
331    }
332
333    impl<F: Future> Future for DummyConnection<F> {
334        type Output = Result<(), ()>;
335
336        fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
337            match self.project().future.poll(cx) {
338                Poll::Ready(_) => Poll::Ready(Ok(())),
339                Poll::Pending => Poll::Pending,
340            }
341        }
342    }
343
344    #[cfg(not(miri))]
345    #[tokio::test]
346    async fn test_graceful_shutdown_ok() {
347        let graceful = GracefulShutdown::new();
348        let shutdown_counter = Arc::new(AtomicUsize::new(0));
349        let (dummy_tx, _) = tokio::sync::broadcast::channel(1);
350
351        for i in 1..=3 {
352            let mut dummy_rx = dummy_tx.subscribe();
353            let shutdown_counter = shutdown_counter.clone();
354
355            let future = async move {
356                tokio::time::sleep(std::time::Duration::from_millis(i * 10)).await;
357                let _ = dummy_rx.recv().await;
358            };
359            let dummy_conn = DummyConnection {
360                future,
361                shutdown_counter,
362            };
363            let conn = graceful.watch(dummy_conn);
364            tokio::spawn(async move {
365                conn.await.unwrap();
366            });
367        }
368
369        assert_eq!(shutdown_counter.load(Ordering::SeqCst), 0);
370        let _ = dummy_tx.send(());
371
372        tokio::select! {
373            _ = tokio::time::sleep(std::time::Duration::from_millis(100)) => {
374                panic!("timeout")
375            },
376            _ = graceful.shutdown() => {
377                assert_eq!(shutdown_counter.load(Ordering::SeqCst), 3);
378            }
379        }
380    }
381
382    #[cfg(not(miri))]
383    #[tokio::test]
384    async fn test_graceful_shutdown_delayed_ok() {
385        let graceful = GracefulShutdown::new();
386        let shutdown_counter = Arc::new(AtomicUsize::new(0));
387
388        for i in 1..=3 {
389            let shutdown_counter = shutdown_counter.clone();
390
391            //tokio::time::sleep(std::time::Duration::from_millis(i * 5)).await;
392            let future = async move {
393                tokio::time::sleep(std::time::Duration::from_millis(i * 50)).await;
394            };
395            let dummy_conn = DummyConnection {
396                future,
397                shutdown_counter,
398            };
399            let conn = graceful.watch(dummy_conn);
400            tokio::spawn(async move {
401                conn.await.unwrap();
402            });
403        }
404
405        assert_eq!(shutdown_counter.load(Ordering::SeqCst), 0);
406
407        tokio::select! {
408            _ = tokio::time::sleep(std::time::Duration::from_millis(200)) => {
409                panic!("timeout")
410            },
411            _ = graceful.shutdown() => {
412                assert_eq!(shutdown_counter.load(Ordering::SeqCst), 3);
413            }
414        }
415    }
416
417    #[cfg(not(miri))]
418    #[tokio::test]
419    async fn test_graceful_shutdown_multi_per_watcher_ok() {
420        let graceful = GracefulShutdown::new();
421        let shutdown_counter = Arc::new(AtomicUsize::new(0));
422
423        for i in 1..=3 {
424            let shutdown_counter = shutdown_counter.clone();
425
426            let mut futures = Vec::new();
427            for u in 1..=i {
428                let future = tokio::time::sleep(std::time::Duration::from_millis(u * 50));
429                let dummy_conn = DummyConnection {
430                    future,
431                    shutdown_counter: shutdown_counter.clone(),
432                };
433                let conn = graceful.watch(dummy_conn);
434                futures.push(conn);
435            }
436            tokio::spawn(async move {
437                futures_util::future::join_all(futures).await;
438            });
439        }
440
441        assert_eq!(shutdown_counter.load(Ordering::SeqCst), 0);
442
443        tokio::select! {
444            _ = tokio::time::sleep(std::time::Duration::from_millis(200)) => {
445                panic!("timeout")
446            },
447            _ = graceful.shutdown() => {
448                assert_eq!(shutdown_counter.load(Ordering::SeqCst), 6);
449            }
450        }
451    }
452
453    #[cfg(not(miri))]
454    #[tokio::test]
455    async fn test_graceful_shutdown_timeout() {
456        let graceful = GracefulShutdown::new();
457        let shutdown_counter = Arc::new(AtomicUsize::new(0));
458
459        for i in 1..=3 {
460            let shutdown_counter = shutdown_counter.clone();
461
462            let future = async move {
463                if i == 1 {
464                    std::future::pending::<()>().await
465                } else {
466                    std::future::ready(()).await
467                }
468            };
469            let dummy_conn = DummyConnection {
470                future,
471                shutdown_counter,
472            };
473            let conn = graceful.watch(dummy_conn);
474            tokio::spawn(async move {
475                conn.await.unwrap();
476            });
477        }
478
479        assert_eq!(shutdown_counter.load(Ordering::SeqCst), 0);
480
481        tokio::select! {
482            _ = tokio::time::sleep(std::time::Duration::from_millis(100)) => {
483                assert_eq!(shutdown_counter.load(Ordering::SeqCst), 3);
484            },
485            _ = graceful.shutdown() => {
486                panic!("shutdown should not be completed: as not all our conns finish")
487            }
488        }
489    }
490}