aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Python
diff options
context:
space:
mode:
authorSam Gross <colesbury@gmail.com>2025-02-13 12:29:03 -0500
committerGitHub <noreply@github.com>2025-02-13 12:29:03 -0500
commit451f291baaff918228ace4e8257be42737d7654a (patch)
tree0824f1f7ab3b13a292bf1c4a9a01b13c63915e2a /Python
parentaa284232013693103431fb672a0bcabac3d4200b (diff)
downloadcpython-451f291baaff918228ace4e8257be42737d7654a.tar.gz
cpython-451f291baaff918228ace4e8257be42737d7654a.zip
gh-128130: Fix unhandled keyboard interrupt data race (gh-129975)
Use an atomic operation when setting `_PyRuntime.signals.unhandled_keyboard_interrupt`. We now only clear the variable at the start of `_PyRun_Main`, which is the same function where we check it. This avoids race conditions where previously another thread might call `run_eval_code_obj()` and erroneously clear the unhandled keyboard interrupt.
Diffstat (limited to 'Python')
-rw-r--r--Python/pythonrun.c33
1 files changed, 8 insertions, 25 deletions
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
index ae0df9685ac..945e267ef72 100644
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -589,8 +589,13 @@ parse_exit_code(PyObject *code, int *exitcode_p)
}
int
-_Py_HandleSystemExit(int *exitcode_p)
+_Py_HandleSystemExitAndKeyboardInterrupt(int *exitcode_p)
{
+ if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
+ _Py_atomic_store_int(&_PyRuntime.signals.unhandled_keyboard_interrupt, 1);
+ return 0;
+ }
+
int inspect = _Py_GetConfig()->inspect;
if (inspect) {
/* Don't exit if -i flag was given. This flag is set to 0
@@ -646,7 +651,7 @@ static void
handle_system_exit(void)
{
int exitcode;
- if (_Py_HandleSystemExit(&exitcode)) {
+ if (_Py_HandleSystemExitAndKeyboardInterrupt(&exitcode)) {
Py_Exit(exitcode);
}
}
@@ -1105,8 +1110,6 @@ _PyErr_Display(PyObject *file, PyObject *unused, PyObject *value, PyObject *tb)
}
}
- int unhandled_keyboard_interrupt = _PyRuntime.signals.unhandled_keyboard_interrupt;
-
// Try first with the stdlib traceback module
PyObject *print_exception_fn = PyImport_ImportModuleAttrString(
"traceback",
@@ -1120,11 +1123,9 @@ _PyErr_Display(PyObject *file, PyObject *unused, PyObject *value, PyObject *tb)
Py_XDECREF(print_exception_fn);
if (result) {
Py_DECREF(result);
- _PyRuntime.signals.unhandled_keyboard_interrupt = unhandled_keyboard_interrupt;
return;
}
fallback:
- _PyRuntime.signals.unhandled_keyboard_interrupt = unhandled_keyboard_interrupt;
#ifdef Py_DEBUG
if (PyErr_Occurred()) {
PyErr_FormatUnraisable(
@@ -1297,20 +1298,6 @@ flush_io(void)
static PyObject *
run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, PyObject *locals)
{
- PyObject *v;
- /*
- * We explicitly re-initialize _Py_UnhandledKeyboardInterrupt every eval
- * _just in case_ someone is calling into an embedded Python where they
- * don't care about an uncaught KeyboardInterrupt exception (why didn't they
- * leave config.install_signal_handlers set to 0?!?) but then later call
- * Py_Main() itself (which _checks_ this flag and dies with a signal after
- * its interpreter exits). We don't want a previous embedded interpreter's
- * uncaught exception to trigger an unexplained signal exit from a future
- * Py_Main() based one.
- */
- // XXX Isn't this dealt with by the move to _PyRuntimeState?
- _PyRuntime.signals.unhandled_keyboard_interrupt = 0;
-
/* Set globals['__builtins__'] if it doesn't exist */
if (!globals || !PyDict_Check(globals)) {
PyErr_SetString(PyExc_SystemError, "globals must be a real dict");
@@ -1328,11 +1315,7 @@ run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, Py
}
}
- v = PyEval_EvalCode((PyObject*)co, globals, locals);
- if (!v && _PyErr_Occurred(tstate) == PyExc_KeyboardInterrupt) {
- _PyRuntime.signals.unhandled_keyboard_interrupt = 1;
- }
- return v;
+ return PyEval_EvalCode((PyObject*)co, globals, locals);
}
static PyObject *