Skip to content

Commit 725892c

Browse files
committed
Add exc_context parameter to {gen/coro}.throw()
This solves the exception context leaking into the generator by allowing an alternate exception context to be passed in. It also removes the need for an exposed function that directly manipulates the exception state since the details are handled in the C implementation.
1 parent d0afd15 commit 725892c

2 files changed

Lines changed: 86 additions & 29 deletions

File tree

Lib/contextlib.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,10 @@ def __exit__(self, typ, value, traceback):
235235
# However, since we're still currently handling the exception
236236
# that we throw into the generator here, the exception context
237237
# in the generator wouldn't change.
238-
# To work around this, we forcefully set the current exception
239-
# context to be what it was just before the generator's yield
240-
# statement before throwing the current exception into it.
238+
# To work around this, we pass the exception context as it was
239+
# just before the generator's yield statement into the generator.
241240
# (see gh-111676).
242-
self.gen.throw(value)
241+
self.gen.throw(value, exc_context=exc_context)
243242
except StopIteration as exc:
244243
# Suppress StopIteration *unless* it's the same exception that
245244
# was passed to throw(). This prevents a StopIteration

Objects/genobject.c

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -612,8 +612,8 @@ gen_throw_current_exception(PyGenObject *gen)
612612
}
613613

614614
PyDoc_STRVAR(throw_doc,
615-
"throw(value)\n\
616-
throw(type[,value[,tb]])\n\
615+
"throw(value, /, *, exc_context=None)\n\
616+
throw(type[,value[,tb]], /, *, exc_context=None)\n\
617617
\n\
618618
Raise exception in generator, return next yielded value or raise\n\
619619
StopIteration.\n\
@@ -622,7 +622,8 @@ and may be removed in a future version of Python.");
622622

623623
static PyObject *
624624
_gen_throw(PyGenObject *gen, int close_on_genexit,
625-
PyObject *typ, PyObject *val, PyObject *tb)
625+
PyObject *typ, PyObject *val, PyObject *tb,
626+
PyObject *exc_context)
626627
{
627628
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
628629
do {
@@ -680,7 +681,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
680681
/* Close the generator that we are currently iterating with
681682
'yield from' or awaiting on with 'await'. */
682683
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
683-
typ, val, tb);
684+
typ, val, tb, exc_context);
684685
tstate->current_frame = prev;
685686
frame->previous = NULL;
686687
}
@@ -715,21 +716,53 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
715716

716717
throw_here:
717718
assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_EXECUTING);
719+
720+
/*
721+
If an exception context is provided, set it (and restore it after)
722+
723+
This ensures that after the generator handles the thrown-in exception,
724+
_PyErr_GetTopmostException will return the provided exception context
725+
instead of whatever exception was next in the stack.
726+
*/
727+
PyThreadState *tstate = NULL;
728+
PyObject *prev_exc = NULL;
729+
if (exc_context != NULL) {
730+
tstate = _PyThreadState_GET();
731+
assert(tstate != NULL);
732+
prev_exc = tstate->exc_info->exc_value;
733+
tstate->exc_info->exc_value = Py_XNewRef(exc_context);
734+
}
718735
if (gen_set_exception(typ, val, tb) < 0) {
736+
if (tstate != NULL) {
737+
Py_XSETREF(tstate->exc_info->exc_value, prev_exc);
738+
}
719739
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, frame_state);
720740
return NULL;
721741
}
722-
return gen_throw_current_exception(gen);
742+
743+
PyObject *result = gen_throw_current_exception(gen);
744+
if (tstate != NULL) {
745+
Py_XSETREF(tstate->exc_info->exc_value, prev_exc);
746+
}
747+
return result;
723748
}
724749

750+
/*
751+
throw(...) method of builtins.generator instance
752+
throw(value, /, *, exc_context=None)
753+
throw(type[,value[,tb]], /, *, exc_context=None)
725754
755+
Raise exception in generator, return next yielded value or raise
756+
StopIteration.
757+
*/
726758
static PyObject *
727-
gen_throw(PyObject *op, PyObject *const *args, Py_ssize_t nargs)
759+
gen_throw(PyObject *op, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
728760
{
729761
PyGenObject *gen = _PyGen_CAST(op);
730762
PyObject *typ;
731763
PyObject *tb = NULL;
732764
PyObject *val = NULL;
765+
PyObject *exc_context = NULL;
733766

734767
if (!_PyArg_CheckPositional("throw", nargs, 1, 3)) {
735768
return NULL;
@@ -742,6 +775,18 @@ gen_throw(PyObject *op, PyObject *const *args, Py_ssize_t nargs)
742775
return NULL;
743776
}
744777
}
778+
779+
static const char * const _keywords[] = {"", "", "", "exc_context", NULL};
780+
static _PyArg_Parser _parser = {
781+
.keywords = _keywords,
782+
.fname = "throw",
783+
};
784+
PyObject *argsbuf[4];
785+
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 3, 0, 1, argsbuf);
786+
if (!args) {
787+
return NULL;
788+
}
789+
745790
typ = args[0];
746791
if (nargs == 3) {
747792
val = args[1];
@@ -750,7 +795,19 @@ gen_throw(PyObject *op, PyObject *const *args, Py_ssize_t nargs)
750795
else if (nargs == 2) {
751796
val = args[1];
752797
}
753-
return _gen_throw(gen, 1, typ, val, tb);
798+
799+
if (kwnames && PyTuple_GET_SIZE(kwnames)){
800+
exc_context = args[3];
801+
if (!Py_IsNone(exc_context) && !PyExceptionInstance_Check(exc_context)){
802+
PyErr_SetString(PyExc_TypeError, "exc_context must be an Exception object or None");
803+
return NULL;
804+
}
805+
}
806+
/* default to current exception */
807+
if (exc_context == NULL){
808+
exc_context = _PyErr_GetTopmostException(_PyThreadState_GET())->exc_value;
809+
}
810+
return _gen_throw(gen, 1, typ, val, tb, exc_context);
754811
}
755812

