From 0a070e207a15799ca1140a2e50bf925a370cba05 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 07:24:47 -0400 Subject: [PATCH 01/16] Only lock the SSL context, not the SSL socket. --- Lib/test/test_ssl.py | 22 ++++++++++++++++++++++ Modules/_ssl.c | 33 +++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index c8939383c75d6d..90a875d91d4213 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4620,6 +4620,28 @@ def server_callback(identity): with client_context.wrap_socket(socket.socket()) as s: s.connect((HOST, server.port)) + def test_thread_recv_while_main_thread_sends(self): + # GH-137583: Locking was added to calls to send() and recv() on SSL + # socket objects. This seemed fine at the surface level because those + # calls weren't re-entrant, but recv() calls would implicitly mimick + # holding a lock by blocking until it received data. This means that + # if a thread started to infinitely block until data was received, calls + # to send() would deadlock, because it would wait forever on the lock + # that the recv() call held. + data = b"A" * 5000 + def background(sock): + received = sock.recv(5000) + assert received == data + + client_context, server_context, _ = testing_context() + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as sock: + sock.connect((HOST, server.port)) + thread = threading.Thread(target=background, args=(sock,)) + thread.start() + sock.sendall(data) + @unittest.skipUnless(has_tls_version('TLSv1_3') and ssl.HAS_PHA, "Test needs TLS 1.3 PHA") diff --git a/Modules/_ssl.c b/Modules/_ssl.c index ab30258faf3f62..9f4e723bba6623 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -994,12 +994,11 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock, BIO_set_nbio(SSL_get_wbio(self->ssl), 1); } - PySSL_BEGIN_ALLOW_THREADS(self) + // No other threads can have access to this, so no need to lock it. if (socket_type == PY_SSL_CLIENT) SSL_set_connect_state(self->ssl); else SSL_set_accept_state(self->ssl); - PySSL_END_ALLOW_THREADS(self) self->socket_type = socket_type; if (sock != NULL) { @@ -1068,10 +1067,11 @@ _ssl__SSLSocket_do_handshake_impl(PySSLSocket *self) /* Actually negotiate SSL connection */ /* XXX If SSL_do_handshake() returns 0, it's also a failure. */ do { - PySSL_BEGIN_ALLOW_THREADS(self) + Py_BEGIN_ALLOW_THREADS ret = SSL_do_handshake(self->ssl); err = _PySSL_errno(ret < 1, self->ssl, ret); - PySSL_END_ALLOW_THREADS(self) + Py_END_ALLOW_THREADS; + _PySSL_FIX_ERRNO; self->err = err; if (PyErr_CheckSignals()) @@ -2615,10 +2615,11 @@ _ssl__SSLSocket_sendfile_impl(PySSLSocket *self, int fd, Py_off_t offset, } do { - PySSL_BEGIN_ALLOW_THREADS(self) + Py_BEGIN_ALLOW_THREADS retval = SSL_sendfile(self->ssl, fd, (off_t)offset, size, flags); err = _PySSL_errno(retval < 0, self->ssl, (int)retval); - PySSL_END_ALLOW_THREADS(self) + Py_END_ALLOW_THREADS; + _PySSL_FIX_ERRNO; self->err = err; if (PyErr_CheckSignals()) { @@ -2746,10 +2747,11 @@ _ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b) } do { - PySSL_BEGIN_ALLOW_THREADS(self) + Py_BEGIN_ALLOW_THREADS; retval = SSL_write_ex(self->ssl, b->buf, (size_t)b->len, &count); err = _PySSL_errno(retval == 0, self->ssl, retval); - PySSL_END_ALLOW_THREADS(self) + Py_END_ALLOW_THREADS; + _PySSL_FIX_ERRNO; self->err = err; if (PyErr_CheckSignals()) @@ -2807,10 +2809,11 @@ _ssl__SSLSocket_pending_impl(PySSLSocket *self) int count = 0; _PySSLError err; - PySSL_BEGIN_ALLOW_THREADS(self) + Py_BEGIN_ALLOW_THREADS; count = SSL_pending(self->ssl); err = _PySSL_errno(count < 0, self->ssl, count); - PySSL_END_ALLOW_THREADS(self) + Py_END_ALLOW_THREADS; + _PySSL_FIX_ERRNO; self->err = err; if (count < 0) @@ -2901,10 +2904,11 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, Py_ssize_t len, deadline = _PyDeadline_Init(timeout); do { - PySSL_BEGIN_ALLOW_THREADS(self) + Py_BEGIN_ALLOW_THREADS; retval = SSL_read_ex(self->ssl, mem, (size_t)len, &count); err = _PySSL_errno(retval == 0, self->ssl, retval); - PySSL_END_ALLOW_THREADS(self) + Py_END_ALLOW_THREADS; + _PySSL_FIX_ERRNO; self->err = err; if (PyErr_CheckSignals()) @@ -3003,7 +3007,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self) } while (1) { - PySSL_BEGIN_ALLOW_THREADS(self) + Py_BEGIN_ALLOW_THREADS; /* Disable read-ahead so that unwrap can work correctly. * Otherwise OpenSSL might read in too much data, * eating clear text data that happens to be @@ -3016,7 +3020,8 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self) SSL_set_read_ahead(self->ssl, 0); ret = SSL_shutdown(self->ssl); err = _PySSL_errno(ret < 0, self->ssl, ret); - PySSL_END_ALLOW_THREADS(self) + Py_END_ALLOW_THREADS; + _PySSL_FIX_ERRNO; self->err = err; /* If err == 1, a secure shutdown with SSL_shutdown() is complete */ From 42a03517d76ec9226607b321477bd9204d231257 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 07:36:02 -0400 Subject: [PATCH 02/16] Fix the test. --- Lib/test/test_ssl.py | 22 +++++++++++++--------- Modules/_ssl.c | 4 ---- Modules/_ssl/debughelpers.c | 1 - 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 90a875d91d4213..ea3c70e4c8c80c 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4628,20 +4628,24 @@ def test_thread_recv_while_main_thread_sends(self): # if a thread started to infinitely block until data was received, calls # to send() would deadlock, because it would wait forever on the lock # that the recv() call held. - data = b"A" * 5000 + data = b"a" * 500 def background(sock): - received = sock.recv(5000) - assert received == data + received = sock.recv(500) + self.assertEqual(received, data) - client_context, server_context, _ = testing_context() + client_context, server_context, hostname = testing_context() server = ThreadedEchoServer(context=server_context) with server: - with client_context.wrap_socket(socket.socket()) as sock: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as sock: sock.connect((HOST, server.port)) - thread = threading.Thread(target=background, args=(sock,)) - thread.start() - sock.sendall(data) - + with threading_helper.catch_threading_exception() as cm: + thread = threading.Thread(target=background, args=(sock,)) + thread.start() + sock.sendall(data) + thread.join() + if cm.exc_value is not None: + raise cm.exc_value @unittest.skipUnless(has_tls_version('TLSv1_3') and ssl.HAS_PHA, "Test needs TLS 1.3 PHA") diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 9f4e723bba6623..fc199ed1f13737 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -366,9 +366,6 @@ typedef struct { * and shutdown methods check for chained exceptions. */ PyObject *exc; - /* Lock to synchronize calls when the thread state is detached. - See also gh-134698. */ - PyMutex tstate_mutex; } PySSLSocket; #define PySSLSocket_CAST(op) ((PySSLSocket *)(op)) @@ -918,7 +915,6 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock, self->server_hostname = NULL; self->err = err; self->exc = NULL; - self->tstate_mutex = (PyMutex){0}; /* Make sure the SSL error state is initialized */ ERR_clear_error(); diff --git a/Modules/_ssl/debughelpers.c b/Modules/_ssl/debughelpers.c index 211fe15a439bfc..aee446d0ccb1b8 100644 --- a/Modules/_ssl/debughelpers.c +++ b/Modules/_ssl/debughelpers.c @@ -140,7 +140,6 @@ _PySSL_keylog_callback(const SSL *ssl, const char *line) * critical debug helper. */ - assert(PyMutex_IsLocked(&ssl_obj->tstate_mutex)); Py_BEGIN_ALLOW_THREADS PyThread_acquire_lock(lock, 1); res = BIO_printf(ssl_obj->ctx->keylog_bio, "%s\n", line); From c776679299e9d8b1f1350f42ea354f0fbca8a20e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 08:28:45 -0400 Subject: [PATCH 03/16] Fix race conditions in the test case. --- Lib/test/test_ssl.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index ea3c70e4c8c80c..edc7c347781c2e 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4628,25 +4628,36 @@ def test_thread_recv_while_main_thread_sends(self): # if a thread started to infinitely block until data was received, calls # to send() would deadlock, because it would wait forever on the lock # that the recv() call held. - data = b"a" * 500 + data = b"1" * 50 + event = threading.Event() + event2 = threading.Event() def background(sock): - received = sock.recv(500) + event.set() + event2.wait() + received = sock.recv(50) self.assertEqual(received, data) client_context, server_context, hostname = testing_context() - server = ThreadedEchoServer(context=server_context) + server = ThreadedEchoServer(context=server_context, + chatty=False, connectionchatty=False) with server: with client_context.wrap_socket(socket.socket(), server_hostname=hostname) as sock: sock.connect((HOST, server.port)) + sock.settimeout(5) with threading_helper.catch_threading_exception() as cm: - thread = threading.Thread(target=background, args=(sock,)) + thread = threading.Thread(target=background, + args=(sock,), daemon=True) thread.start() - sock.sendall(data) + # Use two events to prevent some race conditions here. + event.wait() + event2.set() + sock.sendall(b"1" * 50) thread.join() if cm.exc_value is not None: raise cm.exc_value + @unittest.skipUnless(has_tls_version('TLSv1_3') and ssl.HAS_PHA, "Test needs TLS 1.3 PHA") class TestPostHandshakeAuth(unittest.TestCase): From 90d93c3018928bc08319dac4444a3edd63720758 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 08:30:21 -0400 Subject: [PATCH 04/16] I now realize that approach was bad. --- Lib/test/test_ssl.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index edc7c347781c2e..88b1ca59f844cb 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4630,16 +4630,13 @@ def test_thread_recv_while_main_thread_sends(self): # that the recv() call held. data = b"1" * 50 event = threading.Event() - event2 = threading.Event() def background(sock): event.set() - event2.wait() received = sock.recv(50) self.assertEqual(received, data) client_context, server_context, hostname = testing_context() - server = ThreadedEchoServer(context=server_context, - chatty=False, connectionchatty=False) + server = ThreadedEchoServer(context=server_context) with server: with client_context.wrap_socket(socket.socket(), server_hostname=hostname) as sock: @@ -4651,7 +4648,6 @@ def background(sock): thread.start() # Use two events to prevent some race conditions here. event.wait() - event2.set() sock.sendall(b"1" * 50) thread.join() if cm.exc_value is not None: From 7ea2613dd2b030d27164be0ea24e2a4d1a7479b3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 08:35:33 -0400 Subject: [PATCH 05/16] Some other fixups. --- Lib/test/test_ssl.py | 3 ++- Modules/_ssl.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 88b1ca59f844cb..e3b2cea51f93f6 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4630,6 +4630,7 @@ def test_thread_recv_while_main_thread_sends(self): # that the recv() call held. data = b"1" * 50 event = threading.Event() + import time def background(sock): event.set() received = sock.recv(50) @@ -4646,8 +4647,8 @@ def background(sock): thread = threading.Thread(target=background, args=(sock,), daemon=True) thread.start() - # Use two events to prevent some race conditions here. event.wait() + time.sleep(0) sock.sendall(b"1" * 50) thread.join() if cm.exc_value is not None: diff --git a/Modules/_ssl.c b/Modules/_ssl.c index fc199ed1f13737..a74654b7573f45 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -990,11 +990,12 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock, BIO_set_nbio(SSL_get_wbio(self->ssl), 1); } - // No other threads can have access to this, so no need to lock it. + Py_BEGIN_ALLOW_THREADS; if (socket_type == PY_SSL_CLIENT) SSL_set_connect_state(self->ssl); else SSL_set_accept_state(self->ssl); + Py_END_ALLOW_THREADS; self->socket_type = socket_type; if (sock != NULL) { From 5ff4efcf0ce245892c64d66322ef8a5788a7e703 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 08:41:00 -0400 Subject: [PATCH 06/16] I give up. --- Lib/test/test_ssl.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index e3b2cea51f93f6..0aacf262fbcb1b 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4630,7 +4630,6 @@ def test_thread_recv_while_main_thread_sends(self): # that the recv() call held. data = b"1" * 50 event = threading.Event() - import time def background(sock): event.set() received = sock.recv(50) @@ -4648,7 +4647,6 @@ def background(sock): args=(sock,), daemon=True) thread.start() event.wait() - time.sleep(0) sock.sendall(b"1" * 50) thread.join() if cm.exc_value is not None: From cf630d72029d7fcc4d01050a2be2afbdae051fa0 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 08:51:35 -0400 Subject: [PATCH 07/16] Stupid workaround. --- Lib/test/test_ssl.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 0aacf262fbcb1b..8819b6b39ed8cc 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4647,6 +4647,9 @@ def background(sock): args=(sock,), daemon=True) thread.start() event.wait() + # We need to yield the GIL to prevent some silly race + # condition in ThreadedEchoServer + time.sleep(0) sock.sendall(b"1" * 50) thread.join() if cm.exc_value is not None: From f3ec86c380aef62e0baf3975538cde4d5722c566 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 08:54:27 -0400 Subject: [PATCH 08/16] Add blurb. --- .../Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst diff --git a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst new file mode 100644 index 00000000000000..8e9e97660abb1a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst @@ -0,0 +1,4 @@ +Fix a deadlock introduced in the :mod:`ssl` module when a call to +:class:`~ssl.SSLSocket.recv` was blocked in one thread, while another +attempted to call :class:`~ssl.SSLSocket.send` to unblock it in another +thread. From 12a68a502a9557f3351ef80c70794b93d3051ea4 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 09:08:17 -0400 Subject: [PATCH 09/16] Fix blurb. I spent all that time trying to make sure the test worked and of course it was the blurb that was wrong too. --- .../Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst index 8e9e97660abb1a..273d2796123771 100644 --- a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst +++ b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst @@ -1,4 +1,4 @@ Fix a deadlock introduced in the :mod:`ssl` module when a call to -:class:`~ssl.SSLSocket.recv` was blocked in one thread, while another -attempted to call :class:`~ssl.SSLSocket.send` to unblock it in another +:meth:`~ssl.SSLSocket.recv` was blocked in one thread, while another +attempted to call :meth:`~ssl.SSLSocket.send` to unblock it in another thread. From d45f6a84c055670ef3cd8569af713e594899bb58 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 09:16:54 -0400 Subject: [PATCH 10/16] Supposedly this *actually* fixes the blurb. --- .../Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst index 273d2796123771..94c24b822b10fd 100644 --- a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst +++ b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst @@ -1,4 +1,4 @@ Fix a deadlock introduced in the :mod:`ssl` module when a call to -:meth:`~ssl.SSLSocket.recv` was blocked in one thread, while another -attempted to call :meth:`~ssl.SSLSocket.send` to unblock it in another +:meth:`ssl.SSLSocket.recv` was blocked in one thread, while another +attempted to call :meth:`ssl.SSLSocket.send` to unblock it in another thread. From 6799b03c4876b796b38ae09cc4beb7014d04e35c Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 09:29:32 -0400 Subject: [PATCH 11/16] Avoid Sphinx references for the blurb. --- .../Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst index 94c24b822b10fd..ddb9d898a1fa81 100644 --- a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst +++ b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst @@ -1,4 +1,4 @@ Fix a deadlock introduced in the :mod:`ssl` module when a call to -:meth:`ssl.SSLSocket.recv` was blocked in one thread, while another -attempted to call :meth:`ssl.SSLSocket.send` to unblock it in another +``recv()`` was blocked in one thread, while another +attempted to call ``send()`` to unblock it in another thread. From 38e29f9ccde21d4a9253d8d00fde122f4147f022 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 12:25:50 -0400 Subject: [PATCH 12/16] Fix race condition in the test case (for real this time). Apparently, the thread can start waiting at a point where the server isn't ready to receive messages, which caused it to block indefinitely for some reason. Before we create any threads or set any events, do a simple exchange with the server to ensure that it's in its message loop. This took way too damn long to figure out. --- Lib/test/test_ssl.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 8819b6b39ed8cc..74f2a5adcb100c 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4628,11 +4628,11 @@ def test_thread_recv_while_main_thread_sends(self): # if a thread started to infinitely block until data was received, calls # to send() would deadlock, because it would wait forever on the lock # that the recv() call held. - data = b"1" * 50 + data = b"1" * 1024 event = threading.Event() def background(sock): event.set() - received = sock.recv(50) + received = sock.recv(len(data)) self.assertEqual(received, data) client_context, server_context, hostname = testing_context() @@ -4641,16 +4641,16 @@ def background(sock): with client_context.wrap_socket(socket.socket(), server_hostname=hostname) as sock: sock.connect((HOST, server.port)) - sock.settimeout(5) + sock.settimeout(1) + # Ensure that the server is ready to accept requests + sock.sendall(b"123") + self.assertEqual(sock.recv(3), b"123") with threading_helper.catch_threading_exception() as cm: thread = threading.Thread(target=background, args=(sock,), daemon=True) thread.start() event.wait() - # We need to yield the GIL to prevent some silly race - # condition in ThreadedEchoServer - time.sleep(0) - sock.sendall(b"1" * 50) + sock.sendall(data) thread.join() if cm.exc_value is not None: raise cm.exc_value From 64f7480a952c5cbe81c6a1dce20b611ff2647e5b Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 14:17:40 -0400 Subject: [PATCH 13/16] Fix the blurb (hopefully for the last time). --- .../Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst index ddb9d898a1fa81..6d023bb2bddde7 100644 --- a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst +++ b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst @@ -1,4 +1,4 @@ -Fix a deadlock introduced in the :mod:`ssl` module when a call to -``recv()`` was blocked in one thread, while another -attempted to call ``send()`` to unblock it in another +Fix a deadlock introduced in 3.13.6 when a call to +:meth:`ssl.SSLSocket.recv ` was blocked in one thread, while another +attempted to call :meth:`ssl.SSLSocket.send ` to unblock it in another thread. From 6da074eb12a814925084218a15d58dac92536c8a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Aug 2025 14:23:27 -0400 Subject: [PATCH 14/16] Fix the Windows buildbots. Apparently, settimeout() implicitly makes the socket non-blocking. --- Lib/test/test_ssl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 74f2a5adcb100c..d6ae0ee89e05bc 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4642,6 +4642,7 @@ def background(sock): server_hostname=hostname) as sock: sock.connect((HOST, server.port)) sock.settimeout(1) + sock.setblocking(1) # Ensure that the server is ready to accept requests sock.sendall(b"123") self.assertEqual(sock.recv(3), b"123") From bb99322534b665284aecbdd9a0e9382b49860bec Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 10 Aug 2025 09:32:14 -0400 Subject: [PATCH 15/16] Blurb fixup. --- .../Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst index 6d023bb2bddde7..aa2ac16ca4f99e 100644 --- a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst +++ b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst @@ -1,4 +1,4 @@ Fix a deadlock introduced in 3.13.6 when a call to -:meth:`ssl.SSLSocket.recv ` was blocked in one thread, while another -attempted to call :meth:`ssl.SSLSocket.send ` to unblock it in another -thread. +:meth:`ssl.SSLSocket.recv ` was blocked in one thread, +and then another method on the object (such as :meth:`ssl.SSLSocket.send`) +was subsequently called in another thread. From 36a4d66335b78838ca66a40abf0632e61ed4e21d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 10 Aug 2025 10:17:23 -0400 Subject: [PATCH 16/16] Ugh. --- .../next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst index aa2ac16ca4f99e..3843cc7c8c5524 100644 --- a/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst +++ b/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst @@ -1,4 +1,4 @@ Fix a deadlock introduced in 3.13.6 when a call to :meth:`ssl.SSLSocket.recv ` was blocked in one thread, -and then another method on the object (such as :meth:`ssl.SSLSocket.send`) +and then another method on the object (such as :meth:`ssl.SSLSocket.send `) was subsequently called in another thread.