From 1f6807e2b6807b0c801d867e027930df60d47f1b Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 23 Oct 2026 17:23:00 +0200 Subject: [PATCH] Fix SimpleConnectionPool Waiters#addFirst linking the new node to head.prev instead of head.next Fix #6463 Motivation: Waiters#addFirst sets node.next to head.prev, i.e. the last waiter instead of the first one. When at least two waiters are queued, the forward chain skips the previous first waiter: it can no longer be polled although size still counts it. The waiter is never served (until it times out) and a later poll can return null while size() > 0, leading to a NullPointerException in SetConcurrency. addFirst is used by ConnectSuccess when a connection is established with a concurrency of 0 (e.g. an HTTP/2 server advertising MAX_CONCURRENT_STREAMS=0). Changes: Link the new node to head.next. Add a pool test with a zero concurrency connection and several queued waiters. Assisted-by: Claude Opus 5.5 --- .../net/impl/pool/SimpleConnectionPool.java | 2 +- .../net/impl/pool/ConnectionPoolTest.java | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/vertx/core/net/impl/pool/SimpleConnectionPool.java b/src/main/java/io/vertx/core/net/impl/pool/SimpleConnectionPool.java index dd7c95bb149..c11a7b88426 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/SimpleConnectionPool.java +++ b/src/main/java/io/vertx/core/net/impl/pool/SimpleConnectionPool.java @@ -850,7 +850,7 @@ void addFirst(PoolWaiter node) { } node.queued = true; node.prev = head; - node.next = head.prev; + node.next = head.next; head.next.prev = node; head.next = node; size++; diff --git a/src/test/java/io/vertx/core/net/impl/pool/ConnectionPoolTest.java b/src/test/java/io/vertx/core/net/impl/pool/ConnectionPoolTest.java index 121ed8838d7..2d3463de50e 100644 --- a/src/test/java/io/vertx/core/net/impl/pool/ConnectionPoolTest.java +++ b/src/test/java/io/vertx/core/net/impl/pool/ConnectionPoolTest.java @@ -206,6 +206,30 @@ public void testEmptyConcurrency() { await(); } + @Test + public void testEmptyConcurrencyWithManyWaiters() { + ContextInternal context = vertx.createEventLoopContext(); + ConnectionManager mgr = new ConnectionManager(); + ConnectionPool pool = ConnectionPool.pool(mgr, new int[] { 1 }, 3); + Connection expected = new Connection(); + AtomicInteger seq = new AtomicInteger(); + for (int i = 0;i < 3;i++) { + int expectedSeq = i + 1; + pool.acquire(context, 0, onSuccess(lease -> { + assertSame(lease.get(), expected); + assertEquals(expectedSeq, seq.getAndIncrement()); + if (expectedSeq == 3) { + testComplete(); + } + })); + } + ConnectionRequest request = mgr.assertRequest(); + request.concurrency(0).connect(expected, 0); + assertEquals(0, seq.getAndIncrement()); + request.concurrency(3); + await(); + } + @Test public void testDecreaseConcurrency() throws Exception { ConnectionManager mgr = new ConnectionManager();