From f6792a7d80e3135ee3da64398366d520a914f7b2 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 18 Sep 2026 09:17:09 +0200 Subject: [PATCH] Make PyErr_CheckSignals() cheap when no signal is pending PyErr_CheckSignals() is called from long-running C loops (int multiplication and division) and from every PyObject_Repr() and PyObject_Str(). Before looking at whether any signal had arrived, it fetched the interpreter config for the remote debugger and called pthread_self() for the main-thread check. Test the cheap flags first: _PyErr_CheckSignalsTstate() now does the main-thread check itself, after the is_tripped test, and runs the handlers in an out-of-line helper; _PyRunRemoteDebugger() tests its pending flag before fetching the config. Co-Authored-By: Claude Fable 5.1 --- Modules/signalmodule.c | 31 +++++++++++++++++++------------ Python/ceval_gil.c | 10 ++++------ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index bc5aef55648e07..c749f3ee0c2b19 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -1796,25 +1796,18 @@ PyErr_CheckSignals(void) _PyRunRemoteDebugger(tstate); #endif - if (_Py_ThreadCanHandleSignals(tstate->interp)) { - if (_PyErr_CheckSignalsTstate(tstate) < 0) { - return -1; - } + if (_PyErr_CheckSignalsTstate(tstate) < 0) { + return -1; } return 0; } -/* Declared in cpython/pyerrors.h */ -int -_PyErr_CheckSignalsTstate(PyThreadState *tstate) +// Out of line, to keep the common case of _PyErr_CheckSignalsTstate() cheap. +Py_NO_INLINE static int +run_tripped_handlers(PyThreadState *tstate) { - _Py_CHECK_EMSCRIPTEN_SIGNALS(); - if (!_Py_atomic_load_int(&is_tripped)) { - return 0; - } - /* * The is_tripped variable is meant to speed up the calls to * PyErr_CheckSignals (both directly or via pending calls) when no @@ -1895,6 +1888,20 @@ _PyErr_CheckSignalsTstate(PyThreadState *tstate) } +/* Declared in cpython/pyerrors.h */ +int +_PyErr_CheckSignalsTstate(PyThreadState *tstate) +{ + _Py_CHECK_EMSCRIPTEN_SIGNALS(); + if (!_Py_atomic_load_int(&is_tripped)) { + return 0; + } + if (!_Py_ThreadCanHandleSignals(tstate->interp)) { + return 0; + } + return run_tripped_handlers(tstate); +} + int _PyErr_CheckSignals(void) diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index 2425bc1b39f0dc..346c731d7bac72 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -829,9 +829,6 @@ handle_signals(PyThreadState *tstate) { assert(_PyThreadState_CheckConsistency(tstate)); _Py_unset_eval_breaker_bit(tstate, _PY_SIGNALS_PENDING_BIT); - if (!_Py_ThreadCanHandleSignals(tstate->interp)) { - return 0; - } if (_PyErr_CheckSignalsTstate(tstate) < 0) { /* On failure, re-schedule a call to handle_signals(). */ _Py_set_eval_breaker_bit(tstate, _PY_SIGNALS_PENDING_BIT); @@ -1257,10 +1254,11 @@ static inline void run_remote_debugger_script(PyObject *path) int _PyRunRemoteDebugger(PyThreadState *tstate) { + if (tstate->remote_debugger_support.debugger_pending_call != 1) { + return 0; + } const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp); - if (config->remote_debug == 1 - && tstate->remote_debugger_support.debugger_pending_call == 1) - { + if (config->remote_debug == 1) { tstate->remote_debugger_support.debugger_pending_call = 0; // Immediately make a copy in case of a race with another debugger