Merge branch 'pool-expired'
This commit is contained in:
@@ -91,7 +91,6 @@ impl<C, B> Client<C, B> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new client with a specific connector.
|
||||
#[inline]
|
||||
fn configured(config: Config<C, B>, exec: Exec) -> Client<C, B> {
|
||||
Client {
|
||||
@@ -118,6 +117,14 @@ where C: Connect,
|
||||
/// Send a constructed Request using this Client.
|
||||
#[inline]
|
||||
pub fn request(&self, mut req: Request<B>) -> FutureResponse {
|
||||
// TODO(0.12): do this at construction time.
|
||||
//
|
||||
// It cannot be done in the constructor because the Client::configured
|
||||
// does not have `B: 'static` bounds, which are required to spawn
|
||||
// the interval. In 0.12, add a static bounds to the constructor,
|
||||
// and move this.
|
||||
self.schedule_pool_timer();
|
||||
|
||||
match req.version() {
|
||||
HttpVersion::Http10 |
|
||||
HttpVersion::Http11 => (),
|
||||
@@ -249,6 +256,12 @@ where C: Connect,
|
||||
|
||||
Box::new(resp)
|
||||
}
|
||||
|
||||
fn schedule_pool_timer(&self) {
|
||||
if let Exec::Handle(ref h) = self.executor {
|
||||
self.pool.spawn_expired_interval(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, B> Service for Client<C, B>
|
||||
|
||||
@@ -6,7 +6,8 @@ use std::ops::{Deref, DerefMut, BitAndAssign};
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use futures::{Future, Async, Poll};
|
||||
use futures::{Future, Async, Poll, Stream};
|
||||
use tokio::reactor::{Handle, Interval};
|
||||
use relay;
|
||||
|
||||
use proto::{KeepAlive, KA};
|
||||
@@ -40,6 +41,12 @@ struct PoolInner<T> {
|
||||
// connection.
|
||||
parked: HashMap<Rc<String>, VecDeque<relay::Sender<Entry<T>>>>,
|
||||
timeout: Option<Duration>,
|
||||
// Used to prevent multiple intervals from being spawned to clear
|
||||
// expired connections.
|
||||
//
|
||||
// TODO(0.12): Remove the need for this when Client::schedule_pool_timer
|
||||
// can be done in Client::new.
|
||||
expired_timer_spawned: bool,
|
||||
}
|
||||
|
||||
impl<T: Clone + Ready> Pool<T> {
|
||||
@@ -50,6 +57,7 @@ impl<T: Clone + Ready> Pool<T> {
|
||||
idle: HashMap::new(),
|
||||
parked: HashMap::new(),
|
||||
timeout: timeout,
|
||||
expired_timer_spawned: false,
|
||||
})),
|
||||
}
|
||||
}
|
||||
@@ -194,6 +202,64 @@ impl<T> Pool<T> {
|
||||
inner.parked.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_expired(&self) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
|
||||
let dur = if let Some(dur) = inner.timeout {
|
||||
dur
|
||||
} else {
|
||||
return
|
||||
};
|
||||
|
||||
let now = Instant::now();
|
||||
//self.last_idle_check_at = now;
|
||||
|
||||
inner.idle.retain(|_key, values| {
|
||||
|
||||
values.retain(|val| {
|
||||
match val.status.get() {
|
||||
TimedKA::Idle(idle_at) if now - idle_at < dur => {
|
||||
true
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
//now - val.idle_at < dur
|
||||
});
|
||||
|
||||
// returning false evicts this key/val
|
||||
!values.is_empty()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T: 'static> Pool<T> {
|
||||
pub(super) fn spawn_expired_interval(&self, handle: &Handle) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
|
||||
if !inner.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
if inner.expired_timer_spawned {
|
||||
return;
|
||||
}
|
||||
inner.expired_timer_spawned = true;
|
||||
|
||||
let dur = if let Some(dur) = inner.timeout {
|
||||
dur
|
||||
} else {
|
||||
return
|
||||
};
|
||||
|
||||
let interval = Interval::new(dur, handle)
|
||||
.expect("reactor is gone");
|
||||
handle.spawn(IdleInterval {
|
||||
interval: interval,
|
||||
pool: Rc::downgrade(&self.inner),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Pool<T> {
|
||||
@@ -385,6 +451,28 @@ impl Expiration {
|
||||
}
|
||||
}
|
||||
|
||||
struct IdleInterval<T> {
|
||||
interval: Interval,
|
||||
pool: Weak<RefCell<PoolInner<T>>>,
|
||||
}
|
||||
|
||||
impl<T: 'static> Future for IdleInterval<T> {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
try_ready!(self.interval.poll().map_err(|_| unreachable!("interval cannot error")));
|
||||
|
||||
if let Some(inner) = self.pool.upgrade() {
|
||||
let pool = Pool { inner: inner };
|
||||
pool.clear_expired();
|
||||
} else {
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -428,7 +516,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_removes_expired() {
|
||||
fn test_pool_checkout_removes_expired() {
|
||||
let pool = Pool::new(true, Some(Duration::from_secs(1)));
|
||||
let key = Rc::new("foo".to_string());
|
||||
|
||||
@@ -451,6 +539,31 @@ mod tests {
|
||||
assert!(pool.inner.borrow().idle.get(&key).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_timer_removes_expired() {
|
||||
let mut core = ::tokio::reactor::Core::new().unwrap();
|
||||
let pool = Pool::new(true, Some(Duration::from_millis(100)));
|
||||
pool.spawn_expired_interval(&core.handle());
|
||||
let key = Rc::new("foo".to_string());
|
||||
|
||||
let mut pooled1 = pool.pooled(key.clone(), 41);
|
||||
pooled1.idle();
|
||||
let mut pooled2 = pool.pooled(key.clone(), 5);
|
||||
pooled2.idle();
|
||||
let mut pooled3 = pool.pooled(key.clone(), 99);
|
||||
pooled3.idle();
|
||||
|
||||
assert_eq!(pool.inner.borrow().idle.get(&key).map(|entries| entries.len()), Some(3));
|
||||
|
||||
let timeout = ::tokio::reactor::Timeout::new(
|
||||
Duration::from_millis(400), // allow for too-good resolution
|
||||
&core.handle()
|
||||
).unwrap();
|
||||
core.run(timeout).unwrap();
|
||||
|
||||
assert!(pool.inner.borrow().idle.get(&key).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_checkout_task_unparked() {
|
||||
let pool = Pool::new(true, Some(Duration::from_secs(10)));
|
||||
|
||||
Reference in New Issue
Block a user