1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use tokio::sync::Notify;
const RUNNING: u8 = 0;
const GRACEFUL_SHUTDOWN: u8 = 1;
const ABORT: u8 = 2;
/// Controls the lifetime of the transport connection serving a request.
///
/// A control is shared by every request multiplexed over the same connection.
/// Shutting down an HTTP/2 or HTTP/3 connection therefore also affects its
/// other in-flight request streams.
#[derive(Clone)]
pub struct ConnCtrl {
inner: Arc<Inner>,
}
struct Inner {
state: AtomicU8,
notify: Notify,
relax: AtomicBool,
}
impl Default for ConnCtrl {
fn default() -> Self {
Self::new()
}
}
impl Debug for ConnCtrl {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("ConnCtrl")
.field("state", &self.state())
.finish()
}
}
impl ConnCtrl {
/// Creates a connection control in the running state.
#[must_use]
pub fn new() -> Self {
Self {
inner: Arc::new(Inner {
state: AtomicU8::new(RUNNING),
notify: Notify::new(),
relax: AtomicBool::new(false),
}),
}
}
/// Stops accepting new requests and lets accepted requests finish.
///
/// A later call to [`abort`](Self::abort) escalates graceful shutdown to an
/// immediate abort.
pub fn graceful_shutdown(&self) {
if self
.inner
.state
.compare_exchange(
RUNNING,
GRACEFUL_SHUTDOWN,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
self.inner.notify.notify_waiters();
}
}
/// Immediately aborts the underlying transport connection.
///
/// The server will abruptly close the connection without sending any response.
/// This causes clients to encounter errors such as `curl: (52) Empty reply from server`.
///
/// Aborting the connection immediately frees up system resources allocated to the
/// request flow. This forceful teardown should **only** be used in critical scenarios,
/// such as mitigating active attacks, where maintaining the connection poses a security risk.
pub fn abort(&self) {
if self.inner.state.swap(ABORT, Ordering::AcqRel) != ABORT {
self.inner.notify.notify_waiters();
}
}
/// Returns `true` after graceful shutdown has been requested.
#[must_use]
pub fn is_graceful_shutdown(&self) -> bool {
self.inner.state.load(Ordering::Acquire) == GRACEFUL_SHUTDOWN
}
/// Returns `true` after immediate abort has been requested.
#[must_use]
pub fn is_aborted(&self) -> bool {
self.inner.state.load(Ordering::Acquire) == ABORT
}
/// Suspends the transport idle and write-stall fuse timeouts for this
/// connection.
///
/// The fuse [`connection_idle_timeout`](crate::fuse::FuseConfig::connection_idle_timeout)
/// and [`write_stall_timeout`](crate::fuse::FuseConfig::write_stall_timeout) exist to
/// close connections that stall mid-request. Long-lived protocols built on top of an HTTP
/// upgrade — WebSocket, or a hand-rolled tunnel — legitimately spend long stretches with no
/// transport activity and would otherwise trip those timers. A handler that hands the
/// connection off to such a protocol should call this to keep the transport open.
///
/// This does not affect [`graceful_shutdown`](Self::graceful_shutdown) or
/// [`abort`](Self::abort); an aborted connection is still torn down.
pub fn relax_timeouts(&self) {
// A standalone flag with no other memory to order against; relaxed is enough,
// and it is read on the transport poll path where we avoid needless fences.
self.inner.relax.store(true, Ordering::Relaxed);
}
/// Returns `true` once [`relax_timeouts`](Self::relax_timeouts) has been called.
#[must_use]
pub fn is_relaxed(&self) -> bool {
self.inner.relax.load(Ordering::Relaxed)
}
pub(crate) fn state(&self) -> ConnState {
match self.inner.state.load(Ordering::Acquire) {
GRACEFUL_SHUTDOWN => ConnState::GracefulShutdown,
ABORT => ConnState::Abort,
_ => ConnState::Running,
}
}
#[cfg(any(feature = "http1", feature = "http2", feature = "quinn", test))]
pub(crate) async fn notified(&self) -> ConnState {
self.wait_for_state(false).await
}
#[cfg(any(feature = "http1", feature = "http2", feature = "quinn", test))]
pub(crate) async fn aborted(&self) -> ConnState {
self.wait_for_state(true).await
}
#[cfg(any(feature = "http1", feature = "http2", feature = "quinn", test))]
async fn wait_for_state(&self, abort_only: bool) -> ConnState {
loop {
// Register this waiter before reading the state. This closes the
// race where a transition could otherwise occur between the state
// check and waiter registration. `Notify` keeps every registered
// waiter and `notify_waiters` broadcasts to all of them.
let notified = self.inner.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
let state = self.state();
if state == ConnState::Abort || (!abort_only && state == ConnState::GracefulShutdown) {
return state;
}
notified.await;
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ConnState {
Running,
GracefulShutdown,
Abort,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn abort_wakes_waiter() {
let ctrl = ConnCtrl::new();
let signal = ctrl.clone();
let waiter = tokio::spawn(async move { signal.notified().await });
ctrl.abort();
assert_eq!(waiter.await.unwrap(), ConnState::Abort);
}
#[test]
fn abort_escalates_graceful_shutdown() {
let ctrl = ConnCtrl::new();
ctrl.graceful_shutdown();
assert!(ctrl.is_graceful_shutdown());
ctrl.abort();
assert!(ctrl.is_aborted());
}
#[tokio::test]
async fn abort_waiter_ignores_graceful_shutdown() {
let ctrl = ConnCtrl::new();
ctrl.graceful_shutdown();
assert!(
tokio::time::timeout(std::time::Duration::from_millis(10), ctrl.aborted())
.await
.is_err()
);
ctrl.abort();
assert_eq!(ctrl.aborted().await, ConnState::Abort);
}
#[tokio::test]
async fn abort_wakes_every_waiter() {
let ctrl = ConnCtrl::new();
let connection_ctrl = ctrl.clone();
let connection_waiter = tokio::spawn(async move { connection_ctrl.notified().await });
let request_ctrl_a = ctrl.clone();
let request_waiter_a = tokio::spawn(async move { request_ctrl_a.aborted().await });
let request_ctrl_b = ctrl.clone();
let request_waiter_b = tokio::spawn(async move { request_ctrl_b.aborted().await });
// Give every future a chance to subscribe before broadcasting abort.
tokio::task::yield_now().await;
ctrl.abort();
let all_waiters = async {
assert_eq!(connection_waiter.await.unwrap(), ConnState::Abort);
assert_eq!(request_waiter_a.await.unwrap(), ConnState::Abort);
assert_eq!(request_waiter_b.await.unwrap(), ConnState::Abort);
};
tokio::time::timeout(std::time::Duration::from_secs(1), all_waiters)
.await
.expect("abort must wake every registered waiter");
}
}