refactor(client): don't allocate PoolInner if pool is disabled
This commit is contained in:
@@ -15,7 +15,8 @@ use super::Ver;
|
||||
// FIXME: allow() required due to `impl Trait` leaking types to this lint
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub(super) struct Pool<T> {
|
||||
inner: Arc<PoolInner<T>>,
|
||||
// If the pool is disabled, this is None.
|
||||
inner: Option<Arc<Mutex<PoolInner<T>>>>,
|
||||
}
|
||||
|
||||
// Before using a pooled connection, make sure the sender is not dead.
|
||||
@@ -52,11 +53,6 @@ pub(super) enum Reservation<T> {
|
||||
pub(super) type Key = (Arc<String>, Ver);
|
||||
|
||||
struct PoolInner<T> {
|
||||
connections: Mutex<Connections<T>>,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
struct Connections<T> {
|
||||
// A flag that a connection is being estabilished, and the connection
|
||||
// should be shared. This prevents making multiple HTTP/2 connections
|
||||
// to the same host.
|
||||
@@ -104,30 +100,37 @@ pub(super) struct MaxIdlePerHost(pub(super) usize);
|
||||
|
||||
impl<T> Pool<T> {
|
||||
pub fn new(enabled: Enabled, timeout: IdleTimeout, max_idle: MaxIdlePerHost, __exec: &Exec) -> Pool<T> {
|
||||
let inner = if enabled.0 {
|
||||
Some(Arc::new(Mutex::new(PoolInner {
|
||||
connecting: HashSet::new(),
|
||||
idle: HashMap::new(),
|
||||
#[cfg(feature = "runtime")]
|
||||
idle_interval_ref: None,
|
||||
max_idle_per_host: max_idle.0,
|
||||
waiters: HashMap::new(),
|
||||
#[cfg(feature = "runtime")]
|
||||
exec: __exec.clone(),
|
||||
timeout: timeout.0,
|
||||
})))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Pool {
|
||||
inner: Arc::new(PoolInner {
|
||||
connections: Mutex::new(Connections {
|
||||
connecting: HashSet::new(),
|
||||
idle: HashMap::new(),
|
||||
#[cfg(feature = "runtime")]
|
||||
idle_interval_ref: None,
|
||||
max_idle_per_host: max_idle.0,
|
||||
waiters: HashMap::new(),
|
||||
#[cfg(feature = "runtime")]
|
||||
exec: __exec.clone(),
|
||||
timeout: timeout.0,
|
||||
}),
|
||||
enabled: enabled.0,
|
||||
}),
|
||||
inner,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_enabled(&self) -> bool {
|
||||
self.inner.is_some()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn no_timer(&self) {
|
||||
// Prevent an actual interval from being created for this pool...
|
||||
#[cfg(feature = "runtime")]
|
||||
{
|
||||
let mut inner = self.inner.connections.lock().unwrap();
|
||||
let mut inner = self.inner.as_ref().unwrap().lock().unwrap();
|
||||
assert!(inner.idle_interval_ref.is_none(), "timer already spawned");
|
||||
let (tx, _) = oneshot::channel();
|
||||
inner.idle_interval_ref = Some(tx);
|
||||
@@ -149,26 +152,39 @@ impl<T: Poolable> Pool<T> {
|
||||
/// Ensure that there is only ever 1 connecting task for HTTP/2
|
||||
/// connections. This does nothing for HTTP/1.
|
||||
pub(super) fn connecting(&self, key: &Key) -> Option<Connecting<T>> {
|
||||
if key.1 == Ver::Http2 && self.inner.enabled {
|
||||
let mut inner = self.inner.connections.lock().unwrap();
|
||||
if inner.connecting.insert(key.clone()) {
|
||||
let connecting = Connecting {
|
||||
key: key.clone(),
|
||||
pool: WeakOpt::downgrade(&self.inner),
|
||||
if key.1 == Ver::Http2 {
|
||||
if let Some(ref enabled) = self.inner {
|
||||
let mut inner = enabled.lock().unwrap();
|
||||
return if inner.connecting.insert(key.clone()) {
|
||||
let connecting = Connecting {
|
||||
key: key.clone(),
|
||||
pool: WeakOpt::downgrade(enabled),
|
||||
};
|
||||
Some(connecting)
|
||||
} else {
|
||||
trace!("HTTP/2 connecting already in progress for {:?}", key.0);
|
||||
None
|
||||
};
|
||||
Some(connecting)
|
||||
} else {
|
||||
trace!("HTTP/2 connecting already in progress for {:?}", key.0);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(Connecting {
|
||||
key: key.clone(),
|
||||
// in HTTP/1's case, there is never a lock, so we don't
|
||||
// need to do anything in Drop.
|
||||
pool: WeakOpt::none(),
|
||||
})
|
||||
}
|
||||
|
||||
// else
|
||||
Some(Connecting {
|
||||
key: key.clone(),
|
||||
// in HTTP/1's case, there is never a lock, so we don't
|
||||
// need to do anything in Drop.
|
||||
pool: WeakOpt::none(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn locked(&self) -> ::std::sync::MutexGuard<PoolInner<T>> {
|
||||
self
|
||||
.inner
|
||||
.as_ref()
|
||||
.expect("enabled")
|
||||
.lock()
|
||||
.expect("lock")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime")]
|
||||
@@ -181,10 +197,7 @@ impl<T: Poolable> Pool<T> {
|
||||
#[cfg(test)]
|
||||
pub(super) fn idle_count(&self, key: &Key) -> usize {
|
||||
self
|
||||
.inner
|
||||
.connections
|
||||
.lock()
|
||||
.unwrap()
|
||||
.locked()
|
||||
.idle
|
||||
.get(key)
|
||||
.map(|list| list.len())
|
||||
@@ -193,7 +206,7 @@ impl<T: Poolable> Pool<T> {
|
||||
|
||||
fn take(&self, key: &Key) -> Option<Pooled<T>> {
|
||||
let entry = {
|
||||
let mut inner = self.inner.connections.lock().unwrap();
|
||||
let mut inner = self.inner.as_ref()?.lock().unwrap();
|
||||
let expiration = Expiration::new(inner.timeout);
|
||||
let maybe_entry = inner.idle.get_mut(key)
|
||||
.and_then(|list| {
|
||||
@@ -227,7 +240,7 @@ impl<T: Poolable> Pool<T> {
|
||||
}
|
||||
|
||||
pub(super) fn pooled(&self, mut connecting: Connecting<T>, value: T) -> Pooled<T> {
|
||||
let (value, pool_ref) = if self.inner.enabled {
|
||||
let (value, pool_ref) = if let Some(ref enabled) = self.inner {
|
||||
match value.reserve() {
|
||||
Reservation::Shared(to_insert, to_return) => {
|
||||
debug_assert_eq!(
|
||||
@@ -235,8 +248,8 @@ impl<T: Poolable> Pool<T> {
|
||||
Ver::Http2,
|
||||
"shared reservation without Http2"
|
||||
);
|
||||
let mut inner = self.inner.connections.lock().unwrap();
|
||||
inner.put(connecting.key.clone(), to_insert, &self.inner);
|
||||
let mut inner = enabled.lock().unwrap();
|
||||
inner.put(connecting.key.clone(), to_insert, enabled);
|
||||
// Do this here instead of Drop for Connecting because we
|
||||
// already have a lock, no need to lock the mutex twice.
|
||||
inner.connected(&connecting.key);
|
||||
@@ -251,7 +264,7 @@ impl<T: Poolable> Pool<T> {
|
||||
// Unique reservations must take a reference to the pool
|
||||
// since they hope to reinsert once the reservation is
|
||||
// completed
|
||||
(value, WeakOpt::downgrade(&self.inner))
|
||||
(value, WeakOpt::downgrade(enabled))
|
||||
},
|
||||
}
|
||||
} else {
|
||||
@@ -280,11 +293,12 @@ impl<T: Poolable> Pool<T> {
|
||||
// we just have the final value, without knowledge of if this is
|
||||
// unique or shared. So, the hack is to just assume Ver::Http2 means
|
||||
// shared... :(
|
||||
let pool_ref = if key.1 == Ver::Http2 {
|
||||
WeakOpt::none()
|
||||
} else {
|
||||
WeakOpt::downgrade(&self.inner)
|
||||
};
|
||||
let mut pool_ref = WeakOpt::none();
|
||||
if key.1 == Ver::Http1 {
|
||||
if let Some(ref enabled) = self.inner {
|
||||
pool_ref = WeakOpt::downgrade(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
Pooled {
|
||||
is_reused: true,
|
||||
@@ -294,10 +308,19 @@ impl<T: Poolable> Pool<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn waiter(&mut self, key: Key, tx: oneshot::Sender<T>) {
|
||||
fn waiter(&self, key: Key, tx: oneshot::Sender<T>) {
|
||||
debug_assert!(
|
||||
self.is_enabled(),
|
||||
"pool.waiter() should not be called if disabled",
|
||||
);
|
||||
trace!("checkout waiting for idle connection: {:?}", key);
|
||||
self.inner.connections.lock().unwrap()
|
||||
.waiters.entry(key)
|
||||
self.inner
|
||||
.as_ref()
|
||||
.expect("pool.waiter() expects pool is enabled")
|
||||
.lock()
|
||||
.unwrap()
|
||||
.waiters
|
||||
.entry(key)
|
||||
.or_insert(VecDeque::new())
|
||||
.push_back(tx);
|
||||
}
|
||||
@@ -352,8 +375,8 @@ impl<'a, T: Poolable + 'a> IdlePopper<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Poolable> Connections<T> {
|
||||
fn put(&mut self, key: Key, value: T, __pool_ref: &Arc<PoolInner<T>>) {
|
||||
impl<T: Poolable> PoolInner<T> {
|
||||
fn put(&mut self, key: Key, value: T, __pool_ref: &Arc<Mutex<PoolInner<T>>>) {
|
||||
if key.1 == Ver::Http2 && self.idle.contains_key(&key) {
|
||||
trace!("put; existing idle HTTP/2 connection for {:?}", key);
|
||||
return;
|
||||
@@ -438,10 +461,8 @@ impl<T: Poolable> Connections<T> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime")]
|
||||
fn spawn_idle_interval(&mut self, pool_ref: &Arc<PoolInner<T>>) {
|
||||
fn spawn_idle_interval(&mut self, pool_ref: &Arc<Mutex<PoolInner<T>>>) {
|
||||
let (dur, rx) = {
|
||||
debug_assert!(pool_ref.enabled);
|
||||
|
||||
if self.idle_interval_ref.is_some() {
|
||||
return;
|
||||
}
|
||||
@@ -470,7 +491,7 @@ impl<T: Poolable> Connections<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Connections<T> {
|
||||
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
|
||||
@@ -490,7 +511,7 @@ impl<T> Connections<T> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime")]
|
||||
impl<T: Poolable> Connections<T> {
|
||||
impl<T: Poolable> PoolInner<T> {
|
||||
/// This should *only* be called by the IdleInterval.
|
||||
fn clear_expired(&mut self) {
|
||||
let dur = self.timeout.expect("interval assumes timeout");
|
||||
@@ -533,7 +554,7 @@ pub(super) struct Pooled<T: Poolable> {
|
||||
value: Option<T>,
|
||||
is_reused: bool,
|
||||
key: Key,
|
||||
pool: WeakOpt<PoolInner<T>>,
|
||||
pool: WeakOpt<Mutex<PoolInner<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Poolable> Pooled<T> {
|
||||
@@ -577,11 +598,7 @@ impl<T: Poolable> Drop for Pooled<T> {
|
||||
}
|
||||
|
||||
if let Some(pool) = self.pool.upgrade() {
|
||||
// Pooled should not have had a real reference if pool is
|
||||
// not enabled!
|
||||
debug_assert!(pool.enabled);
|
||||
|
||||
if let Ok(mut inner) = pool.connections.lock() {
|
||||
if let Ok(mut inner) = pool.lock() {
|
||||
inner.put(self.key.clone(), value, &pool);
|
||||
}
|
||||
} else if self.key.1 == Ver::Http1 {
|
||||
@@ -638,6 +655,8 @@ impl<T: Poolable> Checkout<T> {
|
||||
}
|
||||
|
||||
fn add_waiter(&mut self) {
|
||||
debug_assert!(self.pool.is_enabled());
|
||||
|
||||
if self.waiter.is_none() {
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
let _ = rx.poll(); // park this task
|
||||
@@ -660,6 +679,8 @@ impl<T: Poolable> Future for Checkout<T> {
|
||||
|
||||
if let Some(pooled) = entry {
|
||||
Ok(Async::Ready(pooled))
|
||||
} else if !self.pool.is_enabled() {
|
||||
Err(::Error::new_canceled(Some("pool is disabled")))
|
||||
} else {
|
||||
self.add_waiter();
|
||||
Ok(Async::NotReady)
|
||||
@@ -670,7 +691,7 @@ impl<T: Poolable> Future for Checkout<T> {
|
||||
impl<T> Drop for Checkout<T> {
|
||||
fn drop(&mut self) {
|
||||
if self.waiter.take().is_some() {
|
||||
if let Ok(mut inner) = self.pool.inner.connections.lock() {
|
||||
if let Some(Ok(mut inner)) = self.pool.inner.as_ref().map(|i| i.lock()) {
|
||||
inner.clean_waiters(&self.key);
|
||||
}
|
||||
}
|
||||
@@ -681,14 +702,14 @@ impl<T> Drop for Checkout<T> {
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub(super) struct Connecting<T: Poolable> {
|
||||
key: Key,
|
||||
pool: WeakOpt<PoolInner<T>>,
|
||||
pool: WeakOpt<Mutex<PoolInner<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Poolable> Drop for Connecting<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(pool) = self.pool.upgrade() {
|
||||
// No need to panic on drop, that could abort!
|
||||
if let Ok(mut inner) = pool.connections.lock() {
|
||||
if let Ok(mut inner) = pool.lock() {
|
||||
debug_assert_eq!(
|
||||
self.key.1,
|
||||
Ver::Http2,
|
||||
@@ -718,7 +739,7 @@ impl Expiration {
|
||||
#[cfg(feature = "runtime")]
|
||||
struct IdleInterval<T> {
|
||||
interval: Interval,
|
||||
pool: WeakOpt<PoolInner<T>>,
|
||||
pool: WeakOpt<Mutex<PoolInner<T>>>,
|
||||
// This allows the IdleInterval to be notified as soon as the entire
|
||||
// Pool is fully dropped, and shutdown. This channel is never sent on,
|
||||
// but Err(Canceled) will be received when the Pool is dropped.
|
||||
@@ -749,7 +770,7 @@ impl<T: Poolable + 'static> Future for IdleInterval<T> {
|
||||
}));
|
||||
|
||||
if let Some(inner) = self.pool.upgrade() {
|
||||
if let Ok(mut inner) = inner.connections.lock() {
|
||||
if let Ok(mut inner) = inner.lock() {
|
||||
trace!("idle interval checking for expired");
|
||||
inner.clear_expired();
|
||||
continue;
|
||||
@@ -842,7 +863,7 @@ mod tests {
|
||||
let key = (Arc::new("foo".to_string()), Ver::Http1);
|
||||
let pooled = pool.pooled(c(key.clone()), Uniq(41));
|
||||
drop(pooled);
|
||||
::std::thread::sleep(pool.inner.connections.lock().unwrap().timeout.unwrap());
|
||||
::std::thread::sleep(pool.locked().timeout.unwrap());
|
||||
assert!(pool.checkout(key).poll().unwrap().is_not_ready());
|
||||
::futures::future::ok::<(), ()>(())
|
||||
}).wait().unwrap();
|
||||
@@ -858,12 +879,12 @@ mod tests {
|
||||
pool.pooled(c(key.clone()), Uniq(5));
|
||||
pool.pooled(c(key.clone()), Uniq(99));
|
||||
|
||||
assert_eq!(pool.inner.connections.lock().unwrap().idle.get(&key).map(|entries| entries.len()), Some(3));
|
||||
::std::thread::sleep(pool.inner.connections.lock().unwrap().timeout.unwrap());
|
||||
assert_eq!(pool.locked().idle.get(&key).map(|entries| entries.len()), Some(3));
|
||||
::std::thread::sleep(pool.locked().timeout.unwrap());
|
||||
|
||||
// checkout.poll() should clean out the expired
|
||||
pool.checkout(key.clone()).poll().unwrap();
|
||||
assert!(pool.inner.connections.lock().unwrap().idle.get(&key).is_none());
|
||||
assert!(pool.locked().idle.get(&key).is_none());
|
||||
|
||||
Ok::<(), ()>(())
|
||||
}).wait().unwrap();
|
||||
@@ -880,7 +901,7 @@ mod tests {
|
||||
pool.pooled(c(key.clone()), Uniq(99));
|
||||
|
||||
// pooled and dropped 3, max_idle should only allow 2
|
||||
assert_eq!(pool.inner.connections.lock().unwrap().idle.get(&key).map(|entries| entries.len()), Some(2));
|
||||
assert_eq!(pool.locked().idle.get(&key).map(|entries| entries.len()), Some(2));
|
||||
|
||||
Ok::<(), ()>(())
|
||||
}).wait().unwrap();
|
||||
@@ -911,14 +932,14 @@ mod tests {
|
||||
Ok::<_, ()>(())
|
||||
})).unwrap();
|
||||
|
||||
assert_eq!(pool.inner.connections.lock().unwrap().idle.get(&key).map(|entries| entries.len()), Some(3));
|
||||
assert_eq!(pool.locked().idle.get(&key).map(|entries| entries.len()), Some(3));
|
||||
|
||||
// Let the timer tick passed the expiration...
|
||||
rt
|
||||
.block_on(Delay::new(Instant::now() + Duration::from_millis(200)))
|
||||
.expect("rt block_on 200ms");
|
||||
|
||||
assert!(pool.inner.connections.lock().unwrap().idle.get(&key).is_none());
|
||||
assert!(pool.locked().idle.get(&key).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -950,16 +971,16 @@ mod tests {
|
||||
|
||||
// first poll needed to get into Pool's parked
|
||||
checkout1.poll().unwrap();
|
||||
assert_eq!(pool.inner.connections.lock().unwrap().waiters.get(&key).unwrap().len(), 1);
|
||||
assert_eq!(pool.locked().waiters.get(&key).unwrap().len(), 1);
|
||||
checkout2.poll().unwrap();
|
||||
assert_eq!(pool.inner.connections.lock().unwrap().waiters.get(&key).unwrap().len(), 2);
|
||||
assert_eq!(pool.locked().waiters.get(&key).unwrap().len(), 2);
|
||||
|
||||
// on drop, clean up Pool
|
||||
drop(checkout1);
|
||||
assert_eq!(pool.inner.connections.lock().unwrap().waiters.get(&key).unwrap().len(), 1);
|
||||
assert_eq!(pool.locked().waiters.get(&key).unwrap().len(), 1);
|
||||
|
||||
drop(checkout2);
|
||||
assert!(pool.inner.connections.lock().unwrap().waiters.get(&key).is_none());
|
||||
assert!(pool.locked().waiters.get(&key).is_none());
|
||||
|
||||
::futures::future::ok::<(), ()>(())
|
||||
}).wait().unwrap();
|
||||
@@ -990,6 +1011,6 @@ mod tests {
|
||||
closed: true,
|
||||
});
|
||||
|
||||
assert!(!pool.inner.connections.lock().unwrap().idle.contains_key(&key));
|
||||
assert!(!pool.locked().idle.contains_key(&key));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user