diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/Http2ConnectionState.java b/client/src/main/java/org/asynchttpclient/netty/channel/Http2ConnectionState.java index 3fe9a907a..4cfd3a8ef 100644 --- a/client/src/main/java/org/asynchttpclient/netty/channel/Http2ConnectionState.java +++ b/client/src/main/java/org/asynchttpclient/netty/channel/Http2ConnectionState.java @@ -123,9 +123,10 @@ public boolean offerPendingOpener(Runnable opener) { * {@link #releaseStream()}. Returns {@code false} — without queuing — when the connection is * already draining or closed, or when the pending queue is already at {@link #MAX_PENDING_OPENERS}: in * each case the caller MUST fail the request itself rather than let it sit until the request timeout fires - * (Issue #2160). A draining/closed connection never runs a queued opener ({@link #drainPendingOpeners} only - * re-offers it, and {@link #failPendingOpeners} has already drained the queue); a full queue means the peer - * is starving slots and the request would otherwise grow heap without bound. + * (Issue #2160). A draining/closed connection never runs a queued opener ({@link #drainPendingOpeners} leaves + * it queued, since {@link #tryAcquireStream()} refuses once draining/closed, and {@link #failPendingOpeners} + * has already drained the queue); a full queue means the peer is starving slots and the request would + * otherwise grow heap without bound. *

* Race-free against {@link #failPendingOpeners}: that method sets {@code closed} and drains the queue under * {@code pendingLock}. An opener enqueued before the drain runs is caught by the drain; an enqueue attempt diff --git a/client/src/test/java/org/asynchttpclient/netty/channel/Http2ConnectionStateTest.java b/client/src/test/java/org/asynchttpclient/netty/channel/Http2ConnectionStateTest.java index 7873ab7f7..a6be0ff99 100644 --- a/client/src/test/java/org/asynchttpclient/netty/channel/Http2ConnectionStateTest.java +++ b/client/src/test/java/org/asynchttpclient/netty/channel/Http2ConnectionStateTest.java @@ -330,6 +330,9 @@ public void queuedOpenerRunsOutsidePendingLock() throws Exception { @Test public void raisedLimitDrainsMultiplePendingOpenersInOrder() { + // Pins the SETTINGS-raise wakeup: every freed slot is drained in one pass, not just the first (a + // missed-wakeup here is the Issue #2160 silent-timeout class). Slot accounting on a throwing opener + // is covered separately by throwingBatchOpenerLeavesRemainingQueueDrainable. Http2ConnectionState state = new Http2ConnectionState(); state.updateMaxConcurrentStreams(1); assertTrue(state.tryAcquireStream());