mirror of
https://github.com/python/cpython.git
synced 2025-07-15 23:35:23 +00:00
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.
This commit is contained in:
parent
aa28423201
commit
451f291baa
4 changed files with 13 additions and 36 deletions
|
@ -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 *
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue