Repository navigation
Conversation
When a connect with sslnegotiation=postgres is cancelled (for example
by the connect timeout) while loop.create_connection() waits for its
transport, the transport is closed and connection_lost() later sets
TLSUpgradeProto.on_data with ConnectionError('unexpected connection_lost()
call'). Nobody awaits that future, so asyncio logs "Future exception
was never retrieved". The same happens when the cancellation arrives
after on_data already got its exception.
Keep a reference to the upgrade protocol and discard its future on
failure, the way __connect_addr() already does for 'connected'.
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.
When a connect using
sslnegotiation=postgres(the default with ansslcontext) is cancelled whileloop.create_connection()waits for its transport, the transport is closed andTLSUpgradeProto.connection_lost()then setson_datawithConnectionError('unexpected connection_lost() call'). Nobody awaits that future, so asyncio logs:The cancellation can come from the connect's own
timeoutor from a caller'sasyncio.wait_for. We see it in production from a readiness probe whose connect to a slow server outlives its timeout. The same leak happens when the cancellation arrives afteron_dataalready got its exception (the task is cancelled before it reads the result).__connect_addr()already handles this for itsconnectedfuture. This change does the same for the SSL upgrade:_create_ssl_connection()keeps a reference to theTLSUpgradeProtoit creates, and discardson_datawhencreate_connection()fails or is cancelled.on_datawhen awaiting it fails, so an exception set in the same loop iteration as the cancellation is retrieved._discard_future()helper (cancel if pending, otherwise retrieve the exception) is shared with the existingconnectedhandling.The new test
TestConnectionCleanup.test_cancelled_ssl_upgrade_leaves_no_orphan_futureneeds no cluster. It cancels a connect to a local server that never answers at each event-loop step, withdirect_tlsoff and on, and asserts that the loop's exception handler was never called. Without the fix it fails with the message above; with it, it passes.tests/test__sourcecode.py(flake8, mypy) passes.🤖 Generated with Claude Code