Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0c85786

Browse files
jbower-fbswtaarrsarhadthedev
authored
Fix deadlock on shutdown if test_current_{exception,frames} fails (#102019)
* Don't deadlock on shutdown if test_current_{exception,frames} fails These tests spawn a thread that waits on a threading.Event. If the test fails any of its assertions, the Event won't be signaled and the thread will wait indefinitely, causing a deadlock when threading._shutdown() tries to join all outstanding threads. Co-authored-by: Brett Simmers <[email protected]> * Add a news entry * Fix whitespace --------- Co-authored-by: Brett Simmers <[email protected]> Co-authored-by: Oleg Iarygin <[email protected]>
1 parent ccd98a3 commit 0c85786

File tree

2 files changed

+77
-73
lines changed

2 files changed

+77
-73
lines changed

Lib/test/test_sys.py

Lines changed: 75 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -445,46 +445,47 @@ def g456():
445445
t.start()
446446
entered_g.wait()
447447

448-
# At this point, t has finished its entered_g.set(), although it's
449-
# impossible to guess whether it's still on that line or has moved on
450-
# to its leave_g.wait().
451-
self.assertEqual(len(thread_info), 1)
452-
thread_id = thread_info[0]
453-
454-
d = sys._current_frames()
455-
for tid in d:
456-
self.assertIsInstance(tid, int)
457-
self.assertGreater(tid, 0)
458-
459-
main_id = threading.get_ident()
460-
self.assertIn(main_id, d)
461-
self.assertIn(thread_id, d)
462-
463-
# Verify that the captured main-thread frame is _this_ frame.
464-
frame = d.pop(main_id)
465-
self.assertTrue(frame is sys._getframe())
466-
467-
# Verify that the captured thread frame is blocked in g456, called
468-
# from f123. This is a little tricky, since various bits of
469-
# threading.py are also in the thread's call stack.
470-
frame = d.pop(thread_id)
471-
stack = traceback.extract_stack(frame)
472-
for i, (filename, lineno, funcname, sourceline) in enumerate(stack):
473-
if funcname == "f123":
474-
break
475-
else:
476-
self.fail("didn't find f123() on thread's call stack")
477-
478-
self.assertEqual(sourceline, "g456()")
448+
try:
449+
# At this point, t has finished its entered_g.set(), although it's
450+
# impossible to guess whether it's still on that line or has moved on
451+
# to its leave_g.wait().
452+
self.assertEqual(len(thread_info), 1)
453+
thread_id = thread_info[0]
454+
455+
d = sys._current_frames()
456+
for tid in d:
457+
self.assertIsInstance(tid, int)
458+
self.assertGreater(tid, 0)
459+
460+
main_id = threading.get_ident()
461+
self.assertIn(main_id, d)
462+
self.assertIn(thread_id, d)
463+
464+
# Verify that the captured main-thread frame is _this_ frame.
465+
frame = d.pop(main_id)
466+
self.assertTrue(frame is sys._getframe())
467+
468+
# Verify that the captured thread frame is blocked in g456, called
469+
# from f123. This is a little tricky, since various bits of
470+
# threading.py are also in the thread's call stack.
471+
frame = d.pop(thread_id)
472+
stack = traceback.extract_stack(frame)
473+
for i, (filename, lineno, funcname, sourceline) in enumerate(stack):
474+
if funcname == "f123":
475+
break
476+
else:
477+
self.fail("didn't find f123() on thread's call stack")
479478

480-
# And the next record must be for g456().
481-
filename, lineno, funcname, sourceline = stack[i+1]
482-
self.assertEqual(funcname, "g456")
483-
self.assertIn(sourceline, ["leave_g.wait()", "entered_g.set()"])
479+
self.assertEqual(sourceline, "g456()")
484480

485-
# Reap the spawned thread.
486-
leave_g.set()
487-
t.join()
481+
# And the next record must be for g456().
482+
filename, lineno, funcname, sourceline = stack[i+1]
483+
self.assertEqual(funcname, "g456")
484+
self.assertIn(sourceline, ["leave_g.wait()", "entered_g.set()"])
485+
finally:
486+
# Reap the spawned thread.
487+
leave_g.set()
488+
t.join()
488489

489490
@threading_helper.reap_threads
490491
@threading_helper.requires_working_threading()
@@ -516,43 +517,44 @@ def g456():
516517
t.start()
517518
entered_g.wait()
518519

519-
# At this point, t has finished its entered_g.set(), although it's
520-
# impossible to guess whether it's still on that line or has moved on
521-
# to its leave_g.wait().
522-
self.assertEqual(len(thread_info), 1)
523-
thread_id = thread_info[0]
524-
525-
d = sys._current_exceptions()
526-
for tid in d:
527-
self.assertIsInstance(tid, int)
528-
self.assertGreater(tid, 0)
529-
530-
main_id = threading.get_ident()
531-
self.assertIn(main_id, d)
532-
self.assertIn(thread_id, d)
533-
self.assertEqual((None, None, None), d.pop(main_id))
534-
535-
# Verify that the captured thread frame is blocked in g456, called
536-
# from f123. This is a little tricky, since various bits of
537-
# threading.py are also in the thread's call stack.
538-
exc_type, exc_value, exc_tb = d.pop(thread_id)
539-
stack = traceback.extract_stack(exc_tb.tb_frame)
540-
for i, (filename, lineno, funcname, sourceline) in enumerate(stack):
541-
if funcname == "f123":
542-
break
543-
else:
544-
self.fail("didn't find f123() on thread's call stack")
545-
546-
self.assertEqual(sourceline, "g456()")
520+
try:
521+
# At this point, t has finished its entered_g.set(), although it's
522+
# impossible to guess whether it's still on that line or has moved on
523+
# to its leave_g.wait().
524+
self.assertEqual(len(thread_info), 1)
525+
thread_id = thread_info[0]
526+
527+
d = sys._current_exceptions()
528+
for tid in d:
529+
self.assertIsInstance(tid, int)
530+
self.assertGreater(tid, 0)
531+
532+
main_id = threading.get_ident()
533+
self.assertIn(main_id, d)
534+
self.assertIn(thread_id, d)
535+
self.assertEqual((None, None, None), d.pop(main_id))
536+
537+
# Verify that the captured thread frame is blocked in g456, called
538+
# from f123. This is a little tricky, since various bits of
539+
# threading.py are also in the thread's call stack.
540+
exc_type, exc_value, exc_tb = d.pop(thread_id)
541+
stack = traceback.extract_stack(exc_tb.tb_frame)
542+
for i, (filename, lineno, funcname, sourceline) in enumerate(stack):
543+
if funcname == "f123":
544+
break
545+
else:
546+
self.fail("didn't find f123() on thread's call stack")
547547

548-
# And the next record must be for g456().
549-
filename, lineno, funcname, sourceline = stack[i+1]
550-
self.assertEqual(funcname, "g456")
551-
self.assertTrue(sourceline.startswith("if leave_g.wait("))
548+
self.assertEqual(sourceline, "g456()")
552549

553-
# Reap the spawned thread.
554-
leave_g.set()
555-
t.join()
550+
# And the next record must be for g456().
551+
filename, lineno, funcname, sourceline = stack[i+1]
552+
self.assertEqual(funcname, "g456")
553+
self.assertTrue(sourceline.startswith("if leave_g.wait("))
554+
finally:
555+
# Reap the spawned thread.
556+
leave_g.set()
557+
t.join()
556558

557559
def test_attributes(self):
558560
self.assertIsInstance(sys.api_version, int)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix deadlock on shutdown if ``test_current_{exception,frames}`` fails. Patch
2+
by Jacob Bower.

0 commit comments

Comments
 (0)