Conversation
…nup() when disconnectEndpoint() throws When the underlying socket is in a broken state (e.g. RST mid-body from an unstable tunnel), ExecRuntime#disconnectEndpoint() throws IOException from endpoint.close(), which caused the following ExecRuntime#discardEndpoint() to be skipped. discardEndpoint() is the only path that returns the lease to the pool (its manager.release(...) runs inside a finally block), so the connection was left permanently marked as leased. Wrap the two calls in a try/finally so discardEndpoint() runs regardless of the outcome of disconnectEndpoint(). discardEndpoint() is idempotent (endpointRef.getAndSet(null)) and its manager.release(...) is already inside a finally, so the change is safe for the healthy path. Signed-off-by: Victor Alekseev <krocodl@gmail.com>
…int() failure Adds testPoolLeaseReturnedWhenDisconnectEndpointThrows which wires a real InternalExecRuntime to a fake HttpClientConnectionManager whose leased ConnectionEndpoint throws IOException from close(), then triggers ResponseEntityProxy.cleanup() via streamAbort() and asserts the managerreceived exactly one release() call. Signed-off-by: Victor Alekseev <krocodl@gmail.com>
Member
|
@krocodl The release notes changes are misplaced. 5.7-alpha1 has already been released. I will make sure you will get credit for the fix and a mention in the release notes. Please put everything you want to be in the release notes to the commit message. Looks good otherwise. |
5.7-alpha1 has already been released, so the release notes entry added in f36f1cc does not belong there. Revert that hunk; the maintainer will add the entry to the notes for the next release. Release notes: * HTTPCLIENT-2432: Fix connection-pool leak in ResponseEntityProxy#cleanup() when ExecRuntime#disconnectEndpoint() throws before discardEndpoint(). Contributed by Victor Alekseev <krocodl at gmail.com> Signed-off-by: Victor Alekseev <krocodl@gmail.com>
Author
|
@ok2c done |
ok2c
approved these changes
Sep 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Jira: https://issues.apache.org/jira/browse/HTTPCLIENT-2432
Summary
ResponseEntityProxy#cleanup()invokes two sequential calls without afinally:(A)InternalExecRuntime#disconnectEndpoint()callsendpoint.close()and can throwIOExceptionwhen the underlying socket is in a broken state (e.g. RST mid-body from an unstable tunnel). It does not clearendpointRef.(B)InternalExecRuntime#discardEndpoint()is the only path that returns the lease to the pool — it doesendpointRef.getAndSet(null)and, inside afinally,manager.release(endpoint, null, TimeValue.ZERO_MILLISECONDS).When
(A)throws,(B)is skipped.endpointRefstays populated,manager.release(...)is never invoked, andPoolingHttpClientConnectionManagercontinues to count the connection asleasedfor the entire lifetime of the client. Under any retry stack above the client, a single flapping tunnel can accumulate many leaked slots in one run.Fix
Wrap
(A)intryand put(B)infinallyso the lease is always returned:Safety:
discardEndpoint()is idempotent — first line isendpointRef.getAndSet(null), so a redundant call after a priorreleaseEndpoint()/discardEndpoint()is a no-op.discardEndpoint()is already in afinally, soendpoint.close(CloseMode.IMMEDIATE)throwing on a dead socket does not preventmanager.release(...).endpointRefis already null whencleanup()runs (cleared byreleaseConnection()/discardConnection()), so the addedfinallyno-ops.Test plan
TestResponseEntityProxy#testCleanupDiscardsEndpointWhenDisconnectEndpointThrows— mocksExecRuntime.disconnectEndpoint()to throwIOException, verifiesdiscardEndpoint()is still invoked. Without the fix this fails (discardEndpoint()never called); with the fix it passes.mvn -pl httpclient5 -am test -Dtest=TestResponseEntityProxy— 4/4 green locally.DCO
Commit is
Signed-off-by: Victor Alekseev <krocodl@gmail.com>.