diff options
author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2020-05-22 21:35:22 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-22 21:35:22 (GMT) |
commit | 7f77ac463cff219e0c8afef2611cad5080cc9df1 (patch) | |
tree | 8d28a7f990479fcf34d2493553a8583b515d7e39 /Lib | |
parent | a08b7c3bb0ef9da32400d23b13f78245cd7a9541 (diff) | |
download | cpython-7f77ac463cff219e0c8afef2611cad5080cc9df1.zip cpython-7f77ac463cff219e0c8afef2611cad5080cc9df1.tar.gz cpython-7f77ac463cff219e0c8afef2611cad5080cc9df1.tar.bz2 |
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.
(cherry picked from commit 7c30d12bd5359b0f66c4fbc98aa055398bcc8a7e)
Co-authored-by: Chris Jerdonek <chris.jerdonek@gmail.com>
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_asyncio/test_tasks.py | 39 | ||||
-rw-r--r-- | Lib/test/test_generators.py | 26 |
2 files changed, 62 insertions, 3 deletions
diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 63968e2..3734013 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -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): diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index 87cc2df..bf48221 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -371,6 +371,32 @@ class GeneratorThrowTest(unittest.TestCase): context = cm.exception.__context__ self.assertEqual((type(context), context.args), (KeyError, ('a',))) + def test_exception_context_with_yield_from_with_context_cycle(self): + # Check trying to create an exception context cycle: + # https://bugs.python.org/issue40696 + has_cycle = None + + def f(): + yield + + def g(exc): + nonlocal has_cycle + try: + raise exc + except Exception: + try: + yield from f() + except Exception as exc: + has_cycle = (exc is exc.__context__) + yield + + exc = KeyError('a') + gen = g(exc) + gen.send(None) + gen.throw(exc) + # This also distinguishes from the initial has_cycle=None. + self.assertEqual(has_cycle, False) + def test_throw_after_none_exc_type(self): def g(): try: |