]> granicus.if.org Git - python/commitdiff
bpo-30643: Fix race condition in signal wakeup in forkserver (followup to PR #1989...
authorAntoine Pitrou <pitrou@free.fr>
Tue, 13 Jun 2017 07:46:06 +0000 (09:46 +0200)
committerGitHub <noreply@github.com>
Tue, 13 Jun 2017 07:46:06 +0000 (09:46 +0200)
* Fix race condition in signal wakeup in forkserver (followup to PR #1989)

There's an admittedly well-known race condition where ECHILD can arrive
just before the C function epoll_wait() and the latter wouldn't therefore
return EINTR.  The solution is to use set_wakeup_fd(), which was designed
to avoid such race conditions.

* Reset wakeup fd in child

Lib/multiprocessing/forkserver.py

index ddbd0c257399baf05e3459fc4a2ada062cba9302..70105158e559de9b380a0395e8cb7a345b73b5ae 100644 (file)
@@ -150,15 +150,15 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
     util._close_stdin()
 
     sig_r, sig_w = os.pipe()
+    os.set_blocking(sig_r, False)
     os.set_blocking(sig_w, False)
 
     def sigchld_handler(*_unused):
-        try:
-            os.write(sig_w, b'.')
-        except BlockingIOError:
-            pass
+        # Dummy signal handler, doesn't do anything
+        pass
 
     # letting SIGINT through avoids KeyboardInterrupt tracebacks
+    # unblocking SIGCHLD allows the wakeup fd to notify our event loop
     handlers = {
         signal.SIGCHLD: sigchld_handler,
         signal.SIGINT: signal.SIG_DFL,
@@ -166,6 +166,9 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
     old_handlers = {sig: signal.signal(sig, val)
                     for (sig, val) in handlers.items()}
 
+    # calling os.write() in the Python signal handler is racy
+    signal.set_wakeup_fd(sig_w)
+
     # map child pids to client fds
     pid_to_fd = {}
 
@@ -252,6 +255,7 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
 
 def _serve_one(child_r, fds, unused_fds, handlers):
     # close unnecessary stuff and reset signal handlers
+    signal.set_wakeup_fd(-1)
     for sig, val in handlers.items():
         signal.signal(sig, val)
     for fd in unused_fds: