@@ -1,19 +1,15 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::ops::{Deref, DerefMut, BitAndAssign};
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use futures::{Future, Async, Poll, Stream};
|
||||
use futures::sync::oneshot;
|
||||
use tokio::reactor::{Handle, Interval};
|
||||
use relay;
|
||||
|
||||
use proto::{KeepAlive, KA};
|
||||
|
||||
pub struct Pool<T> {
|
||||
inner: Rc<RefCell<PoolInner<T>>>,
|
||||
inner: Arc<Mutex<PoolInner<T>>>,
|
||||
}
|
||||
|
||||
// Before using a pooled connection, make sure the sender is not dead.
|
||||
@@ -29,7 +25,7 @@ struct PoolInner<T> {
|
||||
enabled: bool,
|
||||
// These are internal Conns sitting in the event loop in the KeepAlive
|
||||
// state, waiting to receive a new Request to send on the socket.
|
||||
idle: HashMap<Rc<String>, Vec<Entry<T>>>,
|
||||
idle: HashMap<Arc<String>, Vec<Idle<T>>>,
|
||||
// These are outstanding Checkouts that are waiting for a socket to be
|
||||
// able to send a Request one. This is used when "racing" for a new
|
||||
// connection.
|
||||
@@ -39,7 +35,7 @@ struct PoolInner<T> {
|
||||
// this list is checked for any parked Checkouts, and tries to notify
|
||||
// them that the Conn could be used instead of waiting for a brand new
|
||||
// connection.
|
||||
parked: HashMap<Rc<String>, VecDeque<relay::Sender<Entry<T>>>>,
|
||||
parked: HashMap<Arc<String>, VecDeque<oneshot::Sender<T>>>,
|
||||
timeout: Option<Duration>,
|
||||
// Used to prevent multiple intervals from being spawned to clear
|
||||
// expired connections.
|
||||
@@ -49,10 +45,10 @@ struct PoolInner<T> {
|
||||
expired_timer_spawned: bool,
|
||||
}
|
||||
|
||||
impl<T: Clone + Closed> Pool<T> {
|
||||
impl<T> Pool<T> {
|
||||
pub fn new(enabled: bool, timeout: Option<Duration>) -> Pool<T> {
|
||||
Pool {
|
||||
inner: Rc::new(RefCell::new(PoolInner {
|
||||
inner: Arc::new(Mutex::new(PoolInner {
|
||||
enabled: enabled,
|
||||
idle: HashMap::new(),
|
||||
parked: HashMap::new(),
|
||||
@@ -61,74 +57,33 @@ impl<T: Clone + Closed> Pool<T> {
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Closed> Pool<T> {
|
||||
pub fn checkout(&self, key: &str) -> Checkout<T> {
|
||||
Checkout {
|
||||
key: Rc::new(key.to_owned()),
|
||||
key: Arc::new(key.to_owned()),
|
||||
pool: self.clone(),
|
||||
parked: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn put(&mut self, key: Rc<String>, entry: Entry<T>) {
|
||||
trace!("Pool::put {:?}", key);
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let mut remove_parked = false;
|
||||
let mut entry = Some(entry);
|
||||
if let Some(parked) = inner.parked.get_mut(&key) {
|
||||
while let Some(tx) = parked.pop_front() {
|
||||
if tx.is_canceled() {
|
||||
trace!("Pool::put removing canceled parked {:?}", key);
|
||||
} else {
|
||||
tx.complete(entry.take().unwrap());
|
||||
break;
|
||||
}
|
||||
/*
|
||||
match tx.send(entry.take().unwrap()) {
|
||||
Ok(()) => break,
|
||||
Err(e) => {
|
||||
trace!("Pool::put removing canceled parked {:?}", key);
|
||||
entry = Some(e);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
remove_parked = parked.is_empty();
|
||||
}
|
||||
if remove_parked {
|
||||
inner.parked.remove(&key);
|
||||
}
|
||||
|
||||
match entry {
|
||||
Some(entry) => {
|
||||
debug!("pooling idle connection for {:?}", key);
|
||||
inner.idle.entry(key)
|
||||
.or_insert(Vec::new())
|
||||
.push(entry);
|
||||
}
|
||||
None => trace!("Pool::put found parked {:?}", key),
|
||||
}
|
||||
}
|
||||
|
||||
fn take(&self, key: &Rc<String>) -> Option<Pooled<T>> {
|
||||
fn take(&self, key: &Arc<String>) -> Option<Pooled<T>> {
|
||||
let entry = {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
let expiration = Expiration::new(inner.timeout);
|
||||
let mut should_remove = false;
|
||||
let entry = inner.idle.get_mut(key).and_then(|list| {
|
||||
trace!("take; url = {:?}, expiration = {:?}", key, expiration.0);
|
||||
while let Some(entry) = list.pop() {
|
||||
match entry.status.get() {
|
||||
TimedKA::Idle(idle_at) if !expiration.expires(idle_at) => {
|
||||
if !entry.value.is_closed() {
|
||||
should_remove = list.is_empty();
|
||||
return Some(entry);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
if !expiration.expires(entry.idle_at) {
|
||||
if !entry.value.is_closed() {
|
||||
should_remove = list.is_empty();
|
||||
return Some(entry);
|
||||
}
|
||||
}
|
||||
trace!("removing unacceptable pooled {:?}", key);
|
||||
// every other case the Entry should just be dropped
|
||||
// every other case the Idle should just be dropped
|
||||
// 1. Idle but expired
|
||||
// 2. Busy (something else somehow took it?)
|
||||
// 3. Disabled don't reuse of course
|
||||
@@ -143,72 +98,102 @@ impl<T: Clone + Closed> Pool<T> {
|
||||
entry
|
||||
};
|
||||
|
||||
entry.map(|e| self.reuse(key, e))
|
||||
entry.map(|e| self.reuse(key, e.value))
|
||||
}
|
||||
|
||||
|
||||
pub fn pooled(&self, key: Rc<String>, value: T) -> Pooled<T> {
|
||||
pub fn pooled(&self, key: Arc<String>, value: T) -> Pooled<T> {
|
||||
Pooled {
|
||||
entry: Entry {
|
||||
value: value,
|
||||
is_reused: false,
|
||||
status: Rc::new(Cell::new(TimedKA::Busy)),
|
||||
},
|
||||
is_reused: false,
|
||||
key: key,
|
||||
pool: Rc::downgrade(&self.inner),
|
||||
pool: Arc::downgrade(&self.inner),
|
||||
value: Some(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_enabled(&self) -> bool {
|
||||
self.inner.borrow().enabled
|
||||
}
|
||||
|
||||
fn reuse(&self, key: &Rc<String>, mut entry: Entry<T>) -> Pooled<T> {
|
||||
fn reuse(&self, key: &Arc<String>, value: T) -> Pooled<T> {
|
||||
debug!("reuse idle connection for {:?}", key);
|
||||
entry.is_reused = true;
|
||||
entry.status.set(TimedKA::Busy);
|
||||
Pooled {
|
||||
entry: entry,
|
||||
is_reused: true,
|
||||
key: key.clone(),
|
||||
pool: Rc::downgrade(&self.inner),
|
||||
pool: Arc::downgrade(&self.inner),
|
||||
value: Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn park(&mut self, key: Rc<String>, tx: relay::Sender<Entry<T>>) {
|
||||
fn park(&mut self, key: Arc<String>, tx: oneshot::Sender<T>) {
|
||||
trace!("park; waiting for idle connection: {:?}", key);
|
||||
self.inner.borrow_mut()
|
||||
self.inner.lock().unwrap()
|
||||
.parked.entry(key)
|
||||
.or_insert(VecDeque::new())
|
||||
.push_back(tx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Pool<T> {
|
||||
impl<T: Closed> PoolInner<T> {
|
||||
fn put(&mut self, key: Arc<String>, value: T) {
|
||||
if !self.enabled {
|
||||
return;
|
||||
}
|
||||
trace!("Pool::put {:?}", key);
|
||||
let mut remove_parked = false;
|
||||
let mut value = Some(value);
|
||||
if let Some(parked) = self.parked.get_mut(&key) {
|
||||
while let Some(tx) = parked.pop_front() {
|
||||
if !tx.is_canceled() {
|
||||
match tx.send(value.take().unwrap()) {
|
||||
Ok(()) => break,
|
||||
Err(e) => {
|
||||
value = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Pool::put removing canceled parked {:?}", key);
|
||||
}
|
||||
remove_parked = parked.is_empty();
|
||||
}
|
||||
if remove_parked {
|
||||
self.parked.remove(&key);
|
||||
}
|
||||
|
||||
match value {
|
||||
Some(value) => {
|
||||
debug!("pooling idle connection for {:?}", key);
|
||||
self.idle.entry(key)
|
||||
.or_insert(Vec::new())
|
||||
.push(Idle {
|
||||
value: value,
|
||||
idle_at: Instant::now(),
|
||||
});
|
||||
}
|
||||
None => trace!("Pool::put found parked {:?}", key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PoolInner<T> {
|
||||
/// Any `FutureResponse`s that were created will have made a `Checkout`,
|
||||
/// and possibly inserted into the pool that it is waiting for an idle
|
||||
/// connection. If a user ever dropped that future, we need to clean out
|
||||
/// those parked senders.
|
||||
fn clean_parked(&mut self, key: &Rc<String>) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
|
||||
fn clean_parked(&mut self, key: &Arc<String>) {
|
||||
let mut remove_parked = false;
|
||||
if let Some(parked) = inner.parked.get_mut(key) {
|
||||
if let Some(parked) = self.parked.get_mut(key) {
|
||||
parked.retain(|tx| {
|
||||
!tx.is_canceled()
|
||||
});
|
||||
remove_parked = parked.is_empty();
|
||||
}
|
||||
if remove_parked {
|
||||
inner.parked.remove(key);
|
||||
self.parked.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Closed> Pool<T> {
|
||||
fn clear_expired(&self) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
|
||||
let dur = if let Some(dur) = inner.timeout {
|
||||
impl<T: Closed> PoolInner<T> {
|
||||
fn clear_expired(&mut self) {
|
||||
let dur = if let Some(dur) = self.timeout {
|
||||
dur
|
||||
} else {
|
||||
return
|
||||
@@ -217,19 +202,13 @@ impl<T: Closed> Pool<T> {
|
||||
let now = Instant::now();
|
||||
//self.last_idle_check_at = now;
|
||||
|
||||
inner.idle.retain(|_key, values| {
|
||||
self.idle.retain(|_key, values| {
|
||||
|
||||
values.retain(|val| {
|
||||
if val.value.is_closed() {
|
||||
values.retain(|entry| {
|
||||
if entry.value.is_closed() {
|
||||
return false;
|
||||
}
|
||||
match val.status.get() {
|
||||
TimedKA::Idle(idle_at) if now - idle_at < dur => {
|
||||
true
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
//now - val.idle_at < dur
|
||||
now - entry.idle_at < dur
|
||||
});
|
||||
|
||||
// returning false evicts this key/val
|
||||
@@ -241,28 +220,30 @@ impl<T: Closed> Pool<T> {
|
||||
|
||||
impl<T: Closed + 'static> Pool<T> {
|
||||
pub(super) fn spawn_expired_interval(&self, handle: &Handle) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let dur = {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
if !inner.enabled {
|
||||
return;
|
||||
}
|
||||
if !inner.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
if inner.expired_timer_spawned {
|
||||
return;
|
||||
}
|
||||
inner.expired_timer_spawned = true;
|
||||
if inner.expired_timer_spawned {
|
||||
return;
|
||||
}
|
||||
inner.expired_timer_spawned = true;
|
||||
|
||||
let dur = if let Some(dur) = inner.timeout {
|
||||
dur
|
||||
} else {
|
||||
return
|
||||
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),
|
||||
pool: Arc::downgrade(&self.inner),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -275,121 +256,83 @@ impl<T> Clone for Pool<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Pooled<T> {
|
||||
entry: Entry<T>,
|
||||
key: Rc<String>,
|
||||
pool: Weak<RefCell<PoolInner<T>>>,
|
||||
pub struct Pooled<T: Closed> {
|
||||
value: Option<T>,
|
||||
is_reused: bool,
|
||||
key: Arc<String>,
|
||||
pool: Weak<Mutex<PoolInner<T>>>,
|
||||
}
|
||||
|
||||
impl<T> Pooled<T> {
|
||||
impl<T: Closed> Pooled<T> {
|
||||
pub fn is_reused(&self) -> bool {
|
||||
self.entry.is_reused
|
||||
self.is_reused
|
||||
}
|
||||
|
||||
fn as_ref(&self) -> &T {
|
||||
self.value.as_ref().expect("not dropped")
|
||||
}
|
||||
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
self.value.as_mut().expect("not dropped")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Pooled<T> {
|
||||
impl<T: Closed> Deref for Pooled<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
&self.entry.value
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Pooled<T> {
|
||||
impl<T: Closed> DerefMut for Pooled<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.entry.value
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Closed> KeepAlive for Pooled<T> {
|
||||
fn busy(&mut self) {
|
||||
self.entry.status.set(TimedKA::Busy);
|
||||
}
|
||||
|
||||
fn disable(&mut self) {
|
||||
self.entry.status.set(TimedKA::Disabled);
|
||||
}
|
||||
|
||||
fn idle(&mut self) {
|
||||
let previous = self.status();
|
||||
self.entry.status.set(TimedKA::Idle(Instant::now()));
|
||||
if let KA::Idle = previous {
|
||||
trace!("Pooled::idle already idle");
|
||||
return;
|
||||
}
|
||||
self.entry.is_reused = true;
|
||||
if let Some(inner) = self.pool.upgrade() {
|
||||
let mut pool = Pool {
|
||||
inner: inner,
|
||||
};
|
||||
if pool.is_enabled() {
|
||||
pool.put(self.key.clone(), self.entry.clone());
|
||||
impl<T: Closed> Drop for Pooled<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(value) = self.value.take() {
|
||||
if let Some(inner) = self.pool.upgrade() {
|
||||
if let Ok(mut inner) = inner.lock() {
|
||||
inner.put(self.key.clone(), value);
|
||||
}
|
||||
} else {
|
||||
trace!("keepalive disabled, dropping pooled ({:?})", self.key);
|
||||
self.disable();
|
||||
trace!("pool dropped, dropping pooled ({:?})", self.key);
|
||||
}
|
||||
} else {
|
||||
trace!("pool dropped, dropping pooled ({:?})", self.key);
|
||||
self.disable();
|
||||
}
|
||||
}
|
||||
|
||||
fn status(&self) -> KA {
|
||||
match self.entry.status.get() {
|
||||
TimedKA::Idle(_) => KA::Idle,
|
||||
TimedKA::Busy => KA::Busy,
|
||||
TimedKA::Disabled => KA::Disabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Pooled<T> {
|
||||
impl<T: Closed> fmt::Debug for Pooled<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Pooled")
|
||||
.field("status", &self.entry.status.get())
|
||||
.field("key", &self.key)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Closed> BitAndAssign<bool> for Pooled<T> {
|
||||
fn bitand_assign(&mut self, enabled: bool) {
|
||||
if !enabled {
|
||||
self.disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Entry<T> {
|
||||
struct Idle<T> {
|
||||
idle_at: Instant,
|
||||
value: T,
|
||||
is_reused: bool,
|
||||
status: Rc<Cell<TimedKA>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum TimedKA {
|
||||
Idle(Instant),
|
||||
Busy,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub struct Checkout<T> {
|
||||
key: Rc<String>,
|
||||
key: Arc<String>,
|
||||
pool: Pool<T>,
|
||||
parked: Option<relay::Receiver<Entry<T>>>,
|
||||
parked: Option<oneshot::Receiver<T>>,
|
||||
}
|
||||
|
||||
struct NotParked;
|
||||
|
||||
impl<T: Clone + Closed> Checkout<T> {
|
||||
impl<T: Closed> Checkout<T> {
|
||||
fn poll_parked(&mut self) -> Poll<Pooled<T>, NotParked> {
|
||||
let mut drop_parked = false;
|
||||
if let Some(ref mut rx) = self.parked {
|
||||
match rx.poll() {
|
||||
Ok(Async::Ready(entry)) => {
|
||||
if !entry.value.is_closed() {
|
||||
return Ok(Async::Ready(self.pool.reuse(&self.key, entry)));
|
||||
Ok(Async::Ready(value)) => {
|
||||
if !value.is_closed() {
|
||||
return Ok(Async::Ready(self.pool.reuse(&self.key, value)));
|
||||
}
|
||||
drop_parked = true;
|
||||
},
|
||||
@@ -405,7 +348,7 @@ impl<T: Clone + Closed> Checkout<T> {
|
||||
|
||||
fn park(&mut self) {
|
||||
if self.parked.is_none() {
|
||||
let (tx, mut rx) = relay::channel();
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
let _ = rx.poll(); // park this task
|
||||
self.pool.park(self.key.clone(), tx);
|
||||
self.parked = Some(rx);
|
||||
@@ -413,9 +356,9 @@ impl<T: Clone + Closed> Checkout<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Closed> Future for Checkout<T> {
|
||||
impl<T: Closed> Future for Checkout<T> {
|
||||
type Item = Pooled<T>;
|
||||
type Error = io::Error;
|
||||
type Error = ::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.poll_parked() {
|
||||
@@ -437,7 +380,9 @@ impl<T: Clone + Closed> Future for Checkout<T> {
|
||||
impl<T> Drop for Checkout<T> {
|
||||
fn drop(&mut self) {
|
||||
self.parked.take();
|
||||
self.pool.clean_parked(&self.key);
|
||||
if let Ok(mut inner) = self.pool.inner.lock() {
|
||||
inner.clean_parked(&self.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +403,7 @@ impl Expiration {
|
||||
|
||||
struct IdleInterval<T> {
|
||||
interval: Interval,
|
||||
pool: Weak<RefCell<PoolInner<T>>>,
|
||||
pool: Weak<Mutex<PoolInner<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Closed + 'static> Future for IdleInterval<T> {
|
||||
@@ -470,22 +415,22 @@ impl<T: Closed + 'static> Future for IdleInterval<T> {
|
||||
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(()));
|
||||
if let Ok(mut inner) = inner.lock() {
|
||||
inner.clear_expired();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use futures::{Async, Future};
|
||||
use futures::future;
|
||||
use proto::KeepAlive;
|
||||
use super::{Closed, Pool};
|
||||
|
||||
impl Closed for i32 {
|
||||
@@ -497,9 +442,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_pool_checkout_smoke() {
|
||||
let pool = Pool::new(true, Some(Duration::from_secs(5)));
|
||||
let key = Rc::new("foo".to_string());
|
||||
let mut pooled = pool.pooled(key.clone(), 41);
|
||||
pooled.idle();
|
||||
let key = Arc::new("foo".to_string());
|
||||
let pooled = pool.pooled(key.clone(), 41);
|
||||
|
||||
drop(pooled);
|
||||
|
||||
match pool.checkout(&key).poll().unwrap() {
|
||||
Async::Ready(pooled) => assert_eq!(*pooled, 41),
|
||||
@@ -510,11 +456,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_pool_checkout_returns_none_if_expired() {
|
||||
future::lazy(|| {
|
||||
let pool = Pool::new(true, Some(Duration::from_secs(1)));
|
||||
let key = Rc::new("foo".to_string());
|
||||
let mut pooled = pool.pooled(key.clone(), 41);
|
||||
pooled.idle();
|
||||
::std::thread::sleep(pool.inner.borrow().timeout.unwrap());
|
||||
let pool = Pool::new(true, Some(Duration::from_millis(100)));
|
||||
let key = Arc::new("foo".to_string());
|
||||
let pooled = pool.pooled(key.clone(), 41);
|
||||
drop(pooled);
|
||||
::std::thread::sleep(pool.inner.lock().unwrap().timeout.unwrap());
|
||||
assert!(pool.checkout(&key).poll().unwrap().is_not_ready());
|
||||
::futures::future::ok::<(), ()>(())
|
||||
}).wait().unwrap();
|
||||
@@ -522,26 +468,23 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_pool_checkout_removes_expired() {
|
||||
let pool = Pool::new(true, Some(Duration::from_secs(1)));
|
||||
let key = Rc::new("foo".to_string());
|
||||
future::lazy(|| {
|
||||
let pool = Pool::new(true, Some(Duration::from_millis(100)));
|
||||
let key = Arc::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();
|
||||
pool.pooled(key.clone(), 41);
|
||||
pool.pooled(key.clone(), 5);
|
||||
pool.pooled(key.clone(), 99);
|
||||
|
||||
assert_eq!(pool.inner.lock().unwrap().idle.get(&key).map(|entries| entries.len()), Some(3));
|
||||
::std::thread::sleep(pool.inner.lock().unwrap().timeout.unwrap());
|
||||
|
||||
assert_eq!(pool.inner.borrow().idle.get(&key).map(|entries| entries.len()), Some(3));
|
||||
::std::thread::sleep(pool.inner.borrow().timeout.unwrap());
|
||||
// checkout.poll() should clean out the expired
|
||||
pool.checkout(&key).poll().unwrap();
|
||||
assert!(pool.inner.lock().unwrap().idle.get(&key).is_none());
|
||||
|
||||
pooled1.idle();
|
||||
pooled2.idle(); // idle after sleep, not expired
|
||||
pool.checkout(&key).poll().unwrap();
|
||||
assert_eq!(pool.inner.borrow().idle.get(&key).map(|entries| entries.len()), Some(1));
|
||||
pool.checkout(&key).poll().unwrap();
|
||||
assert!(pool.inner.borrow().idle.get(&key).is_none());
|
||||
Ok::<(), ()>(())
|
||||
}).wait().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -549,16 +492,13 @@ mod tests {
|
||||
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 key = Arc::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();
|
||||
pool.pooled(key.clone(), 41);
|
||||
pool.pooled(key.clone(), 5);
|
||||
pool.pooled(key.clone(), 99);
|
||||
|
||||
assert_eq!(pool.inner.borrow().idle.get(&key).map(|entries| entries.len()), Some(3));
|
||||
assert_eq!(pool.inner.lock().unwrap().idle.get(&key).map(|entries| entries.len()), Some(3));
|
||||
|
||||
let timeout = ::tokio::reactor::Timeout::new(
|
||||
Duration::from_millis(400), // allow for too-good resolution
|
||||
@@ -566,49 +506,48 @@ mod tests {
|
||||
).unwrap();
|
||||
core.run(timeout).unwrap();
|
||||
|
||||
assert!(pool.inner.borrow().idle.get(&key).is_none());
|
||||
assert!(pool.inner.lock().unwrap().idle.get(&key).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_checkout_task_unparked() {
|
||||
let pool = Pool::new(true, Some(Duration::from_secs(10)));
|
||||
let key = Rc::new("foo".to_string());
|
||||
let pooled1 = pool.pooled(key.clone(), 41);
|
||||
let key = Arc::new("foo".to_string());
|
||||
let pooled = pool.pooled(key.clone(), 41);
|
||||
|
||||
let mut pooled = pooled1.clone();
|
||||
let checkout = pool.checkout(&key).join(future::lazy(move || {
|
||||
// the checkout future will park first,
|
||||
// and then this lazy future will be polled, which will insert
|
||||
// the pooled back into the pool
|
||||
//
|
||||
// this test makes sure that doing so will unpark the checkout
|
||||
pooled.idle();
|
||||
drop(pooled);
|
||||
Ok(())
|
||||
})).map(|(entry, _)| entry);
|
||||
assert_eq!(*checkout.wait().unwrap(), *pooled1);
|
||||
assert_eq!(*checkout.wait().unwrap(), 41);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_checkout_drop_cleans_up_parked() {
|
||||
future::lazy(|| {
|
||||
let pool = Pool::new(true, Some(Duration::from_secs(10)));
|
||||
let key = Rc::new("localhost:12345".to_string());
|
||||
let _pooled1 = pool.pooled(key.clone(), 41);
|
||||
let pool = Pool::<i32>::new(true, Some(Duration::from_secs(10)));
|
||||
let key = Arc::new("localhost:12345".to_string());
|
||||
|
||||
let mut checkout1 = pool.checkout(&key);
|
||||
let mut checkout2 = pool.checkout(&key);
|
||||
|
||||
// first poll needed to get into Pool's parked
|
||||
checkout1.poll().unwrap();
|
||||
assert_eq!(pool.inner.borrow().parked.get(&key).unwrap().len(), 1);
|
||||
assert_eq!(pool.inner.lock().unwrap().parked.get(&key).unwrap().len(), 1);
|
||||
checkout2.poll().unwrap();
|
||||
assert_eq!(pool.inner.borrow().parked.get(&key).unwrap().len(), 2);
|
||||
assert_eq!(pool.inner.lock().unwrap().parked.get(&key).unwrap().len(), 2);
|
||||
|
||||
// on drop, clean up Pool
|
||||
drop(checkout1);
|
||||
assert_eq!(pool.inner.borrow().parked.get(&key).unwrap().len(), 1);
|
||||
assert_eq!(pool.inner.lock().unwrap().parked.get(&key).unwrap().len(), 1);
|
||||
|
||||
drop(checkout2);
|
||||
assert!(pool.inner.borrow().parked.get(&key).is_none());
|
||||
assert!(pool.inner.lock().unwrap().parked.get(&key).is_none());
|
||||
|
||||
::futures::future::ok::<(), ()>(())
|
||||
}).wait().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user