Mercurial > hg
changeset 10344:9501cde4c034
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.
author | Patrick Mezard <pmezard@gmail.com> |
---|---|
date | Sat, 06 Feb 2010 16:50:00 +0100 |
parents | b8e3aeb7542c |
children | bc2866bdf3e0 |
files | mercurial/cmdutil.py mercurial/util.py tests/test-inotify-issue1208.out |
diffstat | 3 files changed, 41 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/cmdutil.py Sat Feb 06 17:31:54 2010 +0100 +++ b/mercurial/cmdutil.py Sat Feb 06 16:50:00 2010 +0100 @@ -584,9 +584,11 @@ 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)
--- a/mercurial/util.py Sat Feb 06 17:31:54 2010 +0100 +++ b/mercurial/util.py Sat Feb 06 16:50:00 2010 +0100 @@ -16,7 +16,7 @@ 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 @@ 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)
--- a/tests/test-inotify-issue1208.out Sat Feb 06 17:31:54 2010 +0100 +++ b/tests/test-inotify-issue1208.out Sat Feb 06 16:50:00 2010 +0100 @@ -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