[3.10] GH--93592: Fix frame chain when throwing exceptions into coroutines (GH-95207)

This commit is contained in:
Kristján Valur Jónsson 2022-08-23 11:23:39 +00:00 committed by GitHub
parent 9c34d644ed
commit d23ab79952
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 35 additions and 4 deletions

View file

@ -4,6 +4,7 @@
import pickle import pickle
import sys import sys
import types import types
import traceback
import unittest import unittest
import warnings import warnings
from test import support from test import support
@ -2119,6 +2120,29 @@ async def run_gen():
return 'end' return 'end'
self.assertEqual(run_async(run_gen()), ([], 'end')) self.assertEqual(run_async(run_gen()), ([], 'end'))
def test_stack_in_coroutine_throw(self):
# Regression test for https://github.com/python/cpython/issues/93592
async def a():
return await b()
async def b():
return await c()
@types.coroutine
def c():
try:
# traceback.print_stack()
yield len(traceback.extract_stack())
except ZeroDivisionError:
# traceback.print_stack()
yield len(traceback.extract_stack())
coro = a()
len_send = coro.send(None)
len_throw = coro.throw(ZeroDivisionError)
# before fixing, visible stack from throw would be shorter than from send.
self.assertEqual(len_send, len_throw)
class CoroAsyncIOCompatTest(unittest.TestCase): class CoroAsyncIOCompatTest(unittest.TestCase):

View file

@ -0,0 +1,2 @@
``coroutine.throw()`` now properly initializes the ``frame.f_back`` when resuming a stack of coroutines.
This allows e.g. ``traceback.print_stack()`` to work correctly when an exception (such as ``CancelledError``) is thrown into a coroutine.

View file

@ -439,20 +439,25 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
/* `yf` is a generator or a coroutine. */ /* `yf` is a generator or a coroutine. */
PyThreadState *tstate = _PyThreadState_GET(); PyThreadState *tstate = _PyThreadState_GET();
PyFrameObject *f = tstate->frame; PyFrameObject *f = tstate->frame;
PyFrameObject *gf = gen->gi_frame;
/* Since we are fast-tracking things by skipping the eval loop, /* Since we are fast-tracking things by skipping the eval loop,
we need to update the current frame so the stack trace we need to update the current frame so the stack trace
will be reported correctly to the user. */ will be reported correctly to the user. */
/* XXX We should probably be updating the current frame /* XXX We should probably be updating the current frame
somewhere in ceval.c. */ somewhere in ceval.c. */
tstate->frame = gen->gi_frame; assert(gf->f_back == NULL);
Py_XINCREF(f);
gf->f_back = f;
tstate->frame = gf;
/* Close the generator that we are currently iterating with /* Close the generator that we are currently iterating with
'yield from' or awaiting on with 'await'. */ 'yield from' or awaiting on with 'await'. */
PyFrameState state = gen->gi_frame->f_state; PyFrameState state = gf->f_state;
gen->gi_frame->f_state = FRAME_EXECUTING; gf->f_state = FRAME_EXECUTING;
ret = _gen_throw((PyGenObject *)yf, close_on_genexit, ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
typ, val, tb); typ, val, tb);
gen->gi_frame->f_state = state; gf->f_state = state;
Py_CLEAR(gf->f_back);
tstate->frame = f; tstate->frame = f;
} else { } else {
/* `yf` is an iterator or a coroutine-like object. */ /* `yf` is an iterator or a coroutine-like object. */