@@ -1354,43 +1354,70 @@ def read(self, n=-1):
13541354 def check_daemon_threads_shutdown_deadlock (self , stream_name ):
13551355 # Issue #23309: deadlocks at shutdown should be avoided when a
13561356 # daemon thread and the main thread both write to a file.
1357+ #
1358+ # Focus on I/O and interaction in locks, test_threading checks daemon
1359+ # threads exit generally. Get into the specific I/O states we care
1360+ # about by using mock I/O which block in the right locks.
13571361 code = """if 1:
1362+ import atexit
1363+ import io
13581364 import sys
1359- import time
1360- import threading
1365+ from threading import current_thread, Event, main_thread, Thread
1366+
1367+ go = Event()
1368+ entered = Event()
1369+
1370+ # Register with atexit before importing test.support which imports
1371+ # logging so that it runs last.
1372+ @atexit.register
1373+ def release_daemon_thread():
1374+ # Get daemon thread to hold BufferedWriter lock.
1375+ go.set()
1376+ entered.wait()
1377+
13611378 from test.support import SuppressCrashReport
13621379
1363- file = sys.{stream_name}
1380+ # Mock I/O that blocks daemon thread in Raw I/O write.
1381+ class GatedRaw(io.RawIOBase):
1382+ name = '<{stream_name}>'
1383+
1384+ def writable(self):
1385+ return True
1386+
1387+ def write(self, b):
1388+ if current_thread() is not main_thread():
1389+ # In daemon, have buffered lock. Hold it.
1390+ entered.set()
1391+ Event().wait()
1392+ return len(b)
1393+
1394+ file = io.TextIOWrapper(io.BufferedWriter(GatedRaw()),
1395+ encoding='utf-8')
1396+ sys.{stream_name} = file
13641397
13651398 def run():
1366- while True:
1367- file.write('.')
1368- file.flush()
1399+ go.wait()
1400+ file.write('.')
1401+ file.flush()
13691402
13701403 crash = SuppressCrashReport()
13711404 crash.__enter__()
13721405 # don't call __exit__(): the crash occurs at Python shutdown
13731406
1374- thread = threading. Thread(target=run)
1407+ thread = Thread(target=run)
13751408 thread.daemon = True
13761409 thread.start()
13771410
1378- time.sleep(0.5)
13791411 file.write('!')
13801412 file.flush()
1381- """ .format_map ( locals () )
1413+ """ .format ( stream_name = stream_name )
13821414 res , _ = run_python_until_end ("-c" , code )
13831415 err = res .err .decode ()
1384- if res .rc != 0 :
1385- # Failure: should be a fatal error
1386- pattern = (r"Fatal Python error: _enter_buffered_busy: "
1387- r"could not acquire lock "
1388- r"for <(_io\.)?BufferedWriter name='<{stream_name}>'> "
1389- r"at interpreter shutdown, possibly due to "
1390- r"daemon threads" .format_map (locals ()))
1391- self .assertRegex (err , pattern )
1392- else :
1393- self .assertFalse (err .strip ('.!' ))
1416+ # Should exit non-zero with fatal error.
1417+ self .assertNotEqual (res .rc , 0 )
1418+ self .assertIn ("Fatal Python error: _enter_buffered_busy" , err )
1419+ self .assertIn (f"<_io.BufferedWriter name='<{ stream_name } >'>" , err )
1420+ self .assertIn ("possibly due to daemon threads" , err )
13941421
13951422 @threading_helper .requires_working_threading ()
13961423 @support .requires_resource ('walltime' )
0 commit comments