https://github.com/python/cpython/commit/451f291baaff918228ace4e8257be42737d7654a
commit: 451f291baaff918228ace4e8257be42737d7654a
branch: main
author: Sam Gross <[email protected]>
committer: colesbury <[email protected]>
date: 2025-02-13T12:29:03-05:00
summary:
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.
files:
M Include/internal/pycore_pylifecycle.h
M Modules/main.c
M Python/pythonrun.c
M Tools/c-analyzer/TODO
diff --git a/Include/internal/pycore_pylifecycle.h
b/Include/internal/pycore_pylifecycle.h
index f426ae0e103b9c..a5f9418ec05718 100644
--- a/Include/internal/pycore_pylifecycle.h
+++ b/Include/internal/pycore_pylifecycle.h
@@ -75,7 +75,7 @@ extern PyStatus _Py_PreInitializeFromConfig(
extern wchar_t * _Py_GetStdlibDir(void);
-extern int _Py_HandleSystemExit(int *exitcode_p);
+extern int _Py_HandleSystemExitAndKeyboardInterrupt(int *exitcode_p);
extern PyObject* _PyErr_WriteUnraisableDefaultHook(PyObject *unraisable);
diff --git a/Modules/main.c b/Modules/main.c
index f8a2438cdd0d93..0aebce0d3b7aa2 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -99,7 +99,7 @@ static int
pymain_err_print(int *exitcode_p)
{
int exitcode;
- if (_Py_HandleSystemExit(&exitcode)) {
+ if (_Py_HandleSystemExitAndKeyboardInterrupt(&exitcode)) {
*exitcode_p = exitcode;
return 1;
}
@@ -292,11 +292,7 @@ pymain_start_pyrepl_no_main(void)
goto done;
}
if (!PyDict_SetItemString(kwargs, "pythonstartup", _PyLong_GetOne())) {
- _PyRuntime.signals.unhandled_keyboard_interrupt = 0;
console_result = PyObject_Call(console, empty_tuple, kwargs);
- if (!console_result && PyErr_Occurred() == PyExc_KeyboardInterrupt) {
- _PyRuntime.signals.unhandled_keyboard_interrupt = 1;
- }
if (console_result == NULL) {
res = pymain_exit_err_print();
}
@@ -338,11 +334,7 @@ pymain_run_module(const wchar_t *modname, int set_argv0)
Py_DECREF(module);
return pymain_exit_err_print();
}
- _PyRuntime.signals.unhandled_keyboard_interrupt = 0;
result = PyObject_Call(runmodule, runargs, NULL);
- if (!result && PyErr_Occurred() == PyExc_KeyboardInterrupt) {
- _PyRuntime.signals.unhandled_keyboard_interrupt = 1;
- }
Py_DECREF(runmodule);
Py_DECREF(module);
Py_DECREF(runargs);
@@ -763,6 +755,8 @@ Py_RunMain(void)
{
int exitcode = 0;
+ _PyRuntime.signals.unhandled_keyboard_interrupt = 0;
+
pymain_run_python(&exitcode);
if (Py_FinalizeEx() < 0) {
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
index ae0df9685ac159..945e267ef72c6f 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 *
diff --git a/Tools/c-analyzer/TODO b/Tools/c-analyzer/TODO
index edd0c4bc7fdaa6..d509489176b945 100644
--- a/Tools/c-analyzer/TODO
+++ b/Tools/c-analyzer/TODO
@@ -532,7 +532,7 @@ Python/pythonrun.c:PyId_stdin
_Py_IDENTIFIER(
Python/pythonrun.c:PyId_stdout
_Py_IDENTIFIER(stdout)
Python/pythonrun.c:PyRun_InteractiveOneObjectEx():PyId___main__
_Py_IDENTIFIER(__main__)
Python/pythonrun.c:PyRun_InteractiveOneObjectEx():PyId_encoding
_Py_IDENTIFIER(encoding)
-Python/pythonrun.c:_Py_HandleSystemExit():PyId_code
_Py_IDENTIFIER(code)
+Python/pythonrun.c:_Py_HandleSystemExitAndKeyboardInterrupt():PyId_code
_Py_IDENTIFIER(code)
Python/pythonrun.c:parse_syntax_error():PyId_filename
_Py_IDENTIFIER(filename)
Python/pythonrun.c:parse_syntax_error():PyId_lineno
_Py_IDENTIFIER(lineno)
Python/pythonrun.c:parse_syntax_error():PyId_msg
_Py_IDENTIFIER(msg)
_______________________________________________
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]