(Contributed by Serhiy Storchaka in :issue:`32856`.)
+* Optimize signal handling in multithreaded applications. If a thread different
+ than the main thread gets a signal, the bytecode evaluation loop is no longer
+ interrupted at each bytecode instruction to check for pending signals which
+ cannot be handled. Only the main thread of the main interpreter can handle
+ signals.
+
+ Previously, the bytecode evaluation loop was interrupted at each instruction
+ until the main thread handles signals.
+ (Contributed by Victor Stinner in :issue:`40010`.)
+
Build and C API Changes
=======================
--- /dev/null
+Optimize signal handling in multithreaded applications. If a thread different
+than the main thread gets a signal, the bytecode evaluation loop is no longer
+interrupted at each bytecode instruction to check for pending signals which
+cannot be handled. Only the main thread of the main interpreter can handle
+signals.
+
+Previously, the bytecode evaluation loop was interrupted at each instruction
+until the main thread handles signals.
static size_t opcache_global_misses = 0;
#endif
+
+/* Only handle signals on the main thread of the main interpreter. */
+static int
+thread_can_handle_signals(void)
+{
+ return (PyThread_get_thread_ident() == _PyRuntime.main_thread);
+}
+
+
/* This can set eval_breaker to 0 even though gil_drop_request became
1. We believe this is all right because the eval loop will release
the GIL eventually anyway. */
_Py_atomic_store_relaxed( \
&(ceval2)->eval_breaker, \
_Py_atomic_load_relaxed(&(ceval)->gil_drop_request) | \
- _Py_atomic_load_relaxed(&(ceval)->signals_pending) | \
+ (_Py_atomic_load_relaxed(&(ceval)->signals_pending) \
+ && thread_can_handle_signals()) | \
_Py_atomic_load_relaxed(&(ceval2)->pending.calls_to_do) | \
(ceval2)->pending.async_exc)
COMPUTE_EVAL_BREAKER(ceval, ceval2); \
} while (0)
+/* eval_breaker is not set to 1 if thread_can_handle_signals() is false. */
#define SIGNAL_PENDING_SIGNALS(ceval, ceval2) \
do { \
_Py_atomic_store_relaxed(&(ceval)->signals_pending, 1); \
- _Py_atomic_store_relaxed(&(ceval2)->eval_breaker, 1); \
+ COMPUTE_EVAL_BREAKER(ceval, ceval2); \
} while (0)
#define UNSIGNAL_PENDING_SIGNALS(ceval, ceval2) \
{
_PyRuntimeState *runtime = tstate->interp->runtime;
- /* Only handle signals on main thread */
- if (PyThread_get_thread_ident() != runtime->main_thread) {
+ if (!thread_can_handle_signals()) {
return 0;
}
/*
COND_SIGNAL(gil->switch_cond);
MUTEX_UNLOCK(gil->switch_mutex);
#endif
+
if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
RESET_GIL_DROP_REQUEST(ceval, ceval2);
}
+ else {
+ /* bpo-40010: eval_breaker should be recomputed to be set to 1 if there
+ a pending signal: signal received by another thread which cannot
+ handle signals.
+
+ Note: RESET_GIL_DROP_REQUEST() calls COMPUTE_EVAL_BREAKER(). */
+ COMPUTE_EVAL_BREAKER(ceval, ceval2);
+ }
int must_exit = tstate_must_exit(tstate);