# HG changeset patch # User Jun Wu # Date 1479175936 0 # Node ID 5069a8a40b1b3b9d2b9035a76c54fe11eec8e87f # Parent 9c25a1a8c685401af99688141843ae0c8a0b0d31 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. diff -r 9c25a1a8c685 -r 5069a8a40b1b mercurial/worker.py --- 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