Skip to content

Commit 75cd8e4

Browse files
authored
bpo-29587: Make gen.throw() chain exceptions with yield from (GH-19858)
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.
1 parent d6fb53f commit 75cd8e4

File tree

3 files changed

+57
-11
lines changed

3 files changed

+57
-11
lines changed

Lib/test/test_asyncio/test_tasks.py

+27
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,33 @@ async def inner2():
466466
t = outer()
467467
self.assertEqual(self.loop.run_until_complete(t), 1042)
468468

469+
def test_exception_chaining_after_await(self):
470+
# Test that when awaiting on a task when an exception is already
471+
# active, if the task raises an exception it will be chained
472+
# with the original.
473+
loop = asyncio.new_event_loop()
474+
self.set_event_loop(loop)
475+
476+
async def raise_error():
477+
raise ValueError
478+
479+
async def run():
480+
try:
481+
raise KeyError(3)
482+
except Exception as exc:
483+
task = self.new_task(loop, raise_error())
484+
try:
485+
await task
486+
except Exception as exc:
487+
self.assertEqual(type(exc), ValueError)
488+
chained = exc.__context__
489+
self.assertEqual((type(chained), chained.args),
490+
(KeyError, (3,)))
491+
492+
task = self.new_task(loop, run())
493+
loop.run_until_complete(task)
494+
loop.close()
495+
469496
def test_cancel(self):
470497

471498
def gen():

Lib/test/test_generators.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def g():
318318

319319
class GeneratorThrowTest(unittest.TestCase):
320320

321-
def test_exception_context_set(self):
321+
def test_exception_context_with_yield(self):
322322
def f():
323323
try:
324324
raise KeyError('a')
@@ -332,6 +332,23 @@ def f():
332332
context = cm.exception.__context__
333333
self.assertEqual((type(context), context.args), (KeyError, ('a',)))
334334

335+
def test_exception_context_with_yield_from(self):
336+
def f():
337+
yield
338+
339+
def g():
340+
try:
341+
raise KeyError('a')
342+
except Exception:
343+
yield from f()
344+
345+
gen = g()
346+
gen.send(None)
347+
with self.assertRaises(ValueError) as cm:
348+
gen.throw(ValueError)
349+
context = cm.exception.__context__
350+
self.assertEqual((type(context), context.args), (KeyError, ('a',)))
351+
335352
def test_throw_after_none_exc_type(self):
336353
def g():
337354
try:

Objects/genobject.c

+12-10
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,18 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
217217
assert(f->f_back == NULL);
218218
f->f_back = tstate->frame;
219219

220+
_PyErr_StackItem *gi_exc_state = &gen->gi_exc_state;
221+
if (exc && gi_exc_state->exc_type != NULL &&
222+
gi_exc_state->exc_type != Py_None)
223+
{
224+
Py_INCREF(gi_exc_state->exc_type);
225+
Py_XINCREF(gi_exc_state->exc_value);
226+
Py_XINCREF(gi_exc_state->exc_traceback);
227+
_PyErr_ChainExceptions(gi_exc_state->exc_type,
228+
gi_exc_state->exc_value,
229+
gi_exc_state->exc_traceback);
230+
}
231+
220232
gen->gi_running = 1;
221233
gen->gi_exc_state.previous_item = tstate->exc_info;
222234
tstate->exc_info = &gen->gi_exc_state;
@@ -512,16 +524,6 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
512524
}
513525

514526
PyErr_Restore(typ, val, tb);
515-
516-
_PyErr_StackItem *gi_exc_state = &gen->gi_exc_state;
517-
if (gi_exc_state->exc_type != NULL && gi_exc_state->exc_type != Py_None) {
518-
Py_INCREF(gi_exc_state->exc_type);
519-
Py_XINCREF(gi_exc_state->exc_value);
520-
Py_XINCREF(gi_exc_state->exc_traceback);
521-
_PyErr_ChainExceptions(gi_exc_state->exc_type,
522-
gi_exc_state->exc_value,
523-
gi_exc_state->exc_traceback);
524-
}
525527
return gen_send_ex(gen, Py_None, 1, 0);
526528

527529
failed_throw:

0 commit comments

Comments
 (0)