changeset 30414:5069a8a40b1b

worker: make waitforworkers reentrant We are going to use it in the SIGCHLD handler. The handler will be executed in the main thread with the non-blocking version of waitpid, while the waitforworkers thread runs the blocking version. It's possible that one of them collects a worker and makes the other error out (no child to wait). This patch handles these errors: ECHILD is ignored. EINTR needs a retry. The "pids" set is designed to be only modifiable by "waitforworkers". And we only remove items after a successful waitpid. Since a child process can only be "waitpid"-ed once. It's guaranteed that "pids.remove(p)" won't be called with duplicated "p"s. And once a "p" is removed from "pids", that "p" does not need to be killed or waited any more.
author Jun Wu <quark@fb.com>
date Tue, 15 Nov 2016 02:12:16 +0000
parents 9c25a1a8c685
children e8fb03cfbbde
files mercurial/worker.py
diffstat 1 files changed, 13 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/worker.py	Tue Nov 15 02:10:40 2016 +0000
+++ b/mercurial/worker.py	Tue Nov 15 02:12:16 2016 +0000
@@ -98,9 +98,20 @@
                 if err.errno != errno.ESRCH:
                     raise
     def waitforworkers(blocking=True):
-        for pid in pids:
-            p, st = os.waitpid(pid, 0 if blocking else os.WNOHANG)
+        for pid in pids.copy():
+            p = st = 0
+            while True:
+                try:
+                    p, st = os.waitpid(pid, (0 if blocking else os.WNOHANG))
+                except OSError as e:
+                    if e.errno == errno.EINTR:
+                        continue
+                    elif e.errno == errno.ECHILD:
+                        break # ignore ECHILD
+                    else:
+                        raise
             if p:
+                pids.remove(p)
                 st = _exitstatus(st)
             if st and not problem[0]:
                 problem[0] = st