Skip to main content

iroh_http_core/http/server/
handle.rs

1//! `ServeHandle` — the join handle / shutdown switch returned by
2//! [`crate::http::server::serve_with_events`].
3//!
4//! Split out of `mod.rs` per Slice C.7 of #182.
5
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::{Arc, Mutex};
8
9/// Stable identity of one serve cycle on an endpoint.
10///
11/// The identity, rather than a separately-mutated generation pair, lets the
12/// endpoint distinguish an adapter confirming the current handle from a late
13/// hand-off of a handle that has already been replaced.
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub(crate) struct ServeToken(u64);
16
17impl ServeToken {
18    pub(crate) fn new(value: u64) -> Self {
19        Self(value)
20    }
21
22    pub(crate) fn get(self) -> u64 {
23        self.0
24    }
25}
26
27struct ServeHandleInner {
28    token: ServeToken,
29    /// Installed immediately after the endpoint registers this cycle. Keeping
30    /// the join in a shared slot lets `stop_serve` win the race before the task
31    /// is spawned without losing the stop request.
32    join: Mutex<Option<tokio::task::JoinHandle<()>>>,
33    abort_requested: AtomicBool,
34    shutdown_requested: AtomicBool,
35    shutdown_notify: Arc<tokio::sync::Notify>,
36    /// Set to `true` and paired with `close_connections` to force the accept
37    /// loop's active per-connection tasks to close their QUIC connections.
38    close_flag: Arc<AtomicBool>,
39    /// Woken to tell active connection tasks to re-check `close_flag`.
40    close_connections: Arc<tokio::sync::Notify>,
41    drain_timeout: std::time::Duration,
42    /// Resolves to `true` once the serve task has fully exited.
43    done_rx: tokio::sync::watch::Receiver<bool>,
44}
45
46/// Control handle for one endpoint serve cycle.
47///
48/// Clones control the same cycle and carry the same internal token. The common
49/// server path stores one clone in the endpoint before returning another to an
50/// adapter, making adapter registration an idempotent confirmation.
51#[derive(Clone)]
52pub struct ServeHandle {
53    inner: Arc<ServeHandleInner>,
54}
55
56impl ServeHandle {
57    pub(super) fn pending(
58        token: ServeToken,
59        shutdown_notify: Arc<tokio::sync::Notify>,
60        close_flag: Arc<AtomicBool>,
61        close_connections: Arc<tokio::sync::Notify>,
62        drain_timeout: std::time::Duration,
63        done_rx: tokio::sync::watch::Receiver<bool>,
64    ) -> Self {
65        Self {
66            inner: Arc::new(ServeHandleInner {
67                token,
68                join: Mutex::new(None),
69                abort_requested: AtomicBool::new(false),
70                shutdown_requested: AtomicBool::new(false),
71                shutdown_notify,
72                close_flag,
73                close_connections,
74                drain_timeout,
75                done_rx,
76            }),
77        }
78    }
79
80    /// Attach the spawned accept task to a cycle that is already registered.
81    /// If an immediate close raced ahead, abort the task before it can run.
82    pub(super) fn attach_join(&self, join: tokio::task::JoinHandle<()>) {
83        let mut slot = self
84            .inner
85            .join
86            .lock()
87            .unwrap_or_else(|error| error.into_inner());
88        if self.inner.abort_requested.load(Ordering::Acquire) {
89            join.abort();
90        } else {
91            *slot = Some(join);
92        }
93    }
94
95    pub(crate) fn token(&self) -> ServeToken {
96        self.inner.token
97    }
98
99    pub(crate) fn is_same_cycle(&self, other: &Self) -> bool {
100        self.token() == other.token()
101    }
102
103    pub(crate) fn is_shutdown_requested(&self) -> bool {
104        self.inner.shutdown_requested.load(Ordering::Acquire)
105    }
106
107    /// Gracefully stop the serve loop: stop accepting new connections, drain
108    /// in-flight requests (up to `drain_timeout`), then close the remaining
109    /// active connections so no request is served after the loop has stopped.
110    pub fn shutdown(&self) {
111        self.inner.shutdown_requested.store(true, Ordering::Release);
112        self.inner.shutdown_notify.notify_one();
113    }
114
115    /// Stop the accept loop **and** force every active connection closed
116    /// *immediately*, without waiting for the graceful drain.
117    ///
118    /// Unlike [`shutdown`], which drains in-flight requests before closing
119    /// connections, this severs the connections up front so remote peers must
120    /// reconnect right away. Used when a serve loop is being *replaced* on the
121    /// same endpoint (#336).
122    pub fn shutdown_and_close(&self) {
123        self.inner.shutdown_requested.store(true, Ordering::Release);
124        self.inner.close_flag.store(true, Ordering::Release);
125        self.inner.shutdown_notify.notify_one();
126        self.inner.close_connections.notify_waiters();
127    }
128
129    pub async fn drain(self) {
130        self.shutdown();
131
132        let join = self
133            .inner
134            .join
135            .lock()
136            .unwrap_or_else(|error| error.into_inner())
137            .take();
138        if let Some(join) = join {
139            let _ = join.await;
140        } else {
141            // `close()` can race the short register-before-spawn window. The
142            // stored Notify permit stops the task when it starts; wait on its
143            // completion signal when there is not a join handle yet.
144            let mut done = self.subscribe_done();
145            let _ = done.wait_for(|value| *value).await;
146        }
147    }
148
149    pub fn abort(&self) {
150        self.inner.abort_requested.store(true, Ordering::Release);
151        if let Some(join) = self
152            .inner
153            .join
154            .lock()
155            .unwrap_or_else(|error| error.into_inner())
156            .as_ref()
157        {
158            join.abort();
159        }
160    }
161
162    pub fn drain_timeout(&self) -> std::time::Duration {
163        self.inner.drain_timeout
164    }
165
166    /// Subscribe to the serve-loop-done signal.
167    ///
168    /// The returned receiver resolves (changes to `true`) once the serve task
169    /// has fully exited, including the drain phase.
170    pub fn subscribe_done(&self) -> tokio::sync::watch::Receiver<bool> {
171        self.inner.done_rx.clone()
172    }
173}