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}