Repository navigation
Replies: 2 comments
|
Root-caused and fixed this, verified against a live repro — not just theorizing. Confirming the reproRan the OP's script unmodified against Matches the report exactly. The exact line
288 stream = connect_response.extensions["network_stream"]
...
300 with Trace("start_tls", logger, request, kwargs) as trace:
301 stream = stream.start_tls(**kwargs)Compare this to the block twelve lines above it, which handles the other way CONNECT can fail (non-2xx status): 281 if connect_response.status < 200 or connect_response.status > 299:
282 ...
285 self._connection.close()
286 raise ProxyError(msg)That path explicitly closes And in 234 except BaseException as exc:
235 ...
238 self._requests.remove(pool_request)only removes the pending request from the queue — it never touches The fixWrap with Trace("start_tls", logger, request, kwargs) as trace:
try:
stream = stream.start_tls(**kwargs)
except BaseException:
self._connection.close()
raiseSame change needed in Proof it worksPatched the installed package locally and re-ran the exact same unmodified repro script: All three now fail cleanly with One nuance from tracing this: This is a minimal, mechanical two-line-per-file fix — happy to open the PR against |
|
May I submit an issue report? |
Uh oh!
There was an error while loading. Please reload this page.
With
httpx2==2.12.0andhttpcore2==2.12.0, a TLS handshake failure after a successful HTTP proxy CONNECT leaves the tunnel connection in the pool with anACTIVEstate.Each failure consumes another connection-pool slot. Once the number of failed handshakes reaches
max_connections, subsequent requests raisePoolTimeout, even though all requests are synchronous and executed sequentially in a single thread.This appears to be the same defect reported in the original HTTPX transport dependency, encode/httpcore discussion #921. A proposed upstream fix is available in encode/httpcore PR #1087.
Environment
httpx2==2.12.0httpcore2==2.12.0The reproduction below was executed in an isolated environment without application imports or compatibility patches.
Minimal reproduction
Save the following as
httpx2_pooltimeout.pyand run:Expected behavior
All three requests should raise the simulated
ConnectError. After each TLS failure, the failed tunnel connection should be closed and removed from the pool, leaving a slot available for the next request.If TLS succeeds on a later attempt, that request should be able to complete using the same client.
Actual behavior
ConnectErrorACTIVEConnectErrorACTIVEPoolTimeoutThe retained entries are reported as
TunnelHTTPConnectionobjects withHTTP/1.1, ACTIVE, Request Count: 1.Suspected cause
In [
TunnelHTTPConnection.handle_request()] https://github.com/pydantic/httpx2/blob/main/src/httpcore2/httpcore2/_sync/http_proxy.py), the proxy CONNECT request succeedsbefore
stream.start_tls(...)is called. If TLS setup raises, the enclosing connection object is not explicitly closed on that path.Closing the underlying network stream does not itself update the HTTP connection object's state. The tunnel can therefore remain
ACTIVE, making it neither reusable nor eligible for normal idle-connection cleanup.The connection pool's exception handler removes the failed request from
its queue, but that does not remove the stale active connection entry. Repeating this sequence exhausts the configured connection limit.
Related upstream report and proposed fix
All reactions