Skip to content

[3.6] bpo-30039: Don't run signal handlers while resuming a yield fro… #1640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Lib/test/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,35 @@

from test import support

_testcapi = support.import_module('_testcapi')


# This tests to make sure that if a SIGINT arrives just before we send into a
# yield from chain, the KeyboardInterrupt is raised in the innermost
# generator (see bpo-30039).
class SignalAndYieldFromTest(unittest.TestCase):

def generator1(self):
return (yield from self.generator2())

def generator2(self):
try:
yield
except KeyboardInterrupt:
return "PASSED"
else:
return "FAILED"

def test_raise_and_yield_from(self):
gen = self.generator1()
gen.send(None)
try:
_testcapi.raise_SIGINT_then_send_None(gen)
except BaseException as _exc:
exc = _exc
self.assertIs(type(exc), StopIteration)
self.assertEqual(exc.value, "PASSED")


class FinalizationTest(unittest.TestCase):

Expand Down
4 changes: 4 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Core and Builtins
- bpo-25794: Fixed type.__setattr__() and type.__delattr__() for
non-interned attribute names. Based on patch by Eryk Sun.

- bpo-30039: If a KeyboardInterrupt happens when the interpreter is in
the middle of resuming a chain of nested 'yield from' or 'await'
calls, it's now correctly delivered to the innermost frame.

- bpo-12414: sys.getsizeof() on a code object now returns the sizes
which includes the code struct and sizes of objects which it references.
Patch by Dong-hee Na.
Expand Down
24 changes: 24 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4125,6 +4125,29 @@ test_pyobject_fastcallkeywords(PyObject *self, PyObject *args)
}


static PyObject *
raise_SIGINT_then_send_None(PyObject *self, PyObject *args)
{
PyGenObject *gen;

if (!PyArg_ParseTuple(args, "O!", &PyGen_Type, &gen))
return NULL;

/* This is used in a test to check what happens if a signal arrives just
as we're in the process of entering a yield from chain (see
bpo-30039).

Needs to be done in C, because:
- we don't have a Python wrapper for raise()
- we need to make sure that the Python-level signal handler doesn't run
*before* we enter the generator frame, which is impossible in Python
because we check for signals before every bytecode operation.
*/
raise(SIGINT);
return _PyGen_Send(gen, Py_None);
}


static PyMethodDef TestMethods[] = {
{"raise_exception", raise_exception, METH_VARARGS},
{"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS},
Expand Down Expand Up @@ -4331,6 +4354,7 @@ static PyMethodDef TestMethods[] = {
{"pyobject_fastcall", test_pyobject_fastcall, METH_VARARGS},
{"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS},
{"pyobject_fastcallkeywords", test_pyobject_fastcallkeywords, METH_VARARGS},
{"raise_SIGINT_then_send_None", raise_SIGINT_then_send_None, METH_VARARGS},
{NULL, NULL} /* sentinel */
};

Expand Down
17 changes: 14 additions & 3 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1119,9 +1119,20 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
Py_MakePendingCalls() above. */

if (_Py_atomic_load_relaxed(&eval_breaker)) {
if (_Py_OPCODE(*next_instr) == SETUP_FINALLY) {
/* Make the last opcode before
a try: finally: block uninterruptible. */
if (_Py_OPCODE(*next_instr) == SETUP_FINALLY ||
_Py_OPCODE(*next_instr) == YIELD_FROM) {
/* Two cases where we skip running signal handlers and other
pending calls:
- If we're about to enter the try: of a try/finally (not
*very* useful, but might help in some cases and it's
traditional)
- If we're resuming a chain of nested 'yield from' or
'await' calls, then each frame is parked with YIELD_FROM
as its next opcode. If the user hit control-C we want to
wait until we've reached the innermost frame before
running the signal handler and raising KeyboardInterrupt
(see bpo-30039).
*/
goto fast_next_opcode;
}
if (_Py_atomic_load_relaxed(&pendingcalls_to_do)) {
Expand Down