Skip to content

Commit 784109d

Browse files
authored
feat: add timer support for legacy::Pool (#84)
1 parent b96757e commit 784109d

File tree

4 files changed

+122
-33
lines changed

4 files changed

+122
-33
lines changed

src/client/legacy/client.rs

+36-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ use tracing::{debug, trace, warn};
2222
use super::connect::HttpConnector;
2323
use super::connect::{Alpn, Connect, Connected, Connection};
2424
use super::pool::{self, Ver};
25-
use crate::common::{lazy as hyper_lazy, Exec, Lazy, SyncWrapper};
25+
26+
use crate::common::{lazy as hyper_lazy, timer, Exec, Lazy, SyncWrapper};
2627

2728
type BoxSendFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
2829

@@ -975,6 +976,7 @@ pub struct Builder {
975976
#[cfg(feature = "http2")]
976977
h2_builder: hyper::client::conn::http2::Builder<Exec>,
977978
pool_config: pool::Config,
979+
pool_timer: Option<timer::Timer>,
978980
}
979981

980982
impl Builder {
@@ -999,13 +1001,34 @@ impl Builder {
9991001
idle_timeout: Some(Duration::from_secs(90)),
10001002
max_idle_per_host: std::usize::MAX,
10011003
},
1004+
pool_timer: None,
10021005
}
10031006
}
10041007
/// Set an optional timeout for idle sockets being kept-alive.
1008+
/// A `Timer` is required for this to take effect. See `Builder::pool_timer`
10051009
///
10061010
/// Pass `None` to disable timeout.
10071011
///
10081012
/// Default is 90 seconds.
1013+
///
1014+
/// # Example
1015+
///
1016+
/// ```
1017+
/// # #[cfg(feature = "tokio")]
1018+
/// # fn run () {
1019+
/// use std::time::Duration;
1020+
/// use hyper_util::client::legacy::Client;
1021+
/// use hyper_util::rt::{TokioExecutor, TokioTimer};
1022+
///
1023+
/// let client = Client::builder(TokioExecutor::new())
1024+
/// .pool_idle_timeout(Duration::from_secs(30))
1025+
/// .pool_timer(TokioTimer::new())
1026+
/// .build_http();
1027+
///
1028+
/// # let infer: Client<_, http_body_util::Full<bytes::Bytes>> = client;
1029+
/// # }
1030+
/// # fn main() {}
1031+
/// ```
10091032
pub fn pool_idle_timeout<D>(&mut self, val: D) -> &mut Self
10101033
where
10111034
D: Into<Option<Duration>>,
@@ -1366,7 +1389,7 @@ impl Builder {
13661389
self
13671390
}
13681391

1369-
/// Provide a timer to be used for timeouts and intervals.
1392+
/// Provide a timer to be used for h2
13701393
///
13711394
/// See the documentation of [`h2::client::Builder::timer`] for more
13721395
/// details.
@@ -1378,7 +1401,15 @@ impl Builder {
13781401
{
13791402
#[cfg(feature = "http2")]
13801403
self.h2_builder.timer(timer);
1381-
// TODO(https://github.com/hyperium/hyper/issues/3167) set for pool as well
1404+
self
1405+
}
1406+
1407+
/// Provide a timer to be used for timeouts and intervals in connection pools.
1408+
pub fn pool_timer<M>(&mut self, timer: M) -> &mut Self
1409+
where
1410+
M: Timer + Clone + Send + Sync + 'static,
1411+
{
1412+
self.pool_timer = Some(timer::Timer::new(timer.clone()));
13821413
self
13831414
}
13841415

@@ -1447,6 +1478,7 @@ impl Builder {
14471478
B::Data: Send,
14481479
{
14491480
let exec = self.exec.clone();
1481+
let timer = self.pool_timer.clone();
14501482
Client {
14511483
config: self.client_config,
14521484
exec: exec.clone(),
@@ -1455,7 +1487,7 @@ impl Builder {
14551487
#[cfg(feature = "http2")]
14561488
h2_builder: self.h2_builder.clone(),
14571489
connector,
1458-
pool: pool::Pool::new(self.pool_config, exec),
1490+
pool: pool::Pool::new(self.pool_config, exec, timer),
14591491
}
14601492
}
14611493
}

src/client/legacy/pool.rs

+47-29
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ use futures_channel::oneshot;
1818
use futures_util::ready;
1919
use tracing::{debug, trace};
2020

21-
use crate::common::exec::{self, Exec};
21+
use hyper::rt::Sleep;
22+
use hyper::rt::Timer as _;
23+
24+
use crate::common::{exec, exec::Exec, timer::Timer};
2225

2326
// FIXME: allow() required due to `impl Trait` leaking types to this lint
2427
#[allow(missing_debug_implementations)]
@@ -97,6 +100,7 @@ struct PoolInner<T, K: Eq + Hash> {
97100
// the Pool completely drops. That way, the interval can cancel immediately.
98101
idle_interval_ref: Option<oneshot::Sender<Infallible>>,
99102
exec: Exec,
103+
timer: Option<Timer>,
100104
timeout: Option<Duration>,
101105
}
102106

@@ -117,11 +121,13 @@ impl Config {
117121
}
118122

119123
impl<T, K: Key> Pool<T, K> {
120-
pub fn new<E>(config: Config, executor: E) -> Pool<T, K>
124+
pub fn new<E, M>(config: Config, executor: E, timer: Option<M>) -> Pool<T, K>
121125
where
122126
E: hyper::rt::Executor<exec::BoxSendFuture> + Send + Sync + Clone + 'static,
127+
M: hyper::rt::Timer + Send + Sync + Clone + 'static,
123128
{
124129
let exec = Exec::new(executor);
130+
let timer = timer.map(|t| Timer::new(t));
125131
let inner = if config.is_enabled() {
126132
Some(Arc::new(Mutex::new(PoolInner {
127133
connecting: HashSet::new(),
@@ -130,6 +136,7 @@ impl<T, K: Key> Pool<T, K> {
130136
max_idle_per_host: config.max_idle_per_host,
131137
waiters: HashMap::new(),
132138
exec,
139+
timer,
133140
timeout: config.idle_timeout,
134141
})))
135142
} else {
@@ -411,31 +418,33 @@ impl<T: Poolable, K: Key> PoolInner<T, K> {
411418
self.waiters.remove(key);
412419
}
413420

414-
fn spawn_idle_interval(&mut self, _pool_ref: &Arc<Mutex<PoolInner<T, K>>>) {
415-
// TODO
416-
/*
417-
let (dur, rx) = {
418-
if self.idle_interval_ref.is_some() {
419-
return;
420-
}
421-
422-
if let Some(dur) = self.timeout {
423-
let (tx, rx) = oneshot::channel();
424-
self.idle_interval_ref = Some(tx);
425-
(dur, rx)
426-
} else {
427-
return;
428-
}
421+
fn spawn_idle_interval(&mut self, pool_ref: &Arc<Mutex<PoolInner<T, K>>>) {
422+
if self.idle_interval_ref.is_some() {
423+
return;
424+
}
425+
let dur = if let Some(dur) = self.timeout {
426+
dur
427+
} else {
428+
return;
429+
};
430+
let timer = if let Some(timer) = self.timer.clone() {
431+
timer
432+
} else {
433+
return;
429434
};
435+
let (tx, rx) = oneshot::channel();
436+
self.idle_interval_ref = Some(tx);
430437

431438
let interval = IdleTask {
432-
interval: tokio::time::interval(dur),
439+
timer: timer.clone(),
440+
duration: dur,
441+
deadline: Instant::now(),
442+
fut: timer.sleep_until(Instant::now()), // ready at first tick
433443
pool: WeakOpt::downgrade(pool_ref),
434444
pool_drop_notifier: rx,
435445
};
436446

437447
self.exec.execute(interval);
438-
*/
439448
}
440449
}
441450

@@ -755,11 +764,12 @@ impl Expiration {
755764
}
756765
}
757766

758-
/*
759767
pin_project_lite::pin_project! {
760768
struct IdleTask<T, K: Key> {
761-
#[pin]
762-
interval: Interval,
769+
timer: Timer,
770+
duration: Duration,
771+
deadline: Instant,
772+
fut: Pin<Box<dyn Sleep>>,
763773
pool: WeakOpt<Mutex<PoolInner<T, K>>>,
764774
// This allows the IdleTask to be notified as soon as the entire
765775
// Pool is fully dropped, and shutdown. This channel is never sent on,
@@ -784,7 +794,15 @@ impl<T: Poolable + 'static, K: Key> Future for IdleTask<T, K> {
784794
}
785795
}
786796

787-
ready!(this.interval.as_mut().poll_tick(cx));
797+
ready!(Pin::new(&mut this.fut).poll(cx));
798+
// Set this task to run after the next deadline
799+
// If the poll missed the deadline by a lot, set the deadline
800+
// from the current time instead
801+
*this.deadline = *this.deadline + *this.duration;
802+
if *this.deadline < Instant::now() - Duration::from_millis(5) {
803+
*this.deadline = Instant::now() + *this.duration;
804+
}
805+
*this.fut = this.timer.sleep_until(*this.deadline);
788806

789807
if let Some(inner) = this.pool.upgrade() {
790808
if let Ok(mut inner) = inner.lock() {
@@ -797,7 +815,6 @@ impl<T: Poolable + 'static, K: Key> Future for IdleTask<T, K> {
797815
}
798816
}
799817
}
800-
*/
801818

802819
impl<T> WeakOpt<T> {
803820
fn none() -> Self {
@@ -823,7 +840,9 @@ mod tests {
823840
use std::time::Duration;
824841

825842
use super::{Connecting, Key, Pool, Poolable, Reservation, WeakOpt};
826-
use crate::rt::TokioExecutor;
843+
use crate::rt::{TokioExecutor, TokioTimer};
844+
845+
use crate::common::timer;
827846

828847
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
829848
struct KeyImpl(http::uri::Scheme, http::uri::Authority);
@@ -870,6 +889,7 @@ mod tests {
870889
max_idle_per_host: max_idle,
871890
},
872891
TokioExecutor::new(),
892+
Option::<timer::Timer>::None,
873893
);
874894
pool.no_timer();
875895
pool
@@ -960,16 +980,14 @@ mod tests {
960980
}
961981

962982
#[tokio::test]
963-
#[ignore] // TODO
964983
async fn test_pool_timer_removes_expired() {
965-
tokio::time::pause();
966-
967984
let pool = Pool::new(
968985
super::Config {
969986
idle_timeout: Some(Duration::from_millis(10)),
970987
max_idle_per_host: std::usize::MAX,
971988
},
972989
TokioExecutor::new(),
990+
Some(TokioTimer::new()),
973991
);
974992

975993
let key = host_key("foo");
@@ -984,7 +1002,7 @@ mod tests {
9841002
);
9851003

9861004
// Let the timer tick passed the expiration...
987-
tokio::time::advance(Duration::from_millis(30)).await;
1005+
tokio::time::sleep(Duration::from_millis(30)).await;
9881006
// Yield so the Interval can reap...
9891007
tokio::task::yield_now().await;
9901008

src/common/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod lazy;
66
pub(crate) mod rewind;
77
#[cfg(feature = "client")]
88
mod sync;
9+
pub(crate) mod timer;
910

1011
#[cfg(feature = "client")]
1112
pub(crate) use exec::Exec;

src/common/timer.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#![allow(dead_code)]
2+
3+
use std::fmt;
4+
use std::pin::Pin;
5+
use std::sync::Arc;
6+
use std::time::Duration;
7+
use std::time::Instant;
8+
9+
use hyper::rt::Sleep;
10+
11+
#[derive(Clone)]
12+
pub(crate) struct Timer(Arc<dyn hyper::rt::Timer + Send + Sync>);
13+
14+
// =====impl Timer=====
15+
impl Timer {
16+
pub(crate) fn new<T>(inner: T) -> Self
17+
where
18+
T: hyper::rt::Timer + Send + Sync + 'static,
19+
{
20+
Self(Arc::new(inner))
21+
}
22+
}
23+
24+
impl fmt::Debug for Timer {
25+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26+
f.debug_struct("Timer").finish()
27+
}
28+
}
29+
30+
impl hyper::rt::Timer for Timer {
31+
fn sleep(&self, duration: Duration) -> Pin<Box<dyn Sleep>> {
32+
self.0.sleep(duration)
33+
}
34+
35+
fn sleep_until(&self, deadline: Instant) -> Pin<Box<dyn Sleep>> {
36+
self.0.sleep_until(deadline)
37+
}
38+
}

0 commit comments

Comments
 (0)