TCPClient.connect: close the new SSLIOStream on TLS handshake timeout#3662
Open
HrachShah wants to merge 1 commit into
Open
TCPClient.connect: close the new SSLIOStream on TLS handshake timeout#3662HrachShah wants to merge 1 commit into
HrachShah wants to merge 1 commit into
Conversation
… (issue tornadoweb#3614) When TCPClient.connect is called with both ssl_options and a timeout, a TLS handshake timeout leaks the underlying socket. IOStream.start_tls transfers socket ownership to a new SSLIOStream *before* the handshake completes and sets self.socket to None on the original stream. The gen.with_timeout call around start_tls only raises TimeoutError to the caller; the original stream is now a no-op (its socket is gone) and the new SSLIOStream that actually owns the socket is reachable only through the inner future -- which gen.with_timeout deliberately leaves running, per its docstring. Capture the future returned by start_tls so that, on timeout, we register a done-callback that closes the resulting SSLIOStream. If the handshake eventually fails, SSLIOStream already closes its socket on the failure path, so the callback only acts when the future resolved successfully. Regression test in tcpclient_test.py replaces start_tls with a stub that builds a real SSLIOStream around the underlying socket but never completes the handshake, instruments close() to record the stream, and asserts that the SSLIOStream is closed when the timeout fires and the future is subsequently resolved. All 28 existing tcpclient tests still pass.
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.
What
Closes the socket leak described in #3614: when
TCPClient.connectis called with bothssl_optionsand atimeout, a TLS handshake timeout leaks the underlying socket.Why
IOStream.start_tlstransfers socket ownership to a newSSLIOStreambefore the handshake completes and setsself.socket = Noneon the original stream. Thegen.with_timeoutcall aroundstart_tlsonly raisesTimeoutErrorto the caller. The original stream is now a no-op (its socket is gone) and the newSSLIOStreamthat actually owns the socket is reachable only through the inner future — whichgen.with_timeoutdeliberately leaves running, per its docstring.The net effect: the socket fd is unreachable from the caller, is not closed by the IOLoop, and is not garbage-collected because the inner future still holds a reference to the
SSLIOStream.Fix
Capture the future returned by
start_tlsso that, on timeout, we register a done-callback that closes the resultingSSLIOStream. If the handshake eventually fails,SSLIOStreamalready closes its socket on the failure path, so the callback only acts when the future resolved successfully. The originalIOStreamis not touched: itssocketis alreadyNoneand the SSL fd has moved to the new stream.Test
test_tls_handshake_timeout_closes_ssl_streamintornado/test/tcpclient_test.py:TestTCPServer.IOStream.start_tlswith a stub that replicates enough of the real implementation to put the underlying socket into a newSSLIOStream, but returns an unresolvedFutureso the connect-timeout fires while the handshake is still pending.SSLIOStream.closeto record the stream.TimeoutError.SSLIOStreamthat owns the socket was closed exactly once.All 28 existing tcpclient tests still pass.