bpo-30038: fix race condition in signal delivery + wakeup fd (#1082) · python/cpython@4ae0149 (original) (raw)

Original file line number Diff line number Diff line change
@@ -244,6 +244,32 @@ trip_signal(int sig_num)
244 244
245 245 Handlers[sig_num].tripped = 1;
246 246
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 +
247 273 #ifdef MS_WINDOWS
248 274 fd = Py_SAFE_DOWNCAST(wakeup.fd, SOCKET_T, int);
249 275 #else
@@ -281,13 +307,6 @@ trip_signal(int sig_num)
281 307 }
282 308 }
283 309 }
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 - }
291 310 }
292 311
293 312 static void