Skip to content

Commit 6309ef8

Browse files
committed
Move SSL handshake from main thread to worker thread
Port fix from gunicorn PR #3440. Previously, conn.init() (which does the SSL handshake via ssl_wrap_socket) ran on the main event loop thread in enqueue_req() with no error handling. A failed TLS handshake from any client (port scanners, browsers rejecting self-signed certs, connection resets) would crash the entire worker process and all its in-flight connections. Now conn.init() runs inside handle() in the thread pool, where ssl.SSLError, OSError, and Exception are already caught. A failed handshake only affects that one connection. Also: - Add double-init guard to TConn.init() for keepalive safety - Set initialized=True only after success, so partial init failures propagate the real error instead of a confusing AssertionError - Set setblocking(True) explicitly in handle() for keepalive connections where finish_request switched to non-blocking
1 parent 04608a4 commit 6309ef8

File tree

1 file changed

+11
-5
lines changed

1 file changed

+11
-5
lines changed

plain/plain/server/workers/thread.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ def __init__(
6464
self.sock.setblocking(False)
6565

6666
def init(self) -> None:
67-
self.initialized = True
68-
self.sock.setblocking(True)
67+
if self.initialized:
68+
return
6969

7070
if self.parser is None:
7171
# wrap the socket if needed
@@ -75,6 +75,8 @@ def init(self) -> None:
7575
# initialize the parser
7676
self.parser = http.RequestParser(self.cfg, self.sock, self.client)
7777

78+
self.initialized = True
79+
7880
def set_timeout(self) -> None:
7981
# set the timeout
8082
self.timeout = time.time() + KEEPALIVE
@@ -130,8 +132,9 @@ def _wrap_future(self, fs: futures.Future[tuple[bool, TConn]], conn: TConn) -> N
130132
fs.add_done_callback(self.finish_request)
131133

132134
def enqueue_req(self, conn: TConn) -> None:
133-
conn.init()
134-
# submit the connection to a worker
135+
# conn.init() is called inside handle(), not here, so that SSL
136+
# handshake errors are caught in the worker thread instead of
137+
# crashing the main loop. (Ported from gunicorn PR #3440.)
135138
fs = self.tpool.submit(self.handle, conn)
136139
self._wrap_future(fs, conn)
137140

@@ -303,7 +306,10 @@ def handle(self, conn: TConn) -> tuple[bool, TConn]:
303306
keepalive = False
304307
req = None
305308
try:
306-
# conn.parser is guaranteed to be initialized by enqueue_req -> conn.init()
309+
# Ensure blocking mode before init/parsing. Critical for keepalive
310+
# connections where finish_request sets non-blocking for the poller.
311+
conn.sock.setblocking(True)
312+
conn.init()
307313
assert conn.parser is not None
308314
req = next(conn.parser)
309315
if not req:

0 commit comments

Comments
 (0)