# HG changeset patch # User Patrick Mezard # Date 2010-02-06 15:50:00 # Node ID 9501cde4c0341fa6994b8b92ae38c7ab341ee93d # Parent b8e3aeb7542cacd245f6bff9d346e9d5564c06fd util: make spawndetached() handle subprocess early terminations The file-based synchronization introduced by e22695b4472f hangs when the child process fails before terminating the handshake, which the previous pipe-based version handled correctly. To fix this, the parent polling loop was fixed to detect premature terminations of the child process. diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -584,9 +584,11 @@ def service(opts, parentfn=None, initfn= elif runargs[i].startswith('--cwd'): del runargs[i:i + 2] break - pid = util.spawndetached(runargs) - while os.path.exists(lockpath): - time.sleep(0.1) + def condfn(): + return not os.path.exists(lockpath) + pid = util.rundetached(runargs, condfn) + if pid < 0: + raise util.Abort(_('child process failed to start')) finally: try: os.unlink(lockpath) diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -16,7 +16,7 @@ hide platform-specific details from the from i18n import _ import error, osutil, encoding import cStringIO, errno, re, shutil, sys, tempfile, traceback -import os, stat, time, calendar, textwrap +import os, stat, time, calendar, textwrap, signal import imp # Python compatibility @@ -1308,3 +1308,37 @@ def hgcmd(): if main_is_frozen(): return [sys.executable] return gethgcmd() + +def rundetached(args, condfn): + """Execute the argument list in a detached process. + + condfn is a callable which is called repeatedly and should return + True once the child process is known to have started successfully. + At this point, the child process PID is returned. If the child + process fails to start or finishes before condfn() evaluates to + True, return -1. + """ + # Windows case is easier because the child process is either + # successfully starting and validating the condition or exiting + # on failure. We just poll on its PID. On Unix, if the child + # process fails to start, it will be left in a zombie state until + # the parent wait on it, which we cannot do since we expect a long + # running process on success. Instead we listen for SIGCHLD telling + # us our child process terminated. + terminated = set() + def handler(signum, frame): + terminated.add(os.wait()) + prevhandler = None + if hasattr(signal, 'SIGCHLD'): + prevhandler = signal.signal(signal.SIGCHLD, handler) + try: + pid = spawndetached(args) + while not condfn(): + if ((pid in terminated or not testpid(pid)) + and not condfn()): + return -1 + time.sleep(0.1) + return pid + finally: + if prevhandler is not None: + signal.signal(signal.SIGCHLD, prevhandler) diff --git a/tests/test-inotify-issue1208.out b/tests/test-inotify-issue1208.out --- a/tests/test-inotify-issue1208.out +++ b/tests/test-inotify-issue1208.out @@ -1,6 +1,6 @@ % fail abort: inotify-server: cannot start: .hg/inotify.sock is a broken symlink -inotify-client: could not talk to new inotify server: No such file or directory +inotify-client: could not start inotify server: child process failed to start abort: inotify-server: cannot start: .hg/inotify.sock is a broken symlink % inserve % status