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.
--- 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