Skip to content

Commit b88c774

Browse files
authored
fix(pool): wake Cache waiters in FIFO order (#298)
`Shared::put` handed a returned connection to the most recently parked waiter (`Vec::pop`), so under sustained load where checkouts outpace available connections the oldest waiters were never served. They waited until the load subsided, producing unbounded tail latency while newer checkouts completed promptly. Store waiters in a `VecDeque` and wake from the front, so a returned connection goes to the longest-waiting checkout. Service order is now arrival order; a waiter's worst-case wait is bounded by the number of waiters ahead of it rather than by the duration of the load.
1 parent 911d0f2 commit b88c774

1 file changed

Lines changed: 73 additions & 4 deletions

File tree

src/client/pool/cache.rs

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub use self::internal::Cached;
1818
// more public, but we can't change type shapes (generics) once things are
1919
// public.
2020
mod internal {
21+
use std::collections::VecDeque;
2122
use std::fmt;
2223
use std::future::Future;
2324
use std::pin::Pin;
@@ -108,7 +109,7 @@ mod internal {
108109
#[derive(Debug)]
109110
pub struct Shared<S> {
110111
services: Vec<S>,
111-
waiters: Vec<oneshot::Sender<S>>,
112+
waiters: VecDeque<oneshot::Sender<S>>,
112113
}
113114

114115
// impl Builder
@@ -149,7 +150,7 @@ mod internal {
149150
events: self.events,
150151
shared: Arc::new(Mutex::new(Shared {
151152
services: Vec::new(),
152-
waiters: Vec::new(),
153+
waiters: VecDeque::new(),
153154
})),
154155
}
155156
}
@@ -205,7 +206,7 @@ mod internal {
205206
}
206207

207208
let (tx, rx) = oneshot::channel();
208-
locked.waiters.push(tx);
209+
locked.waiters.push_back(tx);
209210
rx
210211
};
211212

@@ -363,7 +364,7 @@ mod internal {
363364
impl<V> Shared<V> {
364365
fn put(&mut self, val: V) {
365366
let mut val = Some(val);
366-
while let Some(tx) = self.waiters.pop() {
367+
while let Some(tx) = self.waiters.pop_front() {
367368
if !tx.is_closed() {
368369
match tx.send(val.take().unwrap()) {
369370
Ok(()) => break,
@@ -491,4 +492,72 @@ mod tests {
491492
let cached = f.await.expect("call");
492493
drop(cached);
493494
}
495+
496+
// A returned connection is handed to waiters in the order they parked
497+
// (FIFO), so a waiter cannot be starved by later arrivals.
498+
#[tokio::test]
499+
async fn test_waiters_woken_in_fifo_order() {
500+
use std::future::Future;
501+
use std::task::{Context, Poll, Waker};
502+
503+
let (mock, mut handle) = tower_test::mock::pair::<u32, &'static str>();
504+
let mut cache = super::builder().build(mock);
505+
handle.allow(16);
506+
507+
// Establish one connection and hold it, so the next checkouts find no
508+
// idle service and park.
509+
std::future::poll_fn(|cx| cache.poll_ready(cx))
510+
.await
511+
.unwrap();
512+
let held = future::join(cache.call(0), async {
513+
assert_request_eq!(handle, 0).send_response("conn");
514+
})
515+
.await
516+
.0
517+
.expect("call");
518+
519+
// Park three checkouts in order. Each misses and starts a connect, but
520+
// the connect is never completed, so each parks on its waiter.
521+
let mut cx = Context::from_waker(Waker::noop());
522+
std::future::poll_fn(|cx| cache.poll_ready(cx))
523+
.await
524+
.unwrap();
525+
let mut first = Box::pin(cache.call(1));
526+
assert!(first.as_mut().poll(&mut cx).is_pending());
527+
std::future::poll_fn(|cx| cache.poll_ready(cx))
528+
.await
529+
.unwrap();
530+
let mut second = Box::pin(cache.call(2));
531+
assert!(second.as_mut().poll(&mut cx).is_pending());
532+
std::future::poll_fn(|cx| cache.poll_ready(cx))
533+
.await
534+
.unwrap();
535+
let mut third = Box::pin(cache.call(3));
536+
assert!(third.as_mut().poll(&mut cx).is_pending());
537+
538+
// Returning the connection wakes the oldest waiter first.
539+
drop(held);
540+
let first = match first.as_mut().poll(&mut cx) {
541+
Poll::Ready(r) => r.expect("first"),
542+
Poll::Pending => panic!("oldest waiter was not woken first"),
543+
};
544+
assert!(second.as_mut().poll(&mut cx).is_pending());
545+
assert!(third.as_mut().poll(&mut cx).is_pending());
546+
547+
// Returning it again wakes the next-oldest, then the last.
548+
drop(first);
549+
let second = match second.as_mut().poll(&mut cx) {
550+
Poll::Ready(r) => r.expect("second"),
551+
Poll::Pending => panic!("second waiter was not woken next"),
552+
};
553+
assert!(third.as_mut().poll(&mut cx).is_pending());
554+
555+
drop(second);
556+
match third.as_mut().poll(&mut cx) {
557+
Poll::Ready(r) => {
558+
r.expect("third");
559+
}
560+
Poll::Pending => panic!("last waiter was not woken"),
561+
}
562+
}
494563
}

0 commit comments

Comments
 (0)