Skip to content

Commit 5a3a71d

Browse files
authored
bpo-40010: Optimize signal handling in multithreaded applications (GH-19067)
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. Changes: * COMPUTE_EVAL_BREAKER() and SIGNAL_PENDING_SIGNALS() no longer set eval_breaker to 1 if the current thread cannot handle signals. * take_gil() now always recomputes eval_breaker.
1 parent 77248a2 commit 5a3a71d

File tree

4 files changed

+41
-4
lines changed

4 files changed

+41
-4
lines changed

Doc/whatsnew/3.9.rst

+10
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,16 @@ Optimizations
411411

412412
(Contributed by Serhiy Storchaka in :issue:`32856`.)
413413

414+
* Optimize signal handling in multithreaded applications. If a thread different
415+
than the main thread gets a signal, the bytecode evaluation loop is no longer
416+
interrupted at each bytecode instruction to check for pending signals which
417+
cannot be handled. Only the main thread of the main interpreter can handle
418+
signals.
419+
420+
Previously, the bytecode evaluation loop was interrupted at each instruction
421+
until the main thread handles signals.
422+
(Contributed by Victor Stinner in :issue:`40010`.)
423+
414424

415425
Build and C API Changes
416426
=======================
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Optimize signal handling in multithreaded applications. If a thread different
2+
than the main thread gets a signal, the bytecode evaluation loop is no longer
3+
interrupted at each bytecode instruction to check for pending signals which
4+
cannot be handled. Only the main thread of the main interpreter can handle
5+
signals.
6+
7+
Previously, the bytecode evaluation loop was interrupted at each instruction
8+
until the main thread handles signals.

Python/ceval.c

+14-4
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,24 @@ static size_t opcache_global_hits = 0;
120120
static size_t opcache_global_misses = 0;
121121
#endif
122122

123+
124+
/* Only handle signals on the main thread of the main interpreter. */
125+
static int
126+
thread_can_handle_signals(void)
127+
{
128+
return (PyThread_get_thread_ident() == _PyRuntime.main_thread);
129+
}
130+
131+
123132
/* This can set eval_breaker to 0 even though gil_drop_request became
124133
1. We believe this is all right because the eval loop will release
125134
the GIL eventually anyway. */
126135
#define COMPUTE_EVAL_BREAKER(ceval, ceval2) \
127136
_Py_atomic_store_relaxed( \
128137
&(ceval2)->eval_breaker, \
129138
_Py_atomic_load_relaxed(&(ceval)->gil_drop_request) | \
130-
_Py_atomic_load_relaxed(&(ceval)->signals_pending) | \
139+
(_Py_atomic_load_relaxed(&(ceval)->signals_pending) \
140+
&& thread_can_handle_signals()) | \
131141
_Py_atomic_load_relaxed(&(ceval2)->pending.calls_to_do) | \
132142
(ceval2)->pending.async_exc)
133143

@@ -156,10 +166,11 @@ static size_t opcache_global_misses = 0;
156166
COMPUTE_EVAL_BREAKER(ceval, ceval2); \
157167
} while (0)
158168

169+
/* eval_breaker is not set to 1 if thread_can_handle_signals() is false. */
159170
#define SIGNAL_PENDING_SIGNALS(ceval, ceval2) \
160171
do { \
161172
_Py_atomic_store_relaxed(&(ceval)->signals_pending, 1); \
162-
_Py_atomic_store_relaxed(&(ceval2)->eval_breaker, 1); \
173+
COMPUTE_EVAL_BREAKER(ceval, ceval2); \
163174
} while (0)
164175

165176
#define UNSIGNAL_PENDING_SIGNALS(ceval, ceval2) \
@@ -540,8 +551,7 @@ handle_signals(PyThreadState *tstate)
540551
{
541552
_PyRuntimeState *runtime = tstate->interp->runtime;
542553

543-
/* Only handle signals on main thread */
544-
if (PyThread_get_thread_ident() != runtime->main_thread) {
554+
if (!thread_can_handle_signals()) {
545555
return 0;
546556
}
547557
/*

Python/ceval_gil.h

+9
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,18 @@ take_gil(PyThreadState *tstate)
280280
COND_SIGNAL(gil->switch_cond);
281281
MUTEX_UNLOCK(gil->switch_mutex);
282282
#endif
283+
283284
if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
284285
RESET_GIL_DROP_REQUEST(ceval, ceval2);
285286
}
287+
else {
288+
/* bpo-40010: eval_breaker should be recomputed to be set to 1 if there
289+
a pending signal: signal received by another thread which cannot
290+
handle signals.
291+
292+
Note: RESET_GIL_DROP_REQUEST() calls COMPUTE_EVAL_BREAKER(). */
293+
COMPUTE_EVAL_BREAKER(ceval, ceval2);
294+
}
286295

287296
int must_exit = tstate_must_exit(tstate);
288297

0 commit comments

Comments
 (0)