Skip to content

Commit 862b845

Browse files
authored
Fix race condition causing IllegalStateException: Promise already completed (#6170)
See #6169 A bug in SimpleConnectionPool allows a pool waiter's promise to be completed twice under concurrent recycle + cancel with a short connect/acquire timeout. Recycle, ConnectSuccess (secondary waiter loop), and SetConcurrency polled waiters without setting waiter.disposed=true. Cancel therefore saw disposed=false, reported cancellation success, and failed the promise, racing with the recycle path that had already completed it. Some portions of this content were created with the assistance of Claude Code. Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
1 parent 606b1d4 commit 862b845

2 files changed

Lines changed: 47 additions & 2 deletions

File tree

vertx-core/src/main/java/io/vertx/core/internal/pool/SimpleConnectionPool.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,9 @@ public void run() {
323323
c += m;
324324
leases = new LeaseImpl[m];
325325
for (int i = 0;i < m;i++) {
326-
leases[i] = new LeaseImpl<>(slot, pool.waiters.poll().handler);
326+
PoolWaiter<C> w = pool.waiters.poll();
327+
w.disposed = true;
328+
leases[i] = new LeaseImpl<>(slot, w.handler);
327329
}
328330
} else {
329331
leases = null;
@@ -465,7 +467,9 @@ public Task execute(SimpleConnectionPool<C> pool) {
465467
if (m > 0) {
466468
extra = new LeaseImpl[m];
467469
for (int i = 0;i < m;i++) {
468-
extra[i] = new LeaseImpl<>(slot, pool.waiters.poll().handler);
470+
PoolWaiter<C> w = pool.waiters.poll();
471+
w.disposed = true;
472+
extra[i] = new LeaseImpl<>(slot, w.handler);
469473
}
470474
slot.usage += m;
471475
return new Task() {
@@ -731,6 +735,7 @@ public Task execute(SimpleConnectionPool<C> pool) {
731735
if (!pool.closed && slot.connection != null) {
732736
PoolWaiter<C> waiter;
733737
if (slot.usage <= slot.concurrency && (waiter = pool.waiters.poll()) != null) {
738+
waiter.disposed = true;
734739
LeaseImpl<C> lease = new LeaseImpl<>(slot, waiter.handler);
735740
return new Task() {
736741
@Override

vertx-core/src/test/java/io/vertx/tests/pool/ConnectionPoolTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,46 @@ public void onConnect(PoolWaiter<Connection> waiter) {
876876
await();
877877
}
878878

879+
@Test
880+
public void testCancelWaiterDispatchedByRecycle() throws Exception {
881+
waitFor(1);
882+
ContextInternal context = vertx.createEventLoopContext();
883+
ConnectionManager mgr = new ConnectionManager();
884+
ConnectionPool<Connection> pool = ConnectionPool.pool(mgr, new int[]{1});
885+
886+
// Step 1: Acquire and establish a connection
887+
CountDownLatch connectedLatch = new CountDownLatch(1);
888+
AtomicReference<Lease<Connection>> leaseRef = new AtomicReference<>();
889+
pool.acquire(context, 0, TestUtils.onSuccess2(lease -> {
890+
leaseRef.set(lease);
891+
connectedLatch.countDown();
892+
}));
893+
mgr.assertRequest().connect(new Connection(), 0);
894+
TestUtils.awaitLatch(connectedLatch);
895+
896+
// Step 2: Queue a second waiter (pool is at capacity)
897+
CompletableFuture<PoolWaiter<Connection>> waiterFut = new CompletableFuture<>();
898+
pool.acquire(context, new PoolWaiter.Listener<>() {
899+
@Override
900+
public void onEnqueue(PoolWaiter<Connection> waiter) {
901+
waiterFut.complete(waiter);
902+
}
903+
}, 0, (res, err) -> {
904+
});
905+
PoolWaiter<Connection> waiter = waiterFut.get(10, TimeUnit.SECONDS);
906+
Assert.assertEquals(1, pool.waiters());
907+
908+
// Step 3: Recycle the first lease - dispatches connection to the queued waiter
909+
leaseRef.get().recycle();
910+
911+
// Step 4: Cancel the waiter after recycle dispatched it - should return false
912+
pool.cancel(waiter, TestUtils.onSuccess2(cancelled -> {
913+
Assert.assertFalse(cancelled);
914+
testComplete();
915+
}));
916+
await();
917+
}
918+
879919
@Test
880920
public void testConnectionSelector() throws Exception {
881921
waitFor(1);

0 commit comments

Comments
 (0)