# HG changeset patch # User Rodrigo Damazio Bovendorp # Date 2020-05-07 10:14:52 # Node ID ed684a82e29bbea8556c3079f6159a9159689078 # Parent f727939f3513f4ae2600bfa8fa96026048855106 procutil: always waiting on child processes to prevent zombies with 'hg serve' When runbgcommand is invoked by an extension with ensurestart=False, we never called waitpid - which is fine in most cases, except if that's happening on a command server (e.g. chg), in which case the child defunct process will just sit there for as long as the server is running. The actual semantics of SIGCHLD signal handling is a lot more complex than it seems, and the POSIX standard *seems* to read that it's ignored by default and everything would just work without the waitpid if we're not listening for it, but the truth is that it's only ignored if we *explicitly* set it to SIG_IGN. We further cannot set it to SIG_IGN or to a catch-all handler across all of 'hg serve', because Python's suprocess.Popen relies on that signal, and a few specific parts of hg also set custom handlers, so instead we wait for specific PIDs in dedicated threads. I did a poor-man's benchmark of the thread creation and it seems to take about 1ms, which is way better than the 20+ms from ensurestart=True. Differential Revision: https://phab.mercurial-scm.org/D8497 diff --git a/mercurial/utils/procutil.py b/mercurial/utils/procutil.py --- a/mercurial/utils/procutil.py +++ b/mercurial/utils/procutil.py @@ -16,6 +16,7 @@ import os import signal import subprocess import sys +import threading import time from ..i18n import _ @@ -604,6 +605,14 @@ else: pid = os.fork() if pid: if not ensurestart: + # Even though we're not waiting on the child process, + # we still must call waitpid() on it at some point so + # it's not a zombie/defunct. This is especially relevant for + # chg since the parent process won't die anytime soon. + # We use a thread to make the overhead tiny. + def _do_wait(): + os.waitpid(pid, 0) + threading.Thread(target=_do_wait, daemon=True).start() return # Parent process (_pid, status) = os.waitpid(pid, 0)