| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
On a redirect / 401 / 407 / retry replay, sendNextRequest re-enters NettyRequestSender.sendRequestWithNewChannel from AsyncHttpClientHandler.channelRead — on the Netty event loop. acquirePartitionLockLazily then called the connection semaphore's blocking acquireChannelLock (Semaphore.tryAcquire(acquireFreeChannelTimeout)), parking the event-loop thread for up to the configured timeout when a finite maxConnections / maxConnectionsPerHost limit is saturated. Blocking the loop stalls every other connection it serves, and the permit may only be released by a task queued on that same loop. The sibling waitForHttp2Connection already guards this with isOnEventLoop(); this path did not. Acquire the permit non-blocking when on the event loop: fail fast (the request still gets its single non-blocking HTTP/2-reuse poll before aborting) instead of parking the loop. Off the loop — the initial execute() on the caller thread — the configured blocking wait is unchanged. - ConnectionSemaphore: add a default acquireChannelLock(key, nonBlocking) overload (default delegates to the blocking form, so custom implementations are unaffected). - Max/PerHost/Combined limiters override it with a non-blocking tryAcquire(). - NettyResponseFuture.acquirePartitionLockLazily(boolean) threads the flag through; the call site passes isOnEventLoop(). Gated behind non-default config (a positive cap AND a positive acquireFreeChannelTimeout); inert under defaults. Adds SemaphoreTest coverage that the non-blocking acquire fails fast instead of waiting and that the default overload delegates.
Motivation: #2226 made the connection-permit acquire non-blocking on the event loop. CombinedConnectionSemaphore's non-blocking path takes the global permit first and then the per-host permit, releasing the global one if the per-host permit is unavailable. That releaseGlobal branch is the single place the non-blocking path can leak the global permit, yet it had no coverage: the existing combinedNonBlockingFailsFastWhenExhausted uses equal global and per-host limits (1, 1), so the acquire is rejected at the global gate and never reaches the per-host rejection where releaseGlobal runs. Modification: Add combinedNonBlockingReleasesGlobalPermitWhenPerHostExhausted using a wider global limit (2) than per-host (1). The non-blocking acquire passes the global gate, is rejected by the per-host limit (asserted as TooManyConnectionsPerHostException), and a follow-up non-blocking acquire for a different host must succeed — proving the global permit taken during the failed attempt was released rather than leaked. Result: The global-permit release path of the non-blocking combined acquire is now covered; a regression that leaked the global permit on per-host rejection would starve other hosts of the global permit and fail this test.
Motivation: #2226 made the connection-permit acquire non-blocking on the event loop. CombinedConnectionSemaphore's non-blocking path takes the global permit first and then the per-host permit, releasing the global one if the per-host permit is unavailable. That releaseGlobal branch is the single place the non-blocking path can leak the global permit, yet it had no coverage: the existing combinedNonBlockingFailsFastWhenExhausted uses equal global and per-host limits (1, 1), so the acquire is rejected at the global gate and never reaches the per-host rejection where releaseGlobal runs. Modification: Add combinedNonBlockingReleasesGlobalPermitWhenPerHostExhausted using a wider global limit (2) than per-host (1). The non-blocking acquire passes the global gate, is rejected by the per-host limit (asserted as TooManyConnectionsPerHostException), and a follow-up non-blocking acquire for a different host must succeed; proving the global permit taken during the failed attempt was released rather than leaked. Result: The global-permit release path of the non-blocking combined acquire is now covered; a regression that leaked the global permit on per-host rejection would starve other hosts of the global permit and fail this test.
Motivation: #2226 made the connection-permit acquire non-blocking on the event loop. CombinedConnectionSemaphore's non-blocking path takes the global permit first and then the per-host permit, releasing the global one if the per-host permit is unavailable. That releaseGlobal branch is the single place the non-blocking path can leak the global permit, yet it had no coverage: the existing combinedNonBlockingFailsFastWhenExhausted uses equal global and per-host limits (1, 1), so the acquire is rejected at the global gate and never reaches the per-host rejection where releaseGlobal runs. Modification: Add combinedNonBlockingReleasesGlobalPermitWhenPerHostExhausted using a wider global limit (2) than per-host (1). The non-blocking acquire passes the global gate, is rejected by the per-host limit (asserted as TooManyConnectionsPerHostException), and a follow-up non-blocking acquire for a different host must succeed; proving the global permit taken during the failed attempt was released rather than leaked. Result: The global-permit release path of the non-blocking combined acquire is now covered; a regression that leaked the global permit on per-host rejection would starve other hosts of the global permit and fail this test.
| Back | FazBrowse Home | New Git URL |
On a redirect / 401 / 407 / retry replay, sendNextRequest re-enters NettyRequestSender.sendRequestWithNewChannel from AsyncHttpClientHandler.channelRead — on the Netty event loop. acquirePartitionLockLazily then called the connection semaphore's blocking acquireChannelLock (Semaphore.tryAcquire(acquireFreeChannelTimeout)), parking the event-loop thread for up to the configured timeout when a finite maxConnections / maxConnectionsPerHost limit is saturated. Blocking the loop stalls every other connection it serves, and the permit may only be released by a task queued on that same loop. The sibling waitForHttp2Connection already guards this with isOnEventLoop(); this path did not.
Acquire the permit non-blocking when on the event loop: fail fast (the request still gets its single non-blocking HTTP/2-reuse poll before aborting) instead of parking the loop. Off the loop — the initial execute() on the caller thread — the configured blocking wait is unchanged.
Gated behind non-default config (a positive cap AND a positive acquireFreeChannelTimeout); inert under defaults. Adds SemaphoreTest coverage that the non-blocking acquire fails fast instead of waiting and that the default overload delegates.