Skip to content

Commit 4133181

Browse files
quininerseanmonstar
authored andcommitted
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.
1 parent 0c1e182 commit 4133181

File tree

1 file changed

+48
-64
lines changed

1 file changed

+48
-64
lines changed

src/client/pool.rs

Lines changed: 48 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -198,41 +198,6 @@ impl<T: Poolable> Pool<T> {
198198
.unwrap_or(0)
199199
}
200200

201-
fn take(&self, key: &Key) -> Option<Pooled<T>> {
202-
let entry = {
203-
let mut inner = self.inner.as_ref()?.lock().unwrap();
204-
let expiration = Expiration::new(inner.timeout);
205-
let maybe_entry = inner.idle.get_mut(key)
206-
.and_then(|list| {
207-
trace!("take? {:?}: expiration = {:?}", key, expiration.0);
208-
// A block to end the mutable borrow on list,
209-
// so the map below can check is_empty()
210-
{
211-
let popper = IdlePopper {
212-
key,
213-
list,
214-
};
215-
popper.pop(&expiration)
216-
}
217-
.map(|e| (e, list.is_empty()))
218-
});
219-
220-
let (entry, empty) = if let Some((e, empty)) = maybe_entry {
221-
(Some(e), empty)
222-
} else {
223-
// No entry found means nuke the list for sure.
224-
(None, true)
225-
};
226-
if empty {
227-
//TODO: This could be done with the HashMap::entry API instead.
228-
inner.idle.remove(key);
229-
}
230-
entry
231-
};
232-
233-
entry.map(|e| self.reuse(key, e.value))
234-
}
235-
236201
pub(super) fn pooled(&self, mut connecting: Connecting<T>, value: T) -> Pooled<T> {
237202
let (value, pool_ref) = if let Some(ref enabled) = self.inner {
238203
match value.reserve() {
@@ -296,23 +261,6 @@ impl<T: Poolable> Pool<T> {
296261
value: Some(value),
297262
}
298263
}
299-
300-
fn waiter(&self, key: Key, tx: oneshot::Sender<T>) {
301-
debug_assert!(
302-
self.is_enabled(),
303-
"pool.waiter() should not be called if disabled",
304-
);
305-
trace!("checkout waiting for idle connection: {:?}", key);
306-
self.inner
307-
.as_ref()
308-
.expect("pool.waiter() expects pool is enabled")
309-
.lock()
310-
.unwrap()
311-
.waiters
312-
.entry(key)
313-
.or_insert(VecDeque::new())
314-
.push_back(tx);
315-
}
316264
}
317265

318266
/// Pop off this list, looking for a usable connection that hasn't expired.
@@ -643,15 +591,54 @@ impl<T: Poolable> Checkout<T> {
643591
}
644592
}
645593

646-
fn add_waiter(&mut self) {
647-
debug_assert!(self.pool.is_enabled());
594+
fn checkout(&mut self) -> Option<Pooled<T>> {
595+
let entry = {
596+
let mut inner = self.pool.inner.as_ref()?.lock().unwrap();
597+
let expiration = Expiration::new(inner.timeout);
598+
let maybe_entry = inner.idle.get_mut(&self.key)
599+
.and_then(|list| {
600+
trace!("take? {:?}: expiration = {:?}", self.key, expiration.0);
601+
// A block to end the mutable borrow on list,
602+
// so the map below can check is_empty()
603+
{
604+
let popper = IdlePopper {
605+
key: &self.key,
606+
list,
607+
};
608+
popper.pop(&expiration)
609+
}
610+
.map(|e| (e, list.is_empty()))
611+
});
648612

649-
if self.waiter.is_none() {
650-
let (tx, mut rx) = oneshot::channel();
651-
let _ = rx.poll(); // park this task
652-
self.pool.waiter(self.key.clone(), tx);
653-
self.waiter = Some(rx);
654-
}
613+
let (entry, empty) = if let Some((e, empty)) = maybe_entry {
614+
(Some(e), empty)
615+
} else {
616+
// No entry found means nuke the list for sure.
617+
(None, true)
618+
};
619+
if empty {
620+
//TODO: This could be done with the HashMap::entry API instead.
621+
inner.idle.remove(&self.key);
622+
}
623+
624+
if entry.is_none() && self.waiter.is_none() {
625+
let (tx, mut rx) = oneshot::channel();
626+
let _ = rx.poll(); // park this task
627+
628+
trace!("checkout waiting for idle connection: {:?}", self.key);
629+
inner
630+
.waiters
631+
.entry(self.key.clone())
632+
.or_insert(VecDeque::new())
633+
.push_back(tx);
634+
635+
self.waiter = Some(rx);
636+
}
637+
638+
entry
639+
};
640+
641+
entry.map(|e| self.pool.reuse(&self.key, e.value))
655642
}
656643
}
657644

@@ -664,14 +651,11 @@ impl<T: Poolable> Future for Checkout<T> {
664651
return Ok(Async::Ready(pooled));
665652
}
666653

667-
let entry = self.pool.take(&self.key);
668-
669-
if let Some(pooled) = entry {
654+
if let Some(pooled) = self.checkout() {
670655
Ok(Async::Ready(pooled))
671656
} else if !self.pool.is_enabled() {
672657
Err(::Error::new_canceled(Some("pool is disabled")))
673658
} else {
674-
self.add_waiter();
675659
Ok(Async::NotReady)
676660
}
677661
}

0 commit comments

Comments
 (0)