Skip to content

Commit 0b7629c

Browse files
authored
bpo-30038: fix race condition in signal delivery + wakeup fd (#1082) (#2075)
Before, it was possible to get the following sequence of events (especially on Windows, where the C-level signal handler for SIGINT is run in a separate thread): - SIGINT arrives - trip_signal is called - trip_signal writes to the wakeup fd - the main thread wakes up from select()-or-equivalent - the main thread checks for pending signals, but doesn't see any - the main thread drains the wakeup fd - the main thread goes back to sleep - trip_signal sets is_tripped=1 and calls Py_AddPendingCall to notify the main thread the it should run the Python-level signal handler - the main thread doesn't notice because it's asleep This has been causing repeated failures in the Trio test suite: python-trio/trio#119 (cherry picked from commit 4ae0149)
1 parent 12cbd87 commit 0b7629c

1 file changed

Lines changed: 26 additions & 7 deletions

File tree

Modules/signalmodule.c

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,32 @@ trip_signal(int sig_num)
244244

245245
Handlers[sig_num].tripped = 1;
246246

247+
if (!is_tripped) {
248+
/* Set is_tripped after setting .tripped, as it gets
249+
cleared in PyErr_CheckSignals() before .tripped. */
250+
is_tripped = 1;
251+
Py_AddPendingCall(checksignals_witharg, NULL);
252+
}
253+
254+
/* And then write to the wakeup fd *after* setting all the globals and
255+
doing the Py_AddPendingCall. We used to write to the wakeup fd and then
256+
set the flag, but this allowed the following sequence of events
257+
(especially on windows, where trip_signal runs in a new thread):
258+
259+
- main thread blocks on select([wakeup_fd], ...)
260+
- signal arrives
261+
- trip_signal writes to the wakeup fd
262+
- the main thread wakes up
263+
- the main thread checks the signal flags, sees that they're unset
264+
- the main thread empties the wakeup fd
265+
- the main thread goes back to sleep
266+
- trip_signal sets the flags to request the Python-level signal handler
267+
be run
268+
- the main thread doesn't notice, because it's asleep
269+
270+
See bpo-30038 for more details.
271+
*/
272+
247273
#ifdef MS_WINDOWS
248274
fd = Py_SAFE_DOWNCAST(wakeup.fd, SOCKET_T, int);
249275
#else
@@ -281,13 +307,6 @@ trip_signal(int sig_num)
281307
}
282308
}
283309
}
284-
285-
if (!is_tripped) {
286-
/* Set is_tripped after setting .tripped, as it gets
287-
cleared in PyErr_CheckSignals() before .tripped. */
288-
is_tripped = 1;
289-
Py_AddPendingCall(checksignals_witharg, NULL);
290-
}
291310
}
292311

293312
static void

0 commit comments

Comments
 (0)