https://github.com/python/cpython/commit/53b0004311dc3888a6d117557e7c24933be865c8
commit: 53b0004311dc3888a6d117557e7c24933be865c8
branch: main
author: Cody Maloney <[email protected]>
committer: ambv <[email protected]>
date: 2026-10-23T13:05:52-07:00
summary:

gh-154175: Remove time.sleep in test_io daemon test (#157019)

Deterministically get into the deadlock timeout by using mocked Raw I/O
that blocks on threading events. The test still takes quite a bit of
walltime as `_enter_buffered_busy` has a 1 second timeout on a lock
acquisition which this hits.

files:
M Lib/test/test_io/test_general.py

diff --git a/Lib/test/test_io/test_general.py b/Lib/test/test_io/test_general.py
index d12c5535b263ef..1547f0ae554245 100644
--- a/Lib/test/test_io/test_general.py
+++ b/Lib/test/test_io/test_general.py
@@ -1370,43 +1370,75 @@ def read(self, n=-1):
     def check_daemon_threads_shutdown_deadlock(self, stream_name):
         # Issue #23309: deadlocks at shutdown should be avoided when a
         # daemon thread and the main thread both write to a file.
+        #
+        # Focus on I/O and interaction in locks, test_threading checks daemon
+        # threads exit generally. Get into the specific I/O states we care
+        # about by using mock I/O which block in the right locks.
         code = """if 1:
+            import atexit
+            import io
             import sys
-            import time
-            import threading
+            from threading import current_thread, Event, main_thread, Thread
+
+            go = Event()
+            entered = Event()
+
+            # Register with atexit before importing test.support which imports
+            # logging so that it runs last.
+            @atexit.register
+            def release_daemon_thread():
+                # Get daemon thread to hold BufferedWriter lock.
+                go.set()
+                entered.wait()
+
             from test.support import SuppressCrashReport
 
-            file = sys.{stream_name}
+            # Mock I/O that blocks daemon thread in Raw I/O write.
+            #
+            # BufferedIO holds a lock while doing I/O operations. Use a
+            # threading Event to simulate a thread doing a slow I/O which holds
+            # the lock on sys.stdout, sys.stderr blocking normal cleanup of
+            # those streams during interpreter finalization.
+            class GatedRaw(io.RawIOBase):
+                name = '<{stream_name}>'
+
+                def writable(self):
+                    return True
+
+                def write(self, b):
+                    if current_thread() is not main_thread():
+                        # In daemon, have buffered lock. Hold it.
+                        entered.set()
+                        Event().wait()
+                    return len(b)
+
+            file = io.TextIOWrapper(io.BufferedWriter(GatedRaw()),
+                                    encoding='utf-8')
+            sys.{stream_name} = file
 
             def run():
-                while True:
-                    file.write('.')
-                    file.flush()
+                go.wait()
+                file.write('.')
+                file.flush()
 
             crash = SuppressCrashReport()
             crash.__enter__()
             # don't call __exit__(): the crash occurs at Python shutdown
 
-            thread = threading.Thread(target=run)
+            thread = Thread(target=run)
             thread.daemon = True
             thread.start()
 
-            time.sleep(0.5)
             file.write('!')
             file.flush()
-            """.format_map(locals())
+            """.format(stream_name=stream_name)
         res, _ = run_python_until_end("-c", code)
         err = res.err.decode()
-        if res.rc != 0:
-            # Failure: should be a fatal error
-            pattern = (r"Fatal Python error: _enter_buffered_busy: "
-                       r"could not acquire lock "
-                       r"for <(_io\.)?BufferedWriter name='<{stream_name}>'> "
-                       r"at interpreter shutdown, possibly due to "
-                       r"daemon threads".format_map(locals()))
-            self.assertRegex(err, pattern)
-        else:
-            self.assertFalse(err.strip('.!'))
+        # Should exit non-zero with fatal error.
+        self.assertNotEqual(res.rc, 0)
+        self.assertIn("Fatal Python error: _enter_buffered_busy", err)
+        self.assertIn(f"<_io.BufferedWriter name='<{stream_name}>'>", err)
+        self.assertIn("possibly due to daemon threads", err)
 
     @threading_helper.requires_working_threading()
     @support.requires_resource('walltime')

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to