[Updated] D9019: procutil: avoid using os.fork() to implement runbgcommand

valentin.gatienbaron (Valentin Gatien-Baron) phabricator at mercurial-scm.org
Mon Feb 15 01:47:20 UTC 2021


valentin.gatienbaron edited the summary of this revision.
valentin.gatienbaron updated this revision to Diff 25626.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D9019?vs=23438&id=25626

BRANCH
  default

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D9019/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D9019

AFFECTED FILES
  mercurial/utils/procutil.py

CHANGE DETAILS

diff --git a/mercurial/utils/procutil.py b/mercurial/utils/procutil.py
--- a/mercurial/utils/procutil.py
+++ b/mercurial/utils/procutil.py
@@ -701,7 +701,88 @@
 
 else:
 
-    def runbgcommand(
+    def runbgcommandpy3(
+        cmd,
+        env,
+        shell=False,
+        stdout=None,
+        stderr=None,
+        ensurestart=True,
+        record_wait=None,
+        stdin_bytes=None,
+    ):
+        """Spawn a command without waiting for it to finish.
+
+
+        When `record_wait` is not None, the spawned process will not be fully
+        detached and the `record_wait` argument will be called with a the
+        `Subprocess.wait` function for the spawned process.  This is mostly
+        useful for developers that need to make sure the spawned process
+        finished before a certain point. (eg: writing test)"""
+        if pycompat.isdarwin:
+            # avoid crash in CoreFoundation in case another thread
+            # calls gui() while we're calling fork().
+            gui()
+
+        if shell:
+            script = cmd
+        else:
+            if isinstance(cmd, bytes):
+                cmd = [cmd]
+            script = b' '.join(shellquote(x) for x in cmd)
+        if record_wait is None:
+            # double-fork to completely detach from the parent process
+            script = b'( %s ) &' % script
+            start_new_session = True
+        else:
+            start_new_session = False
+            ensurestart = True
+
+        try:
+            if stdin_bytes is None:
+                stdin = subprocess.DEVNULL
+            else:
+                stdin = pycompat.unnamedtempfile()
+                stdin.write(stdin_bytes)
+                stdin.flush()
+                stdin.seek(0)
+            if stdout is None:
+                stdout = subprocess.DEVNULL
+            if stderr is None:
+                stderr = subprocess.DEVNULL
+
+            p = subprocess.Popen(
+                script,
+                shell=True,
+                env=env,
+                close_fds=True,
+                stdin=stdin,
+                stdout=stdout,
+                stderr=stderr,
+                start_new_session=start_new_session,
+            )
+        except Exception:
+            if record_wait is not None:
+                record_wait(255)
+            raise
+        finally:
+            if stdin_bytes is not None:
+                stdin.close()
+        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.
+            t = threading.Thread(target=lambda: p.wait)
+            t.daemon = True
+            t.start()
+        else:
+            returncode = p.wait
+            if record_wait is not None:
+                record_wait(returncode)
+
+    def runbgcommandpy2(
         cmd,
         env,
         shell=False,
@@ -811,3 +892,15 @@
             stdin.close()
             if record_wait is None:
                 os._exit(returncode)
+
+    if pycompat.ispy3:
+        # This branch is more robust, because it avoids running python
+        # code (hence gc finalizers, like sshpeer.__del__, which
+        # blocks).  But we can't easily do the equivalent in py2,
+        # because of the lack of start_new_session=True flag. Given
+        # that the py2 branch should die soon, the short-lived
+        # duplication seems acceptable.
+        runbgcommand = runbgcommandpy3
+    else:
+        runbgcommand = runbgcommandpy2
+



To: valentin.gatienbaron, #hg-reviewers
Cc: marmoute, pulkit, durin42, indygreg, mercurial-patches
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mercurial-scm.org/pipermail/mercurial-patches/attachments/20210215/4bebfc9d/attachment-0001.html>


More information about the Mercurial-patches mailing list