Skip to content

Commit bb67636

Browse files
committed
Add locks for FT build in signalmodule
1 parent 7b12a41 commit bb67636

1 file changed

Lines changed: 54 additions & 4 deletions

File tree

‎Modules/signalmodule.c‎

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS
1010
#include "pycore_fileutils.h" // _Py_BEGIN_SUPPRESS_IPH
1111
#include "pycore_interpframe.h" // _PyThreadState_GetFrame()
12+
#include "pycore_lock.h" // _PyMutex_at_fork_reinit()
1213
#include "pycore_moduleobject.h" // _PyModule_GetState()
1314
#include "pycore_pyerrors.h" // _PyErr_SetString()
1415
#include "pycore_pystate.h" // _PyThreadState_GET()
@@ -128,7 +129,6 @@ typedef struct {
128129
PyTypeObject *siginfo_type;
129130
} _signal_module_state;
130131

131-
132132
Py_LOCAL_INLINE(PyObject *)
133133
get_handler(int i)
134134
{
@@ -143,6 +143,34 @@ set_handler(int i, PyObject* func)
143143
_Py_atomic_store_ptr(&Handlers[i].func, func);
144144
}
145145

146+
/* Serialize readers of Handlers[] with signal.signal() replacing a handler:
147+
the slot's reference is handed to the caller of signal.signal(), who may
148+
release it while another thread has loaded the pointer but not yet
149+
incref'ed it. Never take this mutex from a signal handler.
150+
151+
The GIL already makes "load the handler and incref it" atomic with respect
152+
to signal.signal(), so the lock is only needed in the free-threaded build;
153+
the helpers do nothing in the default build. */
154+
#ifdef Py_GIL_DISABLED
155+
static PyMutex handlers_mutex;
156+
#endif
157+
158+
Py_LOCAL_INLINE(void)
159+
claim_signalnum(void)
160+
{
161+
#ifdef Py_GIL_DISABLED
162+
PyMutex_Lock(&handlers_mutex);
163+
#endif
164+
}
165+
166+
Py_LOCAL_INLINE(void)
167+
release_signalnum(void)
168+
{
169+
#ifdef Py_GIL_DISABLED
170+
PyMutex_Unlock(&handlers_mutex);
171+
#endif
172+
}
173+
146174

147175
static inline _signal_module_state*
148176
get_signal_state(PyObject *module)
@@ -532,8 +560,10 @@ signal_signal_impl(PyObject *module, int signalnum, PyObject *handler)
532560
return NULL;
533561
}
534562

563+
claim_signalnum();
535564
old_handler = get_handler(signalnum);
536565
set_handler(signalnum, Py_NewRef(handler));
566+
release_signalnum();
537567

538568
if (old_handler != NULL) {
539569
return old_handler;
@@ -569,9 +599,12 @@ signal_getsignal_impl(PyObject *module, int signalnum)
569599
"signal number out of range");
570600
return NULL;
571601
}
572-
old_handler = get_handler(signalnum);
602+
claim_signalnum();
603+
old_handler = Py_XNewRef(get_handler(signalnum));
604+
release_signalnum();
605+
573606
if (old_handler != NULL) {
574-
return Py_NewRef(old_handler);
607+
return old_handler;
575608
}
576609
else {
577610
Py_RETURN_NONE;
@@ -1595,8 +1628,10 @@ signal_get_set_handlers(signal_state_t *state, PyObject *mod_dict)
15951628
}
15961629
// If signal_module_exec() is called more than one, we must
15971630
// clear the strong reference to the previous function.
1631+
claim_signalnum();
15981632
PyObject* old_func = get_handler(signum);
15991633
set_handler(signum, Py_NewRef(func));
1634+
release_signalnum();
16001635
Py_XDECREF(old_func);
16011636
}
16021637

@@ -1609,7 +1644,9 @@ signal_get_set_handlers(signal_state_t *state, PyObject *mod_dict)
16091644
return -1;
16101645
}
16111646

1647+
claim_signalnum();
16121648
set_handler(SIGINT, int_handler);
1649+
release_signalnum();
16131650
Py_DECREF(sigint_func);
16141651
PyOS_setsig(SIGINT, signal_handler);
16151652
}
@@ -1744,9 +1781,11 @@ _PySignal_Fini(void)
17441781

17451782
// Restore default signals and clear handlers
17461783
for (int signum = 1; signum < Py_NSIG; signum++) {
1747-
PyObject *func = get_handler(signum);
17481784
_Py_atomic_store_int_relaxed(&Handlers[signum].tripped, 0);
1785+
claim_signalnum();
1786+
PyObject *func = get_handler(signum);
17491787
set_handler(signum, NULL);
1788+
release_signalnum();
17501789
if (func != NULL
17511790
&& func != Py_None
17521791
&& !compare_handler(func, state->default_handler)
@@ -2056,13 +2095,24 @@ _clear_pending_signals(void)
20562095
}
20572096
}
20582097

2098+
static void
2099+
reinit_handlers_mutex(void)
2100+
{
2101+
/* Another thread may have held the mutex when fork() was called; it does
2102+
not exist in the child, so nobody would ever release it. */
2103+
#ifdef Py_GIL_DISABLED
2104+
_PyMutex_at_fork_reinit(&handlers_mutex);
2105+
#endif
2106+
}
2107+
20592108
void
20602109
_PySignal_AfterFork(void)
20612110
{
20622111
/* Clear the signal flags after forking so that they aren't handled
20632112
* in both processes if they came in just before the fork() but before
20642113
* the interpreter had an opportunity to call the handlers. issue9535. */
20652114
_clear_pending_signals();
2115+
reinit_handlers_mutex();
20662116
}
20672117
#endif /* HAVE_FORK */
20682118

0 commit comments

Comments
 (0)