https://github.com/python/cpython/commit/e972f76ac1e1ff9d36dc466424f5922f4a5b301e
commit: e972f76ac1e1ff9d36dc466424f5922f4a5b301e
branch: 3.15
author: Mark Shannon <[email protected]>
committer: markshannon <[email protected]>
date: 2026-08-03T16:23:34+01:00
summary:
[3.15] Backport GH-148874: Make sure that mngr.__exit__() is always called in a
with statement (GH-150911) (GH-154830)
* Even if there is an interrupt during the call to mngr.__enter__()
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-06-04-12-53-10.gh-issue-148874.r121cG.rst
M Lib/test/test_with.py
M Modules/_testinternalcapi.c
M Modules/_testinternalcapi/test_cases.c.h
M Python/bytecodes.c
M Python/ceval_macros.h
M Python/generated_cases.c.h
M Python/optimizer.c
M Tools/c-analyzer/cpython/ignored.tsv
diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py
index f16611b29a2658c..60aaa5cd548cbf0 100644
--- a/Lib/test/test_with.py
+++ b/Lib/test/test_with.py
@@ -10,6 +10,7 @@
import unittest
from collections import deque
from contextlib import _GeneratorContextManager, contextmanager, nullcontext
+from _testinternalcapi import SelfInterruptingContextManager
def do_with(obj):
@@ -850,5 +851,21 @@ def exit_raises():
expected)
+class InterruptDuringEnter(unittest.TestCase):
+
+ def test_exit_called_after_interrupt(self):
+ cm = SelfInterruptingContextManager()
+ self.assertFalse(cm.within())
+ try:
+ with cm:
+ self.assertTrue(cm.within())
+ except KeyboardInterrupt:
+ self.assertFalse(cm.within())
+ return
+ except:
+ self.fail("Wrong exception raised")
+ self.fail("No exception raised")
+
+
if __name__ == '__main__':
unittest.main()
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-04-12-53-10.gh-issue-148874.r121cG.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-04-12-53-10.gh-issue-148874.r121cG.rst
new file mode 100644
index 000000000000000..95f93338beb9129
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-04-12-53-10.gh-issue-148874.r121cG.rst
@@ -0,0 +1,3 @@
+Ignore interrupts immediately after calling the ``__enter__`` method of a
+context menager in a ``with`` statement. This ensures that the ``__exit__``
+method is always called in a ``with`` statement.
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index 344d663624b270c..124e06e5302f45e 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -3194,6 +3194,66 @@
test_thread_state_ensure_from_view_interp_switch(PyObject *self, PyObject *unuse
Py_RETURN_NONE;
}
+/* Self interrupting context manager */
+
+typedef struct {
+ PyObject_HEAD
+ int within;
+} SelfInterruptingContextManagerObject;
+
+static PyObject *
+new_self_interrupting(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ SelfInterruptingContextManagerObject *self =
+ (SelfInterruptingContextManagerObject *)type->tp_alloc(type, 0);
+ if (self != NULL) {
+ self->within = 0;
+ }
+ return (PyObject *)self;
+}
+
+static PyObject *
+self_interrupting_enter(PyObject *op, PyObject *Py_UNUSED(dummy))
+{
+ ((SelfInterruptingContextManagerObject *)op)->within = 1;
+ PyThreadState *tstate = PyThreadState_Get();
+ PyObject *ki = Py_NewRef(PyExc_KeyboardInterrupt);
+ PyObject *old_exc = _Py_atomic_exchange_ptr(&tstate->async_exc, ki);
+ _Py_set_eval_breaker_bit(tstate, _PY_ASYNC_EXCEPTION_BIT);
+ Py_XDECREF(old_exc);
+
+ return Py_NewRef(op);
+}
+
+static PyObject *
+self_interrupting_within(PyObject *op, PyObject *Py_UNUSED(dummy))
+{
+ return PyBool_FromLong(((SelfInterruptingContextManagerObject
*)op)->within);
+}
+
+static PyObject *
+self_interrupting_exit(PyObject *op, PyObject *Py_UNUSED(args)) {
+ ((SelfInterruptingContextManagerObject *)op)->within = 0;
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef self_interrupting_methods[] = {
+ {"__enter__", self_interrupting_enter, METH_NOARGS, NULL},
+ {"within", self_interrupting_within, METH_NOARGS, NULL},
+ {"__exit__", self_interrupting_exit, METH_VARARGS, NULL},
+ {NULL, NULL} /* sentinel */
+};
+
+static PyTypeObject SelfInterruptingContextManager_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "_testcapi.SelfInterruptingContextManager",
+ sizeof(SelfInterruptingContextManagerObject),
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE,
+ .tp_new = new_self_interrupting,
+ .tp_methods = self_interrupting_methods,
+};
+
+
static PyMethodDef module_functions[] = {
{"get_configs", get_configs, METH_NOARGS},
{"get_eval_frame_stats", get_eval_frame_stats, METH_NOARGS, NULL},
@@ -3416,6 +3476,11 @@ module_exec(PyObject *module)
}
#endif
+ if (PyType_Ready(&SelfInterruptingContextManager_Type) < 0) {
+ return 1;
+ }
+ PyModule_AddObject(module, "SelfInterruptingContextManager", (PyObject
*)&SelfInterruptingContextManager_Type);
+
return 0;
}
diff --git a/Modules/_testinternalcapi/test_cases.c.h
b/Modules/_testinternalcapi/test_cases.c.h
index 12f028618f5c69a..e19ff840458e0e0 100644
--- a/Modules/_testinternalcapi/test_cases.c.h
+++ b/Modules/_testinternalcapi/test_cases.c.h
@@ -1866,7 +1866,7 @@
stack_pointer += -1 - oparg;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -2364,7 +2364,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -2452,7 +2452,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -2536,7 +2536,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -2643,7 +2643,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -2749,7 +2749,7 @@
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -3056,7 +3056,7 @@
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -3544,7 +3544,7 @@
stack_pointer += -2 - oparg;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -3943,7 +3943,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4055,7 +4055,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4173,7 +4173,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4303,7 +4303,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4378,7 +4378,7 @@
stack_pointer += -1 - oparg;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4668,7 +4668,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4743,7 +4743,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -6914,7 +6914,7 @@
stack_pointer += -1 - oparg;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -7082,7 +7082,7 @@
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 4f8a67d33fff529..f39a5f0b07a5fcc 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -161,7 +161,7 @@ dummy_func(
}
replaced op(_CHECK_PERIODIC_AT_END, (--)) {
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
ERROR_IF(err != 0);
}
diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h
index dcc4f577680dc31..1ebbc096db72254 100644
--- a/Python/ceval_macros.h
+++ b/Python/ceval_macros.h
@@ -527,6 +527,22 @@ check_periodics(PyThreadState *tstate) {
return 0;
}
+static inline int
+check_periodics_at_end(PyThreadState *tstate, _PyInterpreterFrame *frame) {
+ _Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY();
+ QSBR_QUIESCENT_STATE(tstate);
+ if (_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) &
_PY_EVAL_EVENTS_MASK) {
+ // Do not handle pending interrupts if the previous instruction was
LOAD_SPECIAL
+ // This may also not handle interrupts if a cache looks like
LOAD_SPECIAL,
+ // but this is benign as we won't skip periodic checks indefinitely.
+ if (frame->instr_ptr[-1].op.code == LOAD_SPECIAL) {
+ return 0;
+ }
+ return _Py_HandlePending(tstate);
+ }
+ return 0;
+}
+
// Mark the generator as executing. Returns true if the state was changed,
// false if it was already executing or finished.
static inline bool
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index 24ffb07830adf47..512c358014fcffe 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -1866,7 +1866,7 @@
stack_pointer += -1 - oparg;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -2364,7 +2364,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -2452,7 +2452,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -2536,7 +2536,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -2643,7 +2643,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -2749,7 +2749,7 @@
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -3056,7 +3056,7 @@
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -3544,7 +3544,7 @@
stack_pointer += -2 - oparg;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -3943,7 +3943,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4055,7 +4055,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4173,7 +4173,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4303,7 +4303,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4378,7 +4378,7 @@
stack_pointer += -1 - oparg;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4668,7 +4668,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -4743,7 +4743,7 @@
// _CHECK_PERIODIC_AT_END
{
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -6914,7 +6914,7 @@
stack_pointer += -1 - oparg;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
@@ -7082,7 +7082,7 @@
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int err = check_periodics(tstate);
+ int err = check_periodics_at_end(tstate, frame);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (err != 0) {
JUMP_TO_LABEL(error);
diff --git a/Python/optimizer.c b/Python/optimizer.c
index a39d0494ddc5d5b..1699ba5544ff591 100644
--- a/Python/optimizer.c
+++ b/Python/optimizer.c
@@ -994,10 +994,15 @@ _PyJit_translate_single_bytecode_to_trace(
case OPARG_REPLACED:
uop = _PyUOp_Replacements[uop];
assert(uop != 0);
-
uint32_t next_inst = target + 1 +
_PyOpcode_Caches[_PyOpcode_Deopt[opcode]];
if (uop == _TIER2_RESUME_CHECK) {
- target = next_inst;
+ if (this_instr[-1].op.code == LOAD_SPECIAL) {
+ // Don't check eval breaker immediately after
LOAD_SPECIAL
+ uop = _NOP;
+ }
+ else {
+ target = next_inst;
+ }
}
else {
int extended_arg = orig_oparg > 255;
diff --git a/Tools/c-analyzer/cpython/ignored.tsv
b/Tools/c-analyzer/cpython/ignored.tsv
index bf08e5568205e7a..6e18593ad698570 100644
--- a/Tools/c-analyzer/cpython/ignored.tsv
+++ b/Tools/c-analyzer/cpython/ignored.tsv
@@ -577,6 +577,7 @@ Modules/_testimportmultiple.c -
_testimportmultiple -
Modules/_testinternalcapi.c - pending_identify_result -
Modules/_testinternalcapi.c - Test_EvalFrame_Resumes -
Modules/_testinternalcapi.c - Test_EvalFrame_Loads -
+Modules/_testinternalcapi.c - SelfInterruptingContextManager_Type
-
Modules/_testinternalcapi/interpreter.c - Test_EvalFrame_Resumes
-
Modules/_testinternalcapi/interpreter.c - Test_EvalFrame_Loads
-
Modules/_testlimitedcapi/slots.c - TestMethods -
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]