756813

@@ -1020,7 +1077,7 @@ PyDoc_STRVAR(sizeof__doc__,
10201077

10211078
static PyMethodDef gen_methods[] = {
10221079
{"send", gen_send, METH_O, send_doc},
1023-
{"throw", _PyCFunction_CAST(gen_throw), METH_FASTCALL, throw_doc},
1080+
{"throw", _PyCFunction_CAST(gen_throw), METH_FASTCALL|METH_KEYWORDS, throw_doc},
10241081
{"close", gen_close, METH_NOARGS, close_doc},
10251082
{"__sizeof__", gen_sizeof, METH_NOARGS, sizeof__doc__},
10261083
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
@@ -1357,8 +1414,8 @@ PyDoc_STRVAR(coro_send_doc,
13571414
return next iterated value or raise StopIteration.");
13581415

13591416
PyDoc_STRVAR(coro_throw_doc,
1360-
"throw(value)\n\
1361-
throw(type[,value[,traceback]])\n\
1417+
"throw(value, /, *, exc_context=None)\n\
1418+
throw(type[,value[,tb]], *, exc_context=None)\n\
13621419
\n\
13631420
Raise exception in coroutine, return next iterated value or raise\n\
13641421
StopIteration.\n\
@@ -1371,7 +1428,7 @@ PyDoc_STRVAR(coro_close_doc,
13711428

13721429
static PyMethodDef coro_methods[] = {
13731430
{"send", gen_send, METH_O, coro_send_doc},
1374-
{"throw",_PyCFunction_CAST(gen_throw), METH_FASTCALL, coro_throw_doc},
1431+
{"throw", _PyCFunction_CAST(gen_throw), METH_FASTCALL|METH_KEYWORDS, coro_throw_doc},
13751432
{"close", gen_close, METH_NOARGS, coro_close_doc},
13761433
{"__sizeof__", gen_sizeof, METH_NOARGS, sizeof__doc__},
13771434
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
@@ -1461,10 +1518,10 @@ coro_wrapper_send(PyObject *self, PyObject *arg)
14611518
}
14621519

14631520
static PyObject *
1464-
coro_wrapper_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
1521+
coro_wrapper_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
14651522
{
14661523
PyCoroWrapper *cw = _PyCoroWrapper_CAST(self);
1467-
return gen_throw((PyObject*)cw->cw_coroutine, args, nargs);
1524+
return gen_throw((PyObject*)cw->cw_coroutine, args, nargs, kwnames);
14681525
}
14691526

14701527
static PyObject *
@@ -1484,8 +1541,8 @@ coro_wrapper_traverse(PyObject *self, visitproc visit, void *arg)
14841541

14851542
static PyMethodDef coro_wrapper_methods[] = {
14861543
{"send", coro_wrapper_send, METH_O, coro_send_doc},
1487-
{"throw", _PyCFunction_CAST(coro_wrapper_throw), METH_FASTCALL,
1488-
coro_throw_doc},
1544+
{"throw", _PyCFunction_CAST(coro_wrapper_throw),
1545+
METH_FASTCALL|METH_KEYWORDS, coro_throw_doc},
14891546
{"close", coro_wrapper_close, METH_NOARGS, coro_close_doc},
14901547
{NULL, NULL} /* Sentinel */
14911548
};
@@ -2024,7 +2081,7 @@ async_gen_asend_iternext(PyObject *ags)
20242081

20252082

20262083
static PyObject *
2027-
async_gen_asend_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
2084+
async_gen_asend_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
20282085
{
20292086
PyAsyncGenASend *o = _PyAsyncGenASend_CAST(self);
20302087

@@ -2048,7 +2105,7 @@ async_gen_asend_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
20482105
o->ags_gen->ag_running_async = 1;
20492106
}
20502107

2051-
PyObject *result = gen_throw((PyObject*)o->ags_gen, args, nargs);
2108+
PyObject *result = gen_throw((PyObject*)o->ags_gen, args, nargs, kwnames);
20522109
result = async_gen_unwrap_value(o->ags_gen, result);
20532110

20542111
if (result == NULL) {
@@ -2068,7 +2125,7 @@ async_gen_asend_close(PyObject *self, PyObject *args)
20682125
Py_RETURN_NONE;
20692126
}
20702127

2071-
PyObject *result = async_gen_asend_throw(self, &PyExc_GeneratorExit, 1);
2128+
PyObject *result = async_gen_asend_throw(self, &PyExc_GeneratorExit, 1, NULL);
20722129
if (result == NULL) {
20732130
if (PyErr_ExceptionMatches(PyExc_StopIteration) ||
20742131
PyErr_ExceptionMatches(PyExc_StopAsyncIteration) ||
@@ -2096,7 +2153,8 @@ async_gen_asend_finalize(PyObject *self)
20962153

20972154
static PyMethodDef async_gen_asend_methods[] = {
20982155
{"send", async_gen_asend_send, METH_O, send_doc},
2099-
{"throw", _PyCFunction_CAST(async_gen_asend_throw), METH_FASTCALL, throw_doc},
2156+
{"throw", _PyCFunction_CAST(async_gen_asend_throw),
2157+
METH_FASTCALL|METH_KEYWORDS, throw_doc},
21002158
{"close", async_gen_asend_close, METH_NOARGS, close_doc},
21012159
{NULL, NULL} /* Sentinel */
21022160
};
@@ -2349,7 +2407,7 @@ async_gen_athrow_send(PyObject *self, PyObject *arg)
23492407
retval = _gen_throw((PyGenObject *)gen,
23502408
0, /* Do not close generator when
23512409
PyExc_GeneratorExit is passed */
2352-
PyExc_GeneratorExit, NULL, NULL);
2410+
PyExc_GeneratorExit, NULL, NULL, NULL);
23532411

23542412
if (retval && _PyAsyncGenWrappedValue_CheckExact(retval)) {
23552413
Py_DECREF(retval);
@@ -2359,7 +2417,7 @@ async_gen_athrow_send(PyObject *self, PyObject *arg)
23592417
retval = _gen_throw((PyGenObject *)gen,
23602418
0, /* Do not close generator when
23612419
PyExc_GeneratorExit is passed */
2362-
o->agt_typ, o->agt_val, o->agt_tb);
2420+
o->agt_typ, o->agt_val, o->agt_tb, NULL);
23632421
retval = async_gen_unwrap_value(o->agt_gen, retval);
23642422
}
23652423
if (retval == NULL) {
@@ -2417,7 +2475,7 @@ async_gen_athrow_send(PyObject *self, PyObject *arg)
24172475

24182476

24192477
static PyObject *
2420-
async_gen_athrow_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
2478+
async_gen_athrow_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
24212479
{
24222480
PyAsyncGenAThrow *o = _PyAsyncGenAThrow_CAST(self);
24232481

@@ -2448,7 +2506,7 @@ async_gen_athrow_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
24482506
o->agt_gen->ag_running_async = 1;
24492507
}
24502508

2451-
PyObject *retval = gen_throw((PyObject*)o->agt_gen, args, nargs);
2509+
PyObject *retval = gen_throw((PyObject*)o->agt_gen, args, nargs, kwnames);
24522510
if (o->agt_typ) {
24532511
retval = async_gen_unwrap_value(o->agt_gen, retval);
24542512
if (retval == NULL) {
@@ -2501,7 +2559,7 @@ async_gen_athrow_close(PyObject *self, PyObject *args)
25012559
Py_RETURN_NONE;
25022560
}
25032561
PyObject *result = async_gen_athrow_throw((PyObject*)agt,
2504-
&PyExc_GeneratorExit, 1);
2562+
&PyExc_GeneratorExit, 1, NULL);
25052563
if (result == NULL) {
25062564
if (PyErr_ExceptionMatches(PyExc_StopIteration) ||
25072565
PyErr_ExceptionMatches(PyExc_StopAsyncIteration) ||
@@ -2532,7 +2590,7 @@ async_gen_athrow_finalize(PyObject *op)
25322590
static PyMethodDef async_gen_athrow_methods[] = {
25332591
{"send", async_gen_athrow_send, METH_O, send_doc},
25342592
{"throw", _PyCFunction_CAST(async_gen_athrow_throw),
2535-
METH_FASTCALL, throw_doc},
2593+
METH_FASTCALL|METH_KEYWORDS, throw_doc},
25362594
{"close", async_gen_athrow_close, METH_NOARGS, close_doc},
25372595
{NULL, NULL} /* Sentinel */
25382596
};

0 commit comments

Comments
 (0)