Description
Issue
It's possible to trigger more approvals than are necessary, in turn grabbing more connections than we need. This happens when we drop a connection. The drop produces a notify, which doesn't get used until the pool is empty. The first Pool::get()
call on an empty pool will spawn an connect task, immediately complete notify.notified().await
, then spawn a second connect task. Both will connect and we'll end up with 1 more connection than we need.
This gets worse if one of those connections is broken when it gets returned to the pool. We end up calling notify.notify_waiters();
, which means all connections in the PoolInner::get()
loop could spawn an extra connect task. This should just be a single notify.notify_one();
call.
Line 161 in cb99697
Affected Versions
I have only tested on 0.8.5
Fix
Not sure on this one. I think removing the notify.notify_one()
call from the drop path would fix this specific issue, but it would hurt performance and could cause other issues (e.g. if the pool is at max capacity and we have no inflight connect tasks).
We should definitely change notify.notify_waiters()
-> notify.notify_one()
in the broken connection case if possible.
Reproduction
#[tokio::test]
async fn test_notify() {
let pool = Pool::builder()
.min_idle(0)
.max_size(100)
.queue_strategy(QueueStrategy::Lifo)
.build(OkManager::<FakeConnection>::new())
.await
.unwrap();
// The first get should
// 1) see nothing in the pool,
// 2) spawn a single connect task,
// 3) get notified of the new connection and grab it from the pool
let conn_0 = pool.get().await.expect("should connect");
// Dropping the connection queues up a notify
drop(conn_0);
// The second get should
// 1) see the first connection in the pool and grab it
// NB: This does not consume the notify
let _conn_1: PooledConnection<OkManager<FakeConnection>> = pool.get().await.expect("should connect");
// The third get will
// 1) see nothing in the pool,
// 2) spawn a single connect task,
// 3) get notified from the dropped connection
// 4) see nothing in the pool,
// 5) spawn a single connect task (oh no!),
// 6) get notified of the new connection and grab it from the pool
let _conn_2: PooledConnection<OkManager<FakeConnection>> = pool.get().await.expect("should connect");
assert_eq!(pool.state().connections, 2);
}
thread 'test_notify' panicked at bb8/tests/test.rs:1160:5:
assertion `left == right` failed
left: 3
right: 2