1414from test .support .script_helper import assert_python_ok , assert_python_failure
1515from test .support import threading_helper
1616from test .support import import_helper
17+ from test .support import skip_if_sanitizer
1718import textwrap
1819import unittest
1920import warnings
@@ -471,15 +472,18 @@ def g456():
471472 leave_g .set ()
472473 t .join ()
473474
475+ @skip_if_sanitizer (memory = True , address = True , reason = "Test too slow "
476+ "when the address sanitizer is enabled." )
474477 @threading_helper .reap_threads
475478 @threading_helper .requires_working_threading ()
476479 @support .requires_fork ()
477480 def test_current_frames_exceptions_deadlock (self ):
478481 """
479- Try to reproduce the bug raised in GH-106883 and GH-116969.
482+ Reproduce the bug raised in GH-106883 and GH-116969.
480483 """
481484 import threading
482485 import time
486+ import signal
483487
484488 class MockObject :
485489 def __init__ (self ):
@@ -489,35 +493,56 @@ def __init__(self):
489493 self ._trace = sys ._current_frames ()
490494 self ._exceptions = sys ._current_exceptions ()
491495
496+ def __del__ (self ):
497+ # The presence of the __del__ method causes the deadlock when
498+ # there is one thread executing the _current_frames or
499+ # _current_exceptions functions and the other thread is
500+ # running the GC:
501+ # thread 1 has the interpreter lock and it is trying to
502+ # acquire the GIL; thread 2 holds the GIL but is trying to
503+ # acquire the interpreter lock.
504+ # When the GC is running and it finds that an
505+ # object has the __del__ method, it needs to execute the
506+ # Python code in it and it requires the GIL to execute it
507+ # (which will never happen because it is held by another thread
508+ # blocked on the acquisition of the interpreter lock)
509+ pass
510+
492511 def thread_function (num_objects ):
493512 obj = None
494513 for _ in range (num_objects ):
495- # The sleep is needed to have a syscall: in interrupts the
496- # current thread, releases the GIL and gives way to other
497- # threads to be executed. In this way there are more chances
498- # to reproduce the bug.
499- time .sleep (0 )
500514 obj = MockObject ()
501515
502- NUM_OBJECTS = 25
503- NUM_THREADS = 1000
516+ # The number of objects should be big enough to increase the
517+ # chances to call the GC.
518+ NUM_OBJECTS = 1000
519+ NUM_THREADS = 10
504520
505- # 60 seconds should be enough for the test to be executed: if it
506- # is more than 60 seconds it means that the process is in deadlock
521+ # 40 seconds should be enough for the test to be executed: if it
522+ # is more than 40 seconds it means that the process is in deadlock
507523 # hence the test fails
508- TIMEOUT = 60
524+ TIMEOUT = 40
509525
510526 # Test the sys._current_frames and sys._current_exceptions calls
511527 pid = os .fork ()
512528 if pid : # parent process
513- support .wait_process (pid , exitcode = 0 , timeout = TIMEOUT )
529+ try :
530+ support .wait_process (pid , exitcode = 0 , timeout = TIMEOUT )
531+ except KeyboardInterrupt :
532+ # When pressing CTRL-C kill the deadlocked process
533+ os .kill (pid , signal .SIGTERM )
534+ raise
514535 else : # child process
515536 # Run the actual test in the forked process.
537+ threads = []
516538 for i in range (NUM_THREADS ):
517539 thread = threading .Thread (
518540 target = thread_function , args = (NUM_OBJECTS ,)
519541 )
542+ threads .append (thread )
520543 thread .start ()
544+ for t in threads :
545+ t .join ()
521546 os ._exit (0 )
522547
523548 @threading_helper .reap_threads
0 commit comments