util: make spawndetached() handle subprocess early terminations
authorPatrick Mezard <pmezard@gmail.com>
Sat, 06 Feb 2010 16:50:00 +0100
changeset 10344 9501cde4c034
parent 10343 b8e3aeb7542c
child 10345 bc2866bdf3e0
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.
mercurial/cmdutil.py
mercurial/util.py
tests/test-inotify-issue1208.out
--- 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