Skip to content

Commit 1c505d4

Browse files
committed
bpo-29587: make gen.throw() chain exceptions with yield from
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 b0be6b3 commit 1c505d4

File tree

3 files changed

+57
-11
lines changed

3 files changed

+57
-11
lines changed

Lib/test/test_asyncio/test_tasks.py

Lines changed: 27 additions & 0 deletions
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

Lines changed: 18 additions & 1 deletion
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

Lines changed: 12 additions & 10 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy