1use std::future::Future;
14use std::pin::Pin;
15use std::task::{Context, Poll};
16use std::time::Duration;
17
18use bytes::Bytes;
19use http_body::{Body, Frame, SizeHint};
20use http_body_util::combinators::BoxBody;
21use http_body_util::BodyExt;
22
23use super::BoxError;
24
25#[derive(Debug)]
28pub struct IdleTimeoutError {
29 pub idle: Duration,
31}
32
33impl std::fmt::Display for IdleTimeoutError {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 write!(
36 f,
37 "idle timeout: no response body frame for {:?}",
38 self.idle
39 )
40 }
41}
42
43impl std::error::Error for IdleTimeoutError {}
44
45pub fn idle_timeout_body(
57 body: BoxBody<Bytes, BoxError>,
58 idle: Duration,
59) -> BoxBody<Bytes, BoxError> {
60 IdleTimeoutBody {
61 inner: body,
62 idle,
63 sleep: Box::pin(tokio::time::sleep(idle)),
64 }
65 .boxed()
66}
67
68struct IdleTimeoutBody {
69 inner: BoxBody<Bytes, BoxError>,
70 idle: Duration,
71 sleep: Pin<Box<tokio::time::Sleep>>,
72}
73
74impl Body for IdleTimeoutBody {
75 type Data = Bytes;
76 type Error = BoxError;
77
78 fn poll_frame(
79 self: Pin<&mut Self>,
80 cx: &mut Context<'_>,
81 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
82 let this = self.get_mut();
83
84 match Pin::new(&mut this.inner).poll_frame(cx) {
85 Poll::Ready(frame) => {
86 this.sleep
91 .as_mut()
92 .reset(tokio::time::Instant::now() + this.idle);
93 return Poll::Ready(frame);
94 }
95 Poll::Pending => {}
96 }
97
98 match this.sleep.as_mut().poll(cx) {
99 Poll::Ready(()) => {
100 tracing::warn!(
105 idle_ms = this.idle.as_millis() as u64,
106 "streamed response body idle timeout: no frame for {:?}, reaping the stream",
107 this.idle
108 );
109 Poll::Ready(Some(Err(Box::new(IdleTimeoutError { idle: this.idle }))))
110 }
111 Poll::Pending => Poll::Pending,
112 }
113 }
114
115 fn is_end_stream(&self) -> bool {
116 self.inner.is_end_stream()
117 }
118
119 fn size_hint(&self) -> SizeHint {
120 self.inner.size_hint()
121 }
122}
123
124pub fn body_holding(
130 body: BoxBody<Bytes, BoxError>,
131 guards: Vec<Box<dyn Send + 'static>>,
132) -> BoxBody<Bytes, BoxError> {
133 BodyHolding {
134 inner: body,
135 guards: std::sync::Mutex::new(guards),
136 }
137 .boxed()
138}
139
140struct BodyHolding {
141 inner: BoxBody<Bytes, BoxError>,
142 guards: std::sync::Mutex<Vec<Box<dyn Send + 'static>>>,
153}
154
155impl Body for BodyHolding {
156 type Data = Bytes;
157 type Error = BoxError;
158
159 fn poll_frame(
160 self: Pin<&mut Self>,
161 cx: &mut Context<'_>,
162 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
163 let this = self.get_mut();
164 match Pin::new(&mut this.inner).poll_frame(cx) {
165 Poll::Ready(None) => {
166 this.guards.get_mut().unwrap().clear();
167 Poll::Ready(None)
168 }
169 Poll::Ready(Some(Err(e))) => {
170 this.guards.get_mut().unwrap().clear();
171 Poll::Ready(Some(Err(e)))
172 }
173 other => other,
174 }
175 }
176
177 fn is_end_stream(&self) -> bool {
178 self.inner.is_end_stream()
179 }
180
181 fn size_hint(&self) -> SizeHint {
182 self.inner.size_hint()
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use std::collections::VecDeque;
190
191 struct ScriptedBody {
199 items: VecDeque<Duration>,
200 stall: bool,
201 pending: Option<Pin<Box<tokio::time::Sleep>>>,
202 }
203
204 impl ScriptedBody {
205 fn new(delays: Vec<Duration>, stall: bool) -> Self {
206 Self {
207 items: delays.into(),
208 stall,
209 pending: None,
210 }
211 }
212 }
213
214 impl Body for ScriptedBody {
215 type Data = Bytes;
216 type Error = BoxError;
217
218 fn poll_frame(
219 self: Pin<&mut Self>,
220 cx: &mut Context<'_>,
221 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
222 let this = self.get_mut();
223 if this.pending.is_none() {
227 match this.items.pop_front() {
228 Some(delay) => this.pending = Some(Box::pin(tokio::time::sleep(delay))),
229 None => {
230 return if this.stall {
231 Poll::Pending
232 } else {
233 Poll::Ready(None)
234 };
235 }
236 }
237 }
238 let sleep = this.pending.as_mut().unwrap();
239 match sleep.as_mut().poll(cx) {
240 Poll::Ready(()) => {
241 this.pending = None;
242 Poll::Ready(Some(Ok(Frame::data(Bytes::from_static(b"x")))))
243 }
244 Poll::Pending => Poll::Pending,
245 }
246 }
247 }
248
249 #[tokio::test]
255 async fn test_idle_timeout_reaps_a_silent_stream_as_an_error() {
256 let body = ScriptedBody::new(vec![Duration::ZERO], true);
257 let mut wrapped = idle_timeout_body(body.boxed(), Duration::from_millis(150));
258
259 let first = tokio::time::timeout(
261 Duration::from_millis(500),
262 std::future::poll_fn(|cx| Pin::new(&mut wrapped).poll_frame(cx)),
263 )
264 .await
265 .expect("first frame must arrive promptly");
266 assert!(matches!(first, Some(Ok(_))), "expected the first frame");
267
268 let second = tokio::time::timeout(
271 Duration::from_millis(800),
272 std::future::poll_fn(|cx| Pin::new(&mut wrapped).poll_frame(cx)),
273 )
274 .await
275 .expect("a silent stream must be reaped, not hang past the idle bound");
276 match second {
277 Some(Err(e)) => {
278 assert!(
279 e.downcast_ref::<IdleTimeoutError>().is_some(),
280 "expected an IdleTimeoutError, got: {e}"
281 );
282 }
283 other => panic!("expected the reap to error the stream, got: {other:?}"),
284 }
285 }
286
287 #[tokio::test]
295 async fn test_idle_timeout_lets_a_steady_stream_finish() {
296 let delays = vec![Duration::from_millis(50); 15];
297 let body = ScriptedBody::new(delays, false);
298 let wrapped = idle_timeout_body(body.boxed(), Duration::from_millis(500));
299
300 let collected = tokio::time::timeout(Duration::from_secs(3), wrapped.collect())
301 .await
302 .expect("a steady stream (every gap well under the idle bound) must not be reaped")
303 .expect("no error expected");
304
305 assert_eq!(collected.to_bytes().as_ref(), b"xxxxxxxxxxxxxxx");
306 }
307
308 struct DropGuard(std::sync::Arc<std::sync::atomic::AtomicUsize>);
309 impl Drop for DropGuard {
310 fn drop(&mut self) {
311 self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
312 }
313 }
314
315 #[tokio::test]
318 async fn test_body_holding_holds_guards_until_body_finishes() {
319 use std::sync::atomic::{AtomicUsize, Ordering};
320 use std::sync::Arc;
321
322 let dropped = Arc::new(AtomicUsize::new(0));
323 let body = ScriptedBody::new(vec![Duration::ZERO, Duration::ZERO], false);
324 let mut wrapped = body_holding(body.boxed(), vec![Box::new(DropGuard(dropped.clone()))]);
325
326 let _ = std::future::poll_fn(|cx| Pin::new(&mut wrapped).poll_frame(cx)).await;
328 assert_eq!(
329 dropped.load(Ordering::SeqCst),
330 0,
331 "guard released too early"
332 );
333
334 let _ = wrapped.collect().await.unwrap();
336 assert_eq!(
337 dropped.load(Ordering::SeqCst),
338 1,
339 "guard not released once the body finished"
340 );
341 }
342
343 #[tokio::test]
347 async fn test_body_holding_drops_guards_when_body_dropped_early() {
348 use std::sync::atomic::{AtomicUsize, Ordering};
349 use std::sync::Arc;
350
351 let dropped = Arc::new(AtomicUsize::new(0));
352 let body = ScriptedBody::new(vec![Duration::ZERO], true);
353 let wrapped = body_holding(body.boxed(), vec![Box::new(DropGuard(dropped.clone()))]);
354
355 assert_eq!(dropped.load(Ordering::SeqCst), 0);
356 drop(wrapped);
357 assert_eq!(
358 dropped.load(Ordering::SeqCst),
359 1,
360 "guard not released when the body was dropped early"
361 );
362 }
363
364 #[tokio::test]
368 async fn test_body_holding_drops_guards_on_body_error() {
369 use std::sync::atomic::{AtomicUsize, Ordering};
370 use std::sync::Arc;
371
372 let dropped = Arc::new(AtomicUsize::new(0));
373 let body = ScriptedBody::new(vec![Duration::ZERO], true);
374 let idled = idle_timeout_body(body.boxed(), Duration::from_millis(50));
375 let mut wrapped = body_holding(idled, vec![Box::new(DropGuard(dropped.clone()))]);
376
377 let _ = std::future::poll_fn(|cx| Pin::new(&mut wrapped).poll_frame(cx)).await;
379 assert_eq!(dropped.load(Ordering::SeqCst), 0);
380
381 let second = tokio::time::timeout(
384 Duration::from_millis(500),
385 std::future::poll_fn(|cx| Pin::new(&mut wrapped).poll_frame(cx)),
386 )
387 .await
388 .expect("idle reap must fire");
389 assert!(matches!(second, Some(Err(_))));
390 assert_eq!(
391 dropped.load(Ordering::SeqCst),
392 1,
393 "guard not released when the body ended with an error"
394 );
395 }
396}