Skip to content

Commit 6a88da5

Browse files
committed
Improve test case
1 parent 6d25f11 commit 6a88da5

File tree

1 file changed

+37
-12
lines changed

1 file changed

+37
-12
lines changed

Lib/test/test_sys.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from test.support.script_helper import assert_python_ok, assert_python_failure
1515
from test.support import threading_helper
1616
from test.support import import_helper
17+
from test.support import skip_if_sanitizer
1718
import textwrap
1819
import unittest
1920
import 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

Comments
 (0)