Repository navigation
fix(psycopg): prevent proxy deadlock on TLS 1.3 post-handshake messages - #1494
Merged
Merged
Conversation
In TLS 1.3 (RFC 8446 Section 4.6.1), the server sends a post-handshake NewSessionTicket record asynchronously after the handshake completes. When this record arrives on the remote SSLSocket before psycopg writes its PostgreSQL StartupMessage to the local socket (or during an idle window between queries), selectors.DefaultSelector() wakes up with EVENT_READ on remote. Calling recv() on a blocking SSLSocket consumes the NewSessionTicket internally and then blocks indefinitely inside SSL_read waiting for application data from the server, deadlocking the single-threaded proxy until psycopg's 30s connect_timeout expires. Temporarily put the remote socket in non-blocking mode during selector-driven recv() calls and return True on SSLWantReadError/BlockingIOError so the selector loop continues without blocking.
panavenue
approved these changes
Oct 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.
Summary
Fixes intermittent
psycopg.errors.ConnectionTimeout: connection timeout expiredfailures in CI (particularly onwindows-latestandmacos-latest, e.g. intest_lazy_psycopg_connection,test_system_psycopg_to_thread, andtest_psycopg_connection).Root Cause
In TLS 1.3 (RFC 8446 §4.6.1),
SSLContext.wrap_socket()completes immediately after the client sendsClientFinished, and the Cloud SQL Server Proxy then asynchronously sends a post-handshakeNewSessionTicketTLS record.When that
NewSessionTicketarrives inremote's OS socket receive buffer beforepsycopgwrites its PostgreSQLStartupMessagetolocal(which happens frequently on Windows where_PsycopgProxyuses a local TCP loopback socket127.0.0.1:<port>),sel.select()in_proxy()wakes up withEVENT_READonremoteand callsremote.recv(8192)on the blockingssl.SSLSocket. OpenSSL consumes theNewSessionTicketrecord internally, sees 0 bytes of decrypted application data, and blocks indefinitely insideSSL_readwaiting for PostgreSQL to send data first—deadlocking the single-threaded proxy loop sopsycopg'sStartupMessageonlocalis never forwarded.Fix
nonblocking: bool = Falseto_forward_bytes()and passnonblocking=Truewhen reading fromremoteonsel.select()wakeups in_proxy().src.setblocking(False)aroundsrc.recv(_BUFFER_SIZE)(restoringsrc.setblocking(True)infinallywithcontextlib.suppress(OSError)) and returnTrueon(ssl.SSLWantReadError, BlockingIOError)so the selector loop continues without blocking or closing the connection.test_proxy_tls13_post_handshake_want_readcovering this scenario.