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