Conversation
Closing the socket returned by `Proxy#connect` without a block only half-closed the CONNECT request body, so the proxy client's connection stayed leased (and `Client#close` blocked) until the proxy closed its end of the tunnel. Closing it now stops the pipe and closes the CONNECT request and response with an error. The block form closes the same way. `ConnectFailure#body` also exposes up to 8 KiB of the proxy's response body. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Closing the tunnel stopped the pipe's writer and closed the CONNECT request with an error, discarding any data written to the socket that hadn't been forwarded yet (e.g. `QUIT\r\n` followed immediately by `close`). Instead, stop only the reader and close the socket, so the writer forwards the remaining data and ends the request normally. HTTP/2 then resets the stream with `NO_ERROR` and HTTP/1 closes the connection. The block form closes the same way. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Fixed in 351ef6d: closing a tunnel no longer drops pending writes. Problem: writing What changed:
Trade-offs:
I've updated the PR description to match. |
This branch has not been deployed
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.
Problem
When
Proxy#connectis called without a block, it returnspipe.to_ioand drops theBody::Pipe. Proxied endpoints always connect this way, throughSSLEndpoint#connectand the client connection pool, soPipe#closenever runs. Closing the returned socket only half-closes the CONNECT request body. The pipe's reader keeps reading the CONNECT response until the proxy closes its end. Until then the proxy client's connection stays leased, andClient#closeblocks with "Waiting for … pool to drain".Changes
Proxy#connectwithout a block now returns the pipe's socket extended withProxy::Tunnel. Itsclosecalls the newBody::Pipe#finish, which stops the reader (closing the CONNECT response) and closes the socket. The pipe's writer then forwards any data already written to the socket and ends the CONNECT request normally. HTTP/2 then resets the stream withRST_STREAM(NO_ERROR), via the orderly-shutdown path from Preserve orderly HTTP/2 duplex shutdown. #248, and HTTP/1 closes the connection, which can't be reused after a tunnel anyway. It stays a realSocketbecauseOpenSSL::SSL::SSLSocketrequires one.Pipe#finishtoo. Previously it calledPipe#close, which stopped the writer and discarded data written just before the block exited.ConnectFailure#bodyexposes up to 8 KiB of the proxy's response body, since proxies such as smokescreen put the rejection reason there. I/O and protocol errors while reading it are ignored. Anything else, such asAsync::TimeoutError, propagates to the caller.Notes for review
Client#closeon the proxy client immediately afterwards may briefly log "Waiting for … pool to drain", as it already does onmain, but it no longer waits for the proxy to close its end.ConnectFailure#bodycan block. For example, an HTTP/1 error response with nocontent-lengthon a connection the proxy keeps open. It is bounded only by the caller's own timeout, and this is documented onConnectFailure#body.main.🤖 Generated with Claude Code