|
3 | 3 | pub(crate) mod time; |
4 | 4 |
|
5 | 5 | use chrono::{DateTime, Local, Utc}; |
| 6 | +use futures::{FutureExt, future::Either}; |
6 | 7 | use once_cell::sync::Lazy; |
7 | 8 | use rand::{Rng, distr::Alphanumeric}; |
8 | 9 | use std::ops::ControlFlow; |
@@ -315,16 +316,22 @@ fn armable(duration: Option<Duration>) -> Option<Duration> { |
315 | 316 |
|
316 | 317 | /// [`tokio::time::timeout`] that waits forever instead of arming a timer |
317 | 318 | /// outside [`MAX_TIMER_DURATION`]. |
318 | | -pub(crate) async fn safe_timeout<F>( |
| 319 | +/// |
| 320 | +/// Not an `async fn` on purpose: an async wrapper's state machine holds `F` |
| 321 | +/// once as its argument and again inside the [`tokio::time::Timeout`] it |
| 322 | +/// builds, so every call has to copy the whole future between the two slots. |
| 323 | +/// Some of the futures passed here are kilobytes ([`crate::backend::pool::Pool::get`]), |
| 324 | +/// and that copy is large enough to show up as `memcpy` in profiles. |
| 325 | +pub(crate) fn safe_timeout<F>( |
319 | 326 | duration: Duration, |
320 | 327 | future: F, |
321 | | -) -> Result<F::Output, tokio::time::error::Elapsed> |
| 328 | +) -> impl Future<Output = Result<F::Output, tokio::time::error::Elapsed>> |
322 | 329 | where |
323 | 330 | F: Future, |
324 | 331 | { |
325 | 332 | match armable(Some(duration)) { |
326 | | - Some(duration) => tokio::time::timeout(duration, future).await, |
327 | | - None => Ok(future.await), |
| 333 | + Some(duration) => Either::Left(tokio::time::timeout(duration, future)), |
| 334 | + None => Either::Right(future.map(Ok)), |
328 | 335 | } |
329 | 336 | } |
330 | 337 |
|
|
0 commit comments