]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-150191: Avoid data race in test_sni_callback_race (GH-150193) (#153269)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 7 Jul 2026 16:24:08 +0000 (18:24 +0200)
committerGitHub <noreply@github.com>
Tue, 7 Jul 2026 16:24:08 +0000 (16:24 +0000)
gh-150191: Avoid data race in test_sni_callback_race (GH-150193)
(cherry picked from commit fd9d8c3c66fc147cac6024c63b8c109d65e3aec8)

Co-authored-by: Sam Gross <colesbury@gmail.com>
Lib/test/test_ssl.py

index f41262d81a82904b1af58d14e123672991ee29b3..0983a54fe1e7d7430b2c708066734cfd91823370 100644 (file)
@@ -1606,19 +1606,24 @@ class ContextTests(unittest.TestCase):
         gc.collect()
         self.assertIs(wr(), None)
 
-    @unittest.skipUnless(support.Py_GIL_DISABLED,
-                         "test is only useful if the GIL is disabled")
     @threading_helper.requires_working_threading()
     def test_sni_callback_race(self):
-        # Replacing sni_callback while handshakes are in-flight must not
+        # Replacing sni_callback while a handshake is in-flight must not
         # crash (use-after-free on the callback in free-threaded builds).
+        #
+        # Use a single handshake thread: OpenSSL has internal data races
+        # on shared SSL_CTX state when multiple handshakes run
+        # concurrently against the same context (gh-150191). Concurrency
+        # on the *setter* is what exercises the fix from gh-149816, so
+        # multiple toggler threads race against each other and against
+        # the one handshake worker.
         client_ctx, server_ctx, hostname = testing_context()
 
         server_ctx.sni_callback = lambda *a: None
-        done = threading.Event()
+        deadline = time.monotonic() + 0.1
 
         def do_handshakes():
-            while not done.is_set():
+            while time.monotonic() < deadline:
                 c_in = ssl.MemoryBIO()
                 c_out = ssl.MemoryBIO()
                 s_in = ssl.MemoryBIO()
@@ -1645,19 +1650,11 @@ class ContextTests(unittest.TestCase):
                         c_in.write(s_out.read())
 
         def toggle_callback():
-            while not done.is_set():
+            while time.monotonic() < deadline:
                 server_ctx.sni_callback = lambda *a: None
                 server_ctx.sni_callback = None
 
-        workers = max(4, (os.cpu_count() or 4) * 2)
-        threads = [threading.Thread(target=do_handshakes)
-                   for _ in range(workers)]
-        threads.append(threading.Thread(target=toggle_callback))
-
-        with threading_helper.catch_threading_exception() as cm:
-            with threading_helper.start_threads(threads):
-                done.set()
-            self.assertIsNone(cm.exc_value)
+        threading_helper.run_concurrently([do_handshakes] + 4 * [toggle_callback])
 
     def test_cert_store_stats(self):
         ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)