bpo-40696: Fix a hang that can arise after gen.throw() (GH-20287)

This updates _PyErr_ChainStackItem() to use _PyErr_SetObject()
instead of _PyErr_ChainExceptions(). This prevents a hang in
certain circumstances because _PyErr_SetObject() performs checks
to prevent cycles in the exception context chain while
_PyErr_ChainExceptions() doesn't.
This commit is contained in:
Chris Jerdonek 2020-05-22 13:33:27 -07:00 committed by GitHub
parent 909b5714e1
commit 7c30d12bd5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 130 additions and 23 deletions

View file

@ -536,9 +536,42 @@ class BaseTaskTests:
self.assertEqual((type(chained), chained.args),
(KeyError, (3,)))
task = self.new_task(loop, run())
loop.run_until_complete(task)
loop.close()
try:
task = self.new_task(loop, run())
loop.run_until_complete(task)
finally:
loop.close()
def test_exception_chaining_after_await_with_context_cycle(self):
# Check trying to create an exception context cycle:
# https://bugs.python.org/issue40696
has_cycle = None
loop = asyncio.new_event_loop()
self.set_event_loop(loop)
async def process_exc(exc):
raise exc
async def run():
nonlocal has_cycle
try:
raise KeyError('a')
except Exception as exc:
task = self.new_task(loop, process_exc(exc))
try:
await task
except BaseException as exc:
has_cycle = (exc is exc.__context__)
# Prevent a hang if has_cycle is True.
exc.__context__ = None
try:
task = self.new_task(loop, run())
loop.run_until_complete(task)
finally:
loop.close()
# This also distinguishes from the initial has_cycle=None.
self.assertEqual(has_cycle, False)
def test_cancel(self):