--- a/mercurial/util.py Sat Mar 24 14:32:34 2018 +0900
+++ b/mercurial/util.py Sat Mar 24 13:38:04 2018 +0900
@@ -22,18 +22,14 @@
import errno
import gc
import hashlib
-import imp
-import io
import itertools
import mmap
import os
import platform as pyplatform
import re as remod
import shutil
-import signal
import socket
import stat
-import subprocess
import sys
import tempfile
import time
@@ -52,6 +48,7 @@
)
from .utils import (
dateutil,
+ procutil,
stringutil,
)
@@ -69,9 +66,6 @@
queue = pycompat.queue
safehasattr = pycompat.safehasattr
socketserver = pycompat.socketserver
-stderr = pycompat.stderr
-stdin = pycompat.stdin
-stdout = pycompat.stdout
bytesio = pycompat.bytesio
# TODO deprecate stringio name, as it is a lie on Python 3.
stringio = bytesio
@@ -84,21 +78,8 @@
# workaround for win32mbcs
_filenamebytestr = pycompat.bytestr
-def isatty(fp):
- try:
- return fp.isatty()
- except AttributeError:
- return False
-
-# glibc determines buffering on first write to stdout - if we replace a TTY
-# destined stdout with a pipe destined stdout (e.g. pager), we want line
-# buffering
-if isatty(stdout):
- stdout = os.fdopen(stdout.fileno(), r'wb', 1)
-
if pycompat.iswindows:
from . import windows as platform
- stdout = platform.winstdout(stdout)
else:
from . import posix as platform
@@ -110,16 +91,10 @@
checklink = platform.checklink
copymode = platform.copymode
expandglobs = platform.expandglobs
-explainexit = platform.explainexit
-findexe = platform.findexe
getfsmountpoint = platform.getfsmountpoint
getfstype = platform.getfstype
-_gethgcmd = platform.gethgcmd
-getuser = platform.getuser
-getpid = os.getpid
groupmembers = platform.groupmembers
groupname = platform.groupname
-hidewindow = platform.hidewindow
isexec = platform.isexec
isowner = platform.isowner
listdir = osutil.listdir
@@ -136,27 +111,17 @@
parsepatchoutput = platform.parsepatchoutput
pconvert = platform.pconvert
poll = platform.poll
-popen = platform.popen
posixfile = platform.posixfile
-quotecommand = platform.quotecommand
-readpipe = platform.readpipe
rename = platform.rename
removedirs = platform.removedirs
samedevice = platform.samedevice
samefile = platform.samefile
samestat = platform.samestat
-setbinary = platform.setbinary
setflags = platform.setflags
-setsignalhandler = platform.setsignalhandler
-shellquote = platform.shellquote
-shellsplit = platform.shellsplit
-spawndetached = platform.spawndetached
split = platform.split
-sshargs = platform.sshargs
statfiles = getattr(osutil, 'statfiles', platform.statfiles)
statisexec = platform.statisexec
statislink = platform.statislink
-testpid = platform.testpid
umask = platform.umask
unlink = platform.unlink
username = platform.username
@@ -165,14 +130,6 @@
recvfds = osutil.recvfds
except AttributeError:
pass
-try:
- setprocname = osutil.setprocname
-except AttributeError:
- pass
-try:
- unblocksignal = osutil.unblocksignal
-except AttributeError:
- pass
# Python compatibility
@@ -346,8 +303,6 @@
return memoryview(sliceable)[offset:offset + length]
return memoryview(sliceable)[offset:]
-closefds = pycompat.isposix
-
_chunksize = 4096
class bufferedinputpipe(object):
@@ -464,30 +419,6 @@
return ''
raise
-def popen2(cmd, env=None, newlines=False):
- # Setting bufsize to -1 lets the system decide the buffer size.
- # The default for bufsize is 0, meaning unbuffered. This leads to
- # poor performance on Mac OS X: http://bugs.python.org/issue4194
- p = subprocess.Popen(cmd, shell=True, bufsize=-1,
- close_fds=closefds,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- universal_newlines=newlines,
- env=env)
- return p.stdin, p.stdout
-
-def popen3(cmd, env=None, newlines=False):
- stdin, stdout, stderr, p = popen4(cmd, env, newlines)
- return stdin, stdout, stderr
-
-def popen4(cmd, env=None, newlines=False, bufsize=-1):
- p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
- close_fds=closefds,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- universal_newlines=newlines,
- env=env)
- return p.stdin, p.stdout, p.stderr, p
-
class fileobjectproxy(object):
"""A proxy around file objects that tells a watcher when events occur.
@@ -1500,60 +1431,6 @@
if prop in obj.__dict__:
del obj.__dict__[prop]
-def pipefilter(s, cmd):
- '''filter string S through command CMD, returning its output'''
- p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE)
- pout, perr = p.communicate(s)
- return pout
-
-def tempfilter(s, cmd):
- '''filter string S through a pair of temporary files with CMD.
- CMD is used as a template to create the real command to be run,
- with the strings INFILE and OUTFILE replaced by the real names of
- the temporary files generated.'''
- inname, outname = None, None
- try:
- infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
- fp = os.fdopen(infd, r'wb')
- fp.write(s)
- fp.close()
- outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
- os.close(outfd)
- cmd = cmd.replace('INFILE', inname)
- cmd = cmd.replace('OUTFILE', outname)
- code = os.system(cmd)
- if pycompat.sysplatform == 'OpenVMS' and code & 1:
- code = 0
- if code:
- raise error.Abort(_("command '%s' failed: %s") %
- (cmd, explainexit(code)))
- with open(outname, 'rb') as fp:
- return fp.read()
- finally:
- try:
- if inname:
- os.unlink(inname)
- except OSError:
- pass
- try:
- if outname:
- os.unlink(outname)
- except OSError:
- pass
-
-_filtertable = {
- 'tempfile:': tempfilter,
- 'pipe:': pipefilter,
-}
-
-def filter(s, cmd):
- "filter a string through a command that transforms its input to its output"
- for name, fn in _filtertable.iteritems():
- if cmd.startswith(name):
- return fn(s, cmd[len(name):].lstrip())
- return pipefilter(s, cmd)
-
def increasingchunks(source, min=1024, max=65536):
'''return no less than min bytes per chunk while data remains,
doubling min after each chunk until it reaches max'''
@@ -1644,18 +1521,8 @@
b.reverse()
return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
-def mainfrozen():
- """return True if we are a frozen executable.
-
- The code supports py2exe (most common, Windows only) and tools/freeze
- (portable, not much used).
- """
- return (safehasattr(sys, "frozen") or # new py2exe
- safehasattr(sys, "importers") or # old py2exe
- imp.is_frozen(u"__main__")) # tools/freeze
-
# the location of data files matching the source code
-if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
+if procutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
# executable version (py2exe) doesn't support __file__
datapath = os.path.dirname(pycompat.sysexecutable)
else:
@@ -1663,92 +1530,6 @@
i18n.setdatapath(datapath)
-_hgexecutable = None
-
-def hgexecutable():
- """return location of the 'hg' executable.
-
- Defaults to $HG or 'hg' in the search path.
- """
- if _hgexecutable is None:
- hg = encoding.environ.get('HG')
- mainmod = sys.modules[r'__main__']
- if hg:
- _sethgexecutable(hg)
- elif mainfrozen():
- if getattr(sys, 'frozen', None) == 'macosx_app':
- # Env variable set by py2app
- _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
- else:
- _sethgexecutable(pycompat.sysexecutable)
- elif (os.path.basename(
- pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
- _sethgexecutable(pycompat.fsencode(mainmod.__file__))
- else:
- exe = findexe('hg') or os.path.basename(sys.argv[0])
- _sethgexecutable(exe)
- return _hgexecutable
-
-def _sethgexecutable(path):
- """set location of the 'hg' executable"""
- global _hgexecutable
- _hgexecutable = path
-
-def _testfileno(f, stdf):
- fileno = getattr(f, 'fileno', None)
- try:
- return fileno and fileno() == stdf.fileno()
- except io.UnsupportedOperation:
- return False # fileno() raised UnsupportedOperation
-
-def isstdin(f):
- return _testfileno(f, sys.__stdin__)
-
-def isstdout(f):
- return _testfileno(f, sys.__stdout__)
-
-def shellenviron(environ=None):
- """return environ with optional override, useful for shelling out"""
- def py2shell(val):
- 'convert python object into string that is useful to shell'
- if val is None or val is False:
- return '0'
- if val is True:
- return '1'
- return pycompat.bytestr(val)
- env = dict(encoding.environ)
- if environ:
- env.update((k, py2shell(v)) for k, v in environ.iteritems())
- env['HG'] = hgexecutable()
- return env
-
-def system(cmd, environ=None, cwd=None, out=None):
- '''enhanced shell command execution.
- run with environment maybe modified, maybe in different dir.
-
- if out is specified, it is assumed to be a file-like object that has a
- write() method. stdout and stderr will be redirected to out.'''
- try:
- stdout.flush()
- except Exception:
- pass
- cmd = quotecommand(cmd)
- env = shellenviron(environ)
- if out is None or isstdout(out):
- rc = subprocess.call(cmd, shell=True, close_fds=closefds,
- env=env, cwd=cwd)
- else:
- proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
- env=env, cwd=cwd, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- for line in iter(proc.stdout.readline, ''):
- out.write(line)
- proc.wait()
- rc = proc.returncode
- if pycompat.sysplatform == 'OpenVMS' and rc & 1:
- rc = 0
- return rc
-
def checksignature(func):
'''wrap a function with code to check for calling errors'''
def check(*args, **kwargs):
@@ -2133,21 +1914,6 @@
function if need.'''
return path.split(pycompat.ossep)
-def gui():
- '''Are we running in a GUI?'''
- if pycompat.isdarwin:
- if 'SSH_CONNECTION' in encoding.environ:
- # handle SSH access to a box where the user is logged in
- return False
- elif getattr(osutil, 'isgui', None):
- # check if a CoreGraphics session is available
- return osutil.isgui()
- else:
- # pure build; use a safe default
- return True
- else:
- return pycompat.iswindows or encoding.environ.get("DISPLAY")
-
def mktempcopy(name, emptyok=False, createmode=None):
"""Create a temporary file with the same contents from name
@@ -2716,56 +2482,6 @@
def expandpath(path):
return os.path.expanduser(os.path.expandvars(path))
-def hgcmd():
- """Return the command used to execute current hg
-
- This is different from hgexecutable() because on Windows we want
- to avoid things opening new shell windows like batch files, so we
- get either the python call or current executable.
- """
- if mainfrozen():
- if getattr(sys, 'frozen', None) == 'macosx_app':
- # Env variable set by py2app
- return [encoding.environ['EXECUTABLEPATH']]
- else:
- return [pycompat.sysexecutable]
- 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
- SIGCHLD = getattr(signal, 'SIGCHLD', None)
- if SIGCHLD is not None:
- prevhandler = 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)
-
def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
"""Return the result of interpolating items in the mapping into string s.
@@ -3257,7 +2973,7 @@
yield line % (fnmax, fnln, func)
def debugstacktrace(msg='stacktrace', skip=0,
- f=stderr, otherf=stdout, depth=0):
+ f=procutil.stderr, otherf=procutil.stdout, depth=0):
'''Writes a message to f (stderr) with a nicely formatted stacktrace.
Skips the 'skip' entries closest to the call, then show 'depth' entries.
By default it will flush stdout first.
@@ -4076,6 +3792,50 @@
parsedate = _deprecatedfunc(dateutil.parsedate, '4.6')
matchdate = _deprecatedfunc(dateutil.matchdate, '4.6')
+stderr = procutil.stderr
+stdin = procutil.stdin
+stdout = procutil.stdout
+explainexit = procutil.explainexit
+findexe = procutil.findexe
+getuser = procutil.getuser
+getpid = procutil.getpid
+hidewindow = procutil.hidewindow
+popen = procutil.popen
+quotecommand = procutil.quotecommand
+readpipe = procutil.readpipe
+setbinary = procutil.setbinary
+setsignalhandler = procutil.setsignalhandler
+shellquote = procutil.shellquote
+shellsplit = procutil.shellsplit
+spawndetached = procutil.spawndetached
+sshargs = procutil.sshargs
+testpid = procutil.testpid
+try:
+ setprocname = procutil.setprocname
+except AttributeError:
+ pass
+try:
+ unblocksignal = procutil.unblocksignal
+except AttributeError:
+ pass
+closefds = procutil.closefds
+isatty = procutil.isatty
+popen2 = procutil.popen2
+popen3 = procutil.popen3
+popen4 = procutil.popen4
+pipefilter = procutil.pipefilter
+tempfilter = procutil.tempfilter
+filter = procutil.filter
+mainfrozen = procutil.mainfrozen
+hgexecutable = procutil.hgexecutable
+isstdin = procutil.isstdin
+isstdout = procutil.isstdout
+shellenviron = procutil.shellenviron
+system = procutil.system
+gui = procutil.gui
+hgcmd = procutil.hgcmd
+rundetached = procutil.rundetached
+
escapedata = _deprecatedfunc(stringutil.escapedata, '4.6')
binary = _deprecatedfunc(stringutil.binary, '4.6')
stringmatcher = _deprecatedfunc(stringutil.stringmatcher, '4.6')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/utils/procutil.py Sat Mar 24 13:38:04 2018 +0900
@@ -0,0 +1,319 @@
+# procutil.py - utility for managing processes and executable environment
+#
+# Copyright 2005 K. Thananchayan <thananck@yahoo.com>
+# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import imp
+import io
+import os
+import signal
+import subprocess
+import sys
+import tempfile
+import time
+
+from ..i18n import _
+
+from .. import (
+ encoding,
+ error,
+ policy,
+ pycompat,
+)
+
+osutil = policy.importmod(r'osutil')
+
+stderr = pycompat.stderr
+stdin = pycompat.stdin
+stdout = pycompat.stdout
+
+def isatty(fp):
+ try:
+ return fp.isatty()
+ except AttributeError:
+ return False
+
+# glibc determines buffering on first write to stdout - if we replace a TTY
+# destined stdout with a pipe destined stdout (e.g. pager), we want line
+# buffering
+if isatty(stdout):
+ stdout = os.fdopen(stdout.fileno(), r'wb', 1)
+
+if pycompat.iswindows:
+ from .. import windows as platform
+ stdout = platform.winstdout(stdout)
+else:
+ from .. import posix as platform
+
+explainexit = platform.explainexit
+findexe = platform.findexe
+_gethgcmd = platform.gethgcmd
+getuser = platform.getuser
+getpid = os.getpid
+hidewindow = platform.hidewindow
+popen = platform.popen
+quotecommand = platform.quotecommand
+readpipe = platform.readpipe
+setbinary = platform.setbinary
+setsignalhandler = platform.setsignalhandler
+shellquote = platform.shellquote
+shellsplit = platform.shellsplit
+spawndetached = platform.spawndetached
+sshargs = platform.sshargs
+testpid = platform.testpid
+
+try:
+ setprocname = osutil.setprocname
+except AttributeError:
+ pass
+try:
+ unblocksignal = osutil.unblocksignal
+except AttributeError:
+ pass
+
+closefds = pycompat.isposix
+
+def popen2(cmd, env=None, newlines=False):
+ # Setting bufsize to -1 lets the system decide the buffer size.
+ # The default for bufsize is 0, meaning unbuffered. This leads to
+ # poor performance on Mac OS X: http://bugs.python.org/issue4194
+ p = subprocess.Popen(cmd, shell=True, bufsize=-1,
+ close_fds=closefds,
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ universal_newlines=newlines,
+ env=env)
+ return p.stdin, p.stdout
+
+def popen3(cmd, env=None, newlines=False):
+ stdin, stdout, stderr, p = popen4(cmd, env, newlines)
+ return stdin, stdout, stderr
+
+def popen4(cmd, env=None, newlines=False, bufsize=-1):
+ p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
+ close_fds=closefds,
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=newlines,
+ env=env)
+ return p.stdin, p.stdout, p.stderr, p
+
+def pipefilter(s, cmd):
+ '''filter string S through command CMD, returning its output'''
+ p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ pout, perr = p.communicate(s)
+ return pout
+
+def tempfilter(s, cmd):
+ '''filter string S through a pair of temporary files with CMD.
+ CMD is used as a template to create the real command to be run,
+ with the strings INFILE and OUTFILE replaced by the real names of
+ the temporary files generated.'''
+ inname, outname = None, None
+ try:
+ infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
+ fp = os.fdopen(infd, r'wb')
+ fp.write(s)
+ fp.close()
+ outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
+ os.close(outfd)
+ cmd = cmd.replace('INFILE', inname)
+ cmd = cmd.replace('OUTFILE', outname)
+ code = os.system(cmd)
+ if pycompat.sysplatform == 'OpenVMS' and code & 1:
+ code = 0
+ if code:
+ raise error.Abort(_("command '%s' failed: %s") %
+ (cmd, explainexit(code)))
+ with open(outname, 'rb') as fp:
+ return fp.read()
+ finally:
+ try:
+ if inname:
+ os.unlink(inname)
+ except OSError:
+ pass
+ try:
+ if outname:
+ os.unlink(outname)
+ except OSError:
+ pass
+
+_filtertable = {
+ 'tempfile:': tempfilter,
+ 'pipe:': pipefilter,
+}
+
+def filter(s, cmd):
+ "filter a string through a command that transforms its input to its output"
+ for name, fn in _filtertable.iteritems():
+ if cmd.startswith(name):
+ return fn(s, cmd[len(name):].lstrip())
+ return pipefilter(s, cmd)
+
+def mainfrozen():
+ """return True if we are a frozen executable.
+
+ The code supports py2exe (most common, Windows only) and tools/freeze
+ (portable, not much used).
+ """
+ return (pycompat.safehasattr(sys, "frozen") or # new py2exe
+ pycompat.safehasattr(sys, "importers") or # old py2exe
+ imp.is_frozen(u"__main__")) # tools/freeze
+
+_hgexecutable = None
+
+def hgexecutable():
+ """return location of the 'hg' executable.
+
+ Defaults to $HG or 'hg' in the search path.
+ """
+ if _hgexecutable is None:
+ hg = encoding.environ.get('HG')
+ mainmod = sys.modules[r'__main__']
+ if hg:
+ _sethgexecutable(hg)
+ elif mainfrozen():
+ if getattr(sys, 'frozen', None) == 'macosx_app':
+ # Env variable set by py2app
+ _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
+ else:
+ _sethgexecutable(pycompat.sysexecutable)
+ elif (os.path.basename(
+ pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
+ _sethgexecutable(pycompat.fsencode(mainmod.__file__))
+ else:
+ exe = findexe('hg') or os.path.basename(sys.argv[0])
+ _sethgexecutable(exe)
+ return _hgexecutable
+
+def _sethgexecutable(path):
+ """set location of the 'hg' executable"""
+ global _hgexecutable
+ _hgexecutable = path
+
+def _testfileno(f, stdf):
+ fileno = getattr(f, 'fileno', None)
+ try:
+ return fileno and fileno() == stdf.fileno()
+ except io.UnsupportedOperation:
+ return False # fileno() raised UnsupportedOperation
+
+def isstdin(f):
+ return _testfileno(f, sys.__stdin__)
+
+def isstdout(f):
+ return _testfileno(f, sys.__stdout__)
+
+def shellenviron(environ=None):
+ """return environ with optional override, useful for shelling out"""
+ def py2shell(val):
+ 'convert python object into string that is useful to shell'
+ if val is None or val is False:
+ return '0'
+ if val is True:
+ return '1'
+ return pycompat.bytestr(val)
+ env = dict(encoding.environ)
+ if environ:
+ env.update((k, py2shell(v)) for k, v in environ.iteritems())
+ env['HG'] = hgexecutable()
+ return env
+
+def system(cmd, environ=None, cwd=None, out=None):
+ '''enhanced shell command execution.
+ run with environment maybe modified, maybe in different dir.
+
+ if out is specified, it is assumed to be a file-like object that has a
+ write() method. stdout and stderr will be redirected to out.'''
+ try:
+ stdout.flush()
+ except Exception:
+ pass
+ cmd = quotecommand(cmd)
+ env = shellenviron(environ)
+ if out is None or isstdout(out):
+ rc = subprocess.call(cmd, shell=True, close_fds=closefds,
+ env=env, cwd=cwd)
+ else:
+ proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
+ env=env, cwd=cwd, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ for line in iter(proc.stdout.readline, ''):
+ out.write(line)
+ proc.wait()
+ rc = proc.returncode
+ if pycompat.sysplatform == 'OpenVMS' and rc & 1:
+ rc = 0
+ return rc
+
+def gui():
+ '''Are we running in a GUI?'''
+ if pycompat.isdarwin:
+ if 'SSH_CONNECTION' in encoding.environ:
+ # handle SSH access to a box where the user is logged in
+ return False
+ elif getattr(osutil, 'isgui', None):
+ # check if a CoreGraphics session is available
+ return osutil.isgui()
+ else:
+ # pure build; use a safe default
+ return True
+ else:
+ return pycompat.iswindows or encoding.environ.get("DISPLAY")
+
+def hgcmd():
+ """Return the command used to execute current hg
+
+ This is different from hgexecutable() because on Windows we want
+ to avoid things opening new shell windows like batch files, so we
+ get either the python call or current executable.
+ """
+ if mainfrozen():
+ if getattr(sys, 'frozen', None) == 'macosx_app':
+ # Env variable set by py2app
+ return [encoding.environ['EXECUTABLEPATH']]
+ else:
+ return [pycompat.sysexecutable]
+ 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
+ SIGCHLD = getattr(signal, 'SIGCHLD', None)
+ if SIGCHLD is not None:
+ prevhandler = 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)