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 <bsimmers@meta.com> * Add a news entry * Fix whitespace --------- Co-authored-by: Brett Simmers <bsimmers@meta.com> Co-authored-by: Oleg Iarygin <oleg@arhadthedev.net>
This commit is contained in:
parent
ccd98a3146
commit
0c857865e4
@ -445,46 +445,47 @@ class SysModuleTest(unittest.TestCase):
|
|||||||
t.start()
|
t.start()
|
||||||
entered_g.wait()
|
entered_g.wait()
|
||||||
|
|
||||||
# At this point, t has finished its entered_g.set(), although it's
|
try:
|
||||||
# impossible to guess whether it's still on that line or has moved on
|
# At this point, t has finished its entered_g.set(), although it's
|
||||||
# to its leave_g.wait().
|
# impossible to guess whether it's still on that line or has moved on
|
||||||
self.assertEqual(len(thread_info), 1)
|
# to its leave_g.wait().
|
||||||
thread_id = thread_info[0]
|
self.assertEqual(len(thread_info), 1)
|
||||||
|
thread_id = thread_info[0]
|
||||||
|
|
||||||
d = sys._current_frames()
|
d = sys._current_frames()
|
||||||
for tid in d:
|
for tid in d:
|
||||||
self.assertIsInstance(tid, int)
|
self.assertIsInstance(tid, int)
|
||||||
self.assertGreater(tid, 0)
|
self.assertGreater(tid, 0)
|
||||||
|
|
||||||
main_id = threading.get_ident()
|
main_id = threading.get_ident()
|
||||||
self.assertIn(main_id, d)
|
self.assertIn(main_id, d)
|
||||||
self.assertIn(thread_id, d)
|
self.assertIn(thread_id, d)
|
||||||
|
|
||||||
# Verify that the captured main-thread frame is _this_ frame.
|
# Verify that the captured main-thread frame is _this_ frame.
|
||||||
frame = d.pop(main_id)
|
frame = d.pop(main_id)
|
||||||
self.assertTrue(frame is sys._getframe())
|
self.assertTrue(frame is sys._getframe())
|
||||||
|
|
||||||
# Verify that the captured thread frame is blocked in g456, called
|
# Verify that the captured thread frame is blocked in g456, called
|
||||||
# from f123. This is a little tricky, since various bits of
|
# from f123. This is a little tricky, since various bits of
|
||||||
# threading.py are also in the thread's call stack.
|
# threading.py are also in the thread's call stack.
|
||||||
frame = d.pop(thread_id)
|
frame = d.pop(thread_id)
|
||||||
stack = traceback.extract_stack(frame)
|
stack = traceback.extract_stack(frame)
|
||||||
for i, (filename, lineno, funcname, sourceline) in enumerate(stack):
|
for i, (filename, lineno, funcname, sourceline) in enumerate(stack):
|
||||||
if funcname == "f123":
|
if funcname == "f123":
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.fail("didn't find f123() on thread's call stack")
|
self.fail("didn't find f123() on thread's call stack")
|
||||||
|
|
||||||
self.assertEqual(sourceline, "g456()")
|
self.assertEqual(sourceline, "g456()")
|
||||||
|
|
||||||
# And the next record must be for g456().
|
# And the next record must be for g456().
|
||||||
filename, lineno, funcname, sourceline = stack[i+1]
|
filename, lineno, funcname, sourceline = stack[i+1]
|
||||||
self.assertEqual(funcname, "g456")
|
self.assertEqual(funcname, "g456")
|
||||||
self.assertIn(sourceline, ["leave_g.wait()", "entered_g.set()"])
|
self.assertIn(sourceline, ["leave_g.wait()", "entered_g.set()"])
|
||||||
|
finally:
|
||||||
# Reap the spawned thread.
|
# Reap the spawned thread.
|
||||||
leave_g.set()
|
leave_g.set()
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
@threading_helper.reap_threads
|
@threading_helper.reap_threads
|
||||||
@threading_helper.requires_working_threading()
|
@threading_helper.requires_working_threading()
|
||||||
@ -516,43 +517,44 @@ class SysModuleTest(unittest.TestCase):
|
|||||||
t.start()
|
t.start()
|
||||||
entered_g.wait()
|
entered_g.wait()
|
||||||
|
|
||||||
# At this point, t has finished its entered_g.set(), although it's
|
try:
|
||||||
# impossible to guess whether it's still on that line or has moved on
|
# At this point, t has finished its entered_g.set(), although it's
|
||||||
# to its leave_g.wait().
|
# impossible to guess whether it's still on that line or has moved on
|
||||||
self.assertEqual(len(thread_info), 1)
|
# to its leave_g.wait().
|
||||||
thread_id = thread_info[0]
|
self.assertEqual(len(thread_info), 1)
|
||||||
|
thread_id = thread_info[0]
|
||||||
|
|
||||||
d = sys._current_exceptions()
|
d = sys._current_exceptions()
|
||||||
for tid in d:
|
for tid in d:
|
||||||
self.assertIsInstance(tid, int)
|
self.assertIsInstance(tid, int)
|
||||||
self.assertGreater(tid, 0)
|
self.assertGreater(tid, 0)
|
||||||
|
|
||||||
main_id = threading.get_ident()
|
main_id = threading.get_ident()
|
||||||
self.assertIn(main_id, d)
|
self.assertIn(main_id, d)
|
||||||
self.assertIn(thread_id, d)
|
self.assertIn(thread_id, d)
|
||||||
self.assertEqual((None, None, None), d.pop(main_id))
|
self.assertEqual((None, None, None), d.pop(main_id))
|
||||||
|
|
||||||
# Verify that the captured thread frame is blocked in g456, called
|
# Verify that the captured thread frame is blocked in g456, called
|
||||||
# from f123. This is a little tricky, since various bits of
|
# from f123. This is a little tricky, since various bits of
|
||||||
# threading.py are also in the thread's call stack.
|
# threading.py are also in the thread's call stack.
|
||||||
exc_type, exc_value, exc_tb = d.pop(thread_id)
|
exc_type, exc_value, exc_tb = d.pop(thread_id)
|
||||||
stack = traceback.extract_stack(exc_tb.tb_frame)
|
stack = traceback.extract_stack(exc_tb.tb_frame)
|
||||||
for i, (filename, lineno, funcname, sourceline) in enumerate(stack):
|
for i, (filename, lineno, funcname, sourceline) in enumerate(stack):
|
||||||
if funcname == "f123":
|
if funcname == "f123":
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.fail("didn't find f123() on thread's call stack")
|
self.fail("didn't find f123() on thread's call stack")
|
||||||
|
|
||||||
self.assertEqual(sourceline, "g456()")
|
self.assertEqual(sourceline, "g456()")
|
||||||
|
|
||||||
# And the next record must be for g456().
|
# And the next record must be for g456().
|
||||||
filename, lineno, funcname, sourceline = stack[i+1]
|
filename, lineno, funcname, sourceline = stack[i+1]
|
||||||
self.assertEqual(funcname, "g456")
|
self.assertEqual(funcname, "g456")
|
||||||
self.assertTrue(sourceline.startswith("if leave_g.wait("))
|
self.assertTrue(sourceline.startswith("if leave_g.wait("))
|
||||||
|
finally:
|
||||||
# Reap the spawned thread.
|
# Reap the spawned thread.
|
||||||
leave_g.set()
|
leave_g.set()
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
def test_attributes(self):
|
def test_attributes(self):
|
||||||
self.assertIsInstance(sys.api_version, int)
|
self.assertIsInstance(sys.api_version, int)
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
Fix deadlock on shutdown if ``test_current_{exception,frames}`` fails. Patch
|
||||||
|
by Jacob Bower.
|
Loading…
x
Reference in New Issue
Block a user