fix(client): fix a rare connection pool race condition
It's possible for `PoolInner::put` to happen between `Pool::take` and `Pool::waiter`. This merges `take` and `waiter` into using the same lock.
This commit is contained in:
@@ -198,41 +198,6 @@ impl<T: Poolable> Pool<T> {
|
|||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take(&self, key: &Key) -> Option<Pooled<T>> {
|
|
||||||
let entry = {
|
|
||||||
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| {
|
|
||||||
trace!("take? {:?}: expiration = {:?}", key, expiration.0);
|
|
||||||
// A block to end the mutable borrow on list,
|
|
||||||
// so the map below can check is_empty()
|
|
||||||
{
|
|
||||||
let popper = IdlePopper {
|
|
||||||
key,
|
|
||||||
list,
|
|
||||||
};
|
|
||||||
popper.pop(&expiration)
|
|
||||||
}
|
|
||||||
.map(|e| (e, list.is_empty()))
|
|
||||||
});
|
|
||||||
|
|
||||||
let (entry, empty) = if let Some((e, empty)) = maybe_entry {
|
|
||||||
(Some(e), empty)
|
|
||||||
} else {
|
|
||||||
// No entry found means nuke the list for sure.
|
|
||||||
(None, true)
|
|
||||||
};
|
|
||||||
if empty {
|
|
||||||
//TODO: This could be done with the HashMap::entry API instead.
|
|
||||||
inner.idle.remove(key);
|
|
||||||
}
|
|
||||||
entry
|
|
||||||
};
|
|
||||||
|
|
||||||
entry.map(|e| self.reuse(key, e.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn pooled(&self, mut connecting: Connecting<T>, value: T) -> Pooled<T> {
|
pub(super) fn pooled(&self, mut connecting: Connecting<T>, value: T) -> Pooled<T> {
|
||||||
let (value, pool_ref) = if let Some(ref enabled) = self.inner {
|
let (value, pool_ref) = if let Some(ref enabled) = self.inner {
|
||||||
match value.reserve() {
|
match value.reserve() {
|
||||||
@@ -296,23 +261,6 @@ impl<T: Poolable> Pool<T> {
|
|||||||
value: Some(value),
|
value: Some(value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
.as_ref()
|
|
||||||
.expect("pool.waiter() expects pool is enabled")
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.waiters
|
|
||||||
.entry(key)
|
|
||||||
.or_insert(VecDeque::new())
|
|
||||||
.push_back(tx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pop off this list, looking for a usable connection that hasn't expired.
|
/// Pop off this list, looking for a usable connection that hasn't expired.
|
||||||
@@ -643,15 +591,54 @@ impl<T: Poolable> Checkout<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_waiter(&mut self) {
|
fn checkout(&mut self) -> Option<Pooled<T>> {
|
||||||
debug_assert!(self.pool.is_enabled());
|
let entry = {
|
||||||
|
let mut inner = self.pool.inner.as_ref()?.lock().unwrap();
|
||||||
|
let expiration = Expiration::new(inner.timeout);
|
||||||
|
let maybe_entry = inner.idle.get_mut(&self.key)
|
||||||
|
.and_then(|list| {
|
||||||
|
trace!("take? {:?}: expiration = {:?}", self.key, expiration.0);
|
||||||
|
// A block to end the mutable borrow on list,
|
||||||
|
// so the map below can check is_empty()
|
||||||
|
{
|
||||||
|
let popper = IdlePopper {
|
||||||
|
key: &self.key,
|
||||||
|
list,
|
||||||
|
};
|
||||||
|
popper.pop(&expiration)
|
||||||
|
}
|
||||||
|
.map(|e| (e, list.is_empty()))
|
||||||
|
});
|
||||||
|
|
||||||
if self.waiter.is_none() {
|
let (entry, empty) = if let Some((e, empty)) = maybe_entry {
|
||||||
let (tx, mut rx) = oneshot::channel();
|
(Some(e), empty)
|
||||||
let _ = rx.poll(); // park this task
|
} else {
|
||||||
self.pool.waiter(self.key.clone(), tx);
|
// No entry found means nuke the list for sure.
|
||||||
self.waiter = Some(rx);
|
(None, true)
|
||||||
}
|
};
|
||||||
|
if empty {
|
||||||
|
//TODO: This could be done with the HashMap::entry API instead.
|
||||||
|
inner.idle.remove(&self.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.is_none() && self.waiter.is_none() {
|
||||||
|
let (tx, mut rx) = oneshot::channel();
|
||||||
|
let _ = rx.poll(); // park this task
|
||||||
|
|
||||||
|
trace!("checkout waiting for idle connection: {:?}", self.key);
|
||||||
|
inner
|
||||||
|
.waiters
|
||||||
|
.entry(self.key.clone())
|
||||||
|
.or_insert(VecDeque::new())
|
||||||
|
.push_back(tx);
|
||||||
|
|
||||||
|
self.waiter = Some(rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry
|
||||||
|
};
|
||||||
|
|
||||||
|
entry.map(|e| self.pool.reuse(&self.key, e.value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,14 +651,11 @@ impl<T: Poolable> Future for Checkout<T> {
|
|||||||
return Ok(Async::Ready(pooled));
|
return Ok(Async::Ready(pooled));
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = self.pool.take(&self.key);
|
if let Some(pooled) = self.checkout() {
|
||||||
|
|
||||||
if let Some(pooled) = entry {
|
|
||||||
Ok(Async::Ready(pooled))
|
Ok(Async::Ready(pooled))
|
||||||
} else if !self.pool.is_enabled() {
|
} else if !self.pool.is_enabled() {
|
||||||
Err(::Error::new_canceled(Some("pool is disabled")))
|
Err(::Error::new_canceled(Some("pool is disabled")))
|
||||||
} else {
|
} else {
|
||||||
self.add_waiter();
|
|
||||||
Ok(Async::NotReady)
|
Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user