]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-29587: Make gen.throw() chain exceptions with yield from (GH-19858)
authorChris Jerdonek <chris.jerdonek@gmail.com>
Wed, 13 May 2020 23:18:27 +0000 (16:18 -0700)
committerGitHub <noreply@github.com>
Wed, 13 May 2020 23:18:27 +0000 (16:18 -0700)
The previous commits on bpo-29587 got exception chaining working
with gen.throw() in the `yield` case. This patch also gets the
`yield from` case working.

As a consequence, implicit exception chaining now also works in
the asyncio scenario of awaiting on a task when an exception is
already active.

Tests are included for both the asyncio case and the pure
generator-only case.

Lib/test/test_asyncio/test_tasks.py
Lib/test/test_generators.py
Objects/genobject.c

index 68f3b8cce9f65d7488740714d8effe6783383bff..6eb6b46ec8af7515fac7cf482f04484e6a742ccb 100644 (file)
@@ -466,6 +466,33 @@ class BaseTaskTests:
         t = outer()
         self.assertEqual(self.loop.run_until_complete(t), 1042)
 
+    def test_exception_chaining_after_await(self):
+        # Test that when awaiting on a task when an exception is already
+        # active, if the task raises an exception it will be chained
+        # with the original.
+        loop = asyncio.new_event_loop()
+        self.set_event_loop(loop)
+
+        async def raise_error():
+            raise ValueError
+
+        async def run():
+            try:
+                raise KeyError(3)
+            except Exception as exc:
+                task = self.new_task(loop, raise_error())
+                try:
+                    await task
+                except Exception as exc:
+                    self.assertEqual(type(exc), ValueError)
+                    chained = exc.__context__
+                    self.assertEqual((type(chained), chained.args),
+                        (KeyError, (3,)))
+
+        task = self.new_task(loop, run())
+        loop.run_until_complete(task)
+        loop.close()
+
     def test_cancel(self):
 
         def gen():
index e04780119968079870e4e26a6078960220c1378b..1081107ee64ace2359a99c0a87684307aac88310 100644 (file)
@@ -318,7 +318,7 @@ class ExceptionTest(unittest.TestCase):
 
 class GeneratorThrowTest(unittest.TestCase):
 
-    def test_exception_context_set(self):
+    def test_exception_context_with_yield(self):
         def f():
             try:
                 raise KeyError('a')
@@ -332,6 +332,23 @@ class GeneratorThrowTest(unittest.TestCase):
         context = cm.exception.__context__
         self.assertEqual((type(context), context.args), (KeyError, ('a',)))
 
+    def test_exception_context_with_yield_from(self):
+        def f():
+            yield
+
+        def g():
+            try:
+                raise KeyError('a')
+            except Exception:
+                yield from f()
+
+        gen = g()
+        gen.send(None)
+        with self.assertRaises(ValueError) as cm:
+            gen.throw(ValueError)
+        context = cm.exception.__context__
+        self.assertEqual((type(context), context.args), (KeyError, ('a',)))
+
     def test_throw_after_none_exc_type(self):
         def g():
             try:
index 5b253edfdcd0f634ac9e242bd2b462e53b4c70ca..fb01e581f8ae15998842f8371ca81c63707cc300 100644 (file)
@@ -217,6 +217,18 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
     assert(f->f_back == NULL);
     f->f_back = tstate->frame;
 
+    _PyErr_StackItem *gi_exc_state = &gen->gi_exc_state;
+    if (exc && gi_exc_state->exc_type != NULL &&
+        gi_exc_state->exc_type != Py_None)
+    {
+        Py_INCREF(gi_exc_state->exc_type);
+        Py_XINCREF(gi_exc_state->exc_value);
+        Py_XINCREF(gi_exc_state->exc_traceback);
+        _PyErr_ChainExceptions(gi_exc_state->exc_type,
+                               gi_exc_state->exc_value,
+                               gi_exc_state->exc_traceback);
+    }
+
     gen->gi_running = 1;
     gen->gi_exc_state.previous_item = tstate->exc_info;
     tstate->exc_info = &gen->gi_exc_state;
@@ -512,16 +524,6 @@ throw_here:
     }
 
     PyErr_Restore(typ, val, tb);
-
-    _PyErr_StackItem *gi_exc_state = &gen->gi_exc_state;
-    if (gi_exc_state->exc_type != NULL && gi_exc_state->exc_type != Py_None) {
-        Py_INCREF(gi_exc_state->exc_type);
-        Py_XINCREF(gi_exc_state->exc_value);
-        Py_XINCREF(gi_exc_state->exc_traceback);
-        _PyErr_ChainExceptions(gi_exc_state->exc_type,
-                               gi_exc_state->exc_value,
-                               gi_exc_state->exc_traceback);
-    }
     return gen_send_ex(gen, Py_None, 1, 0);
 
 failed_throw: