##// END OF EJS Templates
procutil: use unbuffered stdout on Windows...
Sune Foldager -
r38722:0b63a674 4.6.2 stable
parent child Browse files
Show More
@@ -1,407 +1,411 b''
1 # procutil.py - utility for managing processes and executable environment
1 # procutil.py - utility for managing processes and executable environment
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import contextlib
12 import contextlib
13 import imp
13 import imp
14 import io
14 import io
15 import os
15 import os
16 import signal
16 import signal
17 import subprocess
17 import subprocess
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20 import time
20 import time
21
21
22 from ..i18n import _
22 from ..i18n import _
23
23
24 from .. import (
24 from .. import (
25 encoding,
25 encoding,
26 error,
26 error,
27 policy,
27 policy,
28 pycompat,
28 pycompat,
29 )
29 )
30
30
31 osutil = policy.importmod(r'osutil')
31 osutil = policy.importmod(r'osutil')
32
32
33 stderr = pycompat.stderr
33 stderr = pycompat.stderr
34 stdin = pycompat.stdin
34 stdin = pycompat.stdin
35 stdout = pycompat.stdout
35 stdout = pycompat.stdout
36
36
37 def isatty(fp):
37 def isatty(fp):
38 try:
38 try:
39 return fp.isatty()
39 return fp.isatty()
40 except AttributeError:
40 except AttributeError:
41 return False
41 return False
42
42
43 # glibc determines buffering on first write to stdout - if we replace a TTY
43 # glibc determines buffering on first write to stdout - if we replace a TTY
44 # destined stdout with a pipe destined stdout (e.g. pager), we want line
44 # destined stdout with a pipe destined stdout (e.g. pager), we want line
45 # buffering
45 # buffering (or unbuffered, on Windows)
46 if isatty(stdout):
46 if isatty(stdout):
47 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
47 if pycompat.iswindows:
48 # Windows doesn't support line buffering
49 stdout = os.fdopen(stdout.fileno(), r'wb', 0)
50 else:
51 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
48
52
49 if pycompat.iswindows:
53 if pycompat.iswindows:
50 from .. import windows as platform
54 from .. import windows as platform
51 stdout = platform.winstdout(stdout)
55 stdout = platform.winstdout(stdout)
52 else:
56 else:
53 from .. import posix as platform
57 from .. import posix as platform
54
58
55 findexe = platform.findexe
59 findexe = platform.findexe
56 _gethgcmd = platform.gethgcmd
60 _gethgcmd = platform.gethgcmd
57 getuser = platform.getuser
61 getuser = platform.getuser
58 getpid = os.getpid
62 getpid = os.getpid
59 hidewindow = platform.hidewindow
63 hidewindow = platform.hidewindow
60 quotecommand = platform.quotecommand
64 quotecommand = platform.quotecommand
61 readpipe = platform.readpipe
65 readpipe = platform.readpipe
62 setbinary = platform.setbinary
66 setbinary = platform.setbinary
63 setsignalhandler = platform.setsignalhandler
67 setsignalhandler = platform.setsignalhandler
64 shellquote = platform.shellquote
68 shellquote = platform.shellquote
65 shellsplit = platform.shellsplit
69 shellsplit = platform.shellsplit
66 spawndetached = platform.spawndetached
70 spawndetached = platform.spawndetached
67 sshargs = platform.sshargs
71 sshargs = platform.sshargs
68 testpid = platform.testpid
72 testpid = platform.testpid
69
73
70 try:
74 try:
71 setprocname = osutil.setprocname
75 setprocname = osutil.setprocname
72 except AttributeError:
76 except AttributeError:
73 pass
77 pass
74 try:
78 try:
75 unblocksignal = osutil.unblocksignal
79 unblocksignal = osutil.unblocksignal
76 except AttributeError:
80 except AttributeError:
77 pass
81 pass
78
82
79 closefds = pycompat.isposix
83 closefds = pycompat.isposix
80
84
81 def explainexit(code):
85 def explainexit(code):
82 """return a message describing a subprocess status
86 """return a message describing a subprocess status
83 (codes from kill are negative - not os.system/wait encoding)"""
87 (codes from kill are negative - not os.system/wait encoding)"""
84 if code >= 0:
88 if code >= 0:
85 return _("exited with status %d") % code
89 return _("exited with status %d") % code
86 return _("killed by signal %d") % -code
90 return _("killed by signal %d") % -code
87
91
88 class _pfile(object):
92 class _pfile(object):
89 """File-like wrapper for a stream opened by subprocess.Popen()"""
93 """File-like wrapper for a stream opened by subprocess.Popen()"""
90
94
91 def __init__(self, proc, fp):
95 def __init__(self, proc, fp):
92 self._proc = proc
96 self._proc = proc
93 self._fp = fp
97 self._fp = fp
94
98
95 def close(self):
99 def close(self):
96 # unlike os.popen(), this returns an integer in subprocess coding
100 # unlike os.popen(), this returns an integer in subprocess coding
97 self._fp.close()
101 self._fp.close()
98 return self._proc.wait()
102 return self._proc.wait()
99
103
100 def __iter__(self):
104 def __iter__(self):
101 return iter(self._fp)
105 return iter(self._fp)
102
106
103 def __getattr__(self, attr):
107 def __getattr__(self, attr):
104 return getattr(self._fp, attr)
108 return getattr(self._fp, attr)
105
109
106 def __enter__(self):
110 def __enter__(self):
107 return self
111 return self
108
112
109 def __exit__(self, exc_type, exc_value, exc_tb):
113 def __exit__(self, exc_type, exc_value, exc_tb):
110 self.close()
114 self.close()
111
115
112 def popen(cmd, mode='rb', bufsize=-1):
116 def popen(cmd, mode='rb', bufsize=-1):
113 if mode == 'rb':
117 if mode == 'rb':
114 return _popenreader(cmd, bufsize)
118 return _popenreader(cmd, bufsize)
115 elif mode == 'wb':
119 elif mode == 'wb':
116 return _popenwriter(cmd, bufsize)
120 return _popenwriter(cmd, bufsize)
117 raise error.ProgrammingError('unsupported mode: %r' % mode)
121 raise error.ProgrammingError('unsupported mode: %r' % mode)
118
122
119 def _popenreader(cmd, bufsize):
123 def _popenreader(cmd, bufsize):
120 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
124 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
121 close_fds=closefds,
125 close_fds=closefds,
122 stdout=subprocess.PIPE)
126 stdout=subprocess.PIPE)
123 return _pfile(p, p.stdout)
127 return _pfile(p, p.stdout)
124
128
125 def _popenwriter(cmd, bufsize):
129 def _popenwriter(cmd, bufsize):
126 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
130 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
127 close_fds=closefds,
131 close_fds=closefds,
128 stdin=subprocess.PIPE)
132 stdin=subprocess.PIPE)
129 return _pfile(p, p.stdin)
133 return _pfile(p, p.stdin)
130
134
131 def popen2(cmd, env=None):
135 def popen2(cmd, env=None):
132 # Setting bufsize to -1 lets the system decide the buffer size.
136 # Setting bufsize to -1 lets the system decide the buffer size.
133 # The default for bufsize is 0, meaning unbuffered. This leads to
137 # The default for bufsize is 0, meaning unbuffered. This leads to
134 # poor performance on Mac OS X: http://bugs.python.org/issue4194
138 # poor performance on Mac OS X: http://bugs.python.org/issue4194
135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
139 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
136 close_fds=closefds,
140 close_fds=closefds,
137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
141 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
138 env=env)
142 env=env)
139 return p.stdin, p.stdout
143 return p.stdin, p.stdout
140
144
141 def popen3(cmd, env=None):
145 def popen3(cmd, env=None):
142 stdin, stdout, stderr, p = popen4(cmd, env)
146 stdin, stdout, stderr, p = popen4(cmd, env)
143 return stdin, stdout, stderr
147 return stdin, stdout, stderr
144
148
145 def popen4(cmd, env=None, bufsize=-1):
149 def popen4(cmd, env=None, bufsize=-1):
146 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
150 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
147 close_fds=closefds,
151 close_fds=closefds,
148 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
152 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
149 stderr=subprocess.PIPE,
153 stderr=subprocess.PIPE,
150 env=env)
154 env=env)
151 return p.stdin, p.stdout, p.stderr, p
155 return p.stdin, p.stdout, p.stderr, p
152
156
153 def pipefilter(s, cmd):
157 def pipefilter(s, cmd):
154 '''filter string S through command CMD, returning its output'''
158 '''filter string S through command CMD, returning its output'''
155 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
159 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
156 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
160 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
157 pout, perr = p.communicate(s)
161 pout, perr = p.communicate(s)
158 return pout
162 return pout
159
163
160 def tempfilter(s, cmd):
164 def tempfilter(s, cmd):
161 '''filter string S through a pair of temporary files with CMD.
165 '''filter string S through a pair of temporary files with CMD.
162 CMD is used as a template to create the real command to be run,
166 CMD is used as a template to create the real command to be run,
163 with the strings INFILE and OUTFILE replaced by the real names of
167 with the strings INFILE and OUTFILE replaced by the real names of
164 the temporary files generated.'''
168 the temporary files generated.'''
165 inname, outname = None, None
169 inname, outname = None, None
166 try:
170 try:
167 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
171 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
168 fp = os.fdopen(infd, r'wb')
172 fp = os.fdopen(infd, r'wb')
169 fp.write(s)
173 fp.write(s)
170 fp.close()
174 fp.close()
171 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
175 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
172 os.close(outfd)
176 os.close(outfd)
173 cmd = cmd.replace('INFILE', inname)
177 cmd = cmd.replace('INFILE', inname)
174 cmd = cmd.replace('OUTFILE', outname)
178 cmd = cmd.replace('OUTFILE', outname)
175 code = system(cmd)
179 code = system(cmd)
176 if pycompat.sysplatform == 'OpenVMS' and code & 1:
180 if pycompat.sysplatform == 'OpenVMS' and code & 1:
177 code = 0
181 code = 0
178 if code:
182 if code:
179 raise error.Abort(_("command '%s' failed: %s") %
183 raise error.Abort(_("command '%s' failed: %s") %
180 (cmd, explainexit(code)))
184 (cmd, explainexit(code)))
181 with open(outname, 'rb') as fp:
185 with open(outname, 'rb') as fp:
182 return fp.read()
186 return fp.read()
183 finally:
187 finally:
184 try:
188 try:
185 if inname:
189 if inname:
186 os.unlink(inname)
190 os.unlink(inname)
187 except OSError:
191 except OSError:
188 pass
192 pass
189 try:
193 try:
190 if outname:
194 if outname:
191 os.unlink(outname)
195 os.unlink(outname)
192 except OSError:
196 except OSError:
193 pass
197 pass
194
198
195 _filtertable = {
199 _filtertable = {
196 'tempfile:': tempfilter,
200 'tempfile:': tempfilter,
197 'pipe:': pipefilter,
201 'pipe:': pipefilter,
198 }
202 }
199
203
200 def filter(s, cmd):
204 def filter(s, cmd):
201 "filter a string through a command that transforms its input to its output"
205 "filter a string through a command that transforms its input to its output"
202 for name, fn in _filtertable.iteritems():
206 for name, fn in _filtertable.iteritems():
203 if cmd.startswith(name):
207 if cmd.startswith(name):
204 return fn(s, cmd[len(name):].lstrip())
208 return fn(s, cmd[len(name):].lstrip())
205 return pipefilter(s, cmd)
209 return pipefilter(s, cmd)
206
210
207 def mainfrozen():
211 def mainfrozen():
208 """return True if we are a frozen executable.
212 """return True if we are a frozen executable.
209
213
210 The code supports py2exe (most common, Windows only) and tools/freeze
214 The code supports py2exe (most common, Windows only) and tools/freeze
211 (portable, not much used).
215 (portable, not much used).
212 """
216 """
213 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
217 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
214 pycompat.safehasattr(sys, "importers") or # old py2exe
218 pycompat.safehasattr(sys, "importers") or # old py2exe
215 imp.is_frozen(u"__main__")) # tools/freeze
219 imp.is_frozen(u"__main__")) # tools/freeze
216
220
217 _hgexecutable = None
221 _hgexecutable = None
218
222
219 def hgexecutable():
223 def hgexecutable():
220 """return location of the 'hg' executable.
224 """return location of the 'hg' executable.
221
225
222 Defaults to $HG or 'hg' in the search path.
226 Defaults to $HG or 'hg' in the search path.
223 """
227 """
224 if _hgexecutable is None:
228 if _hgexecutable is None:
225 hg = encoding.environ.get('HG')
229 hg = encoding.environ.get('HG')
226 mainmod = sys.modules[r'__main__']
230 mainmod = sys.modules[r'__main__']
227 if hg:
231 if hg:
228 _sethgexecutable(hg)
232 _sethgexecutable(hg)
229 elif mainfrozen():
233 elif mainfrozen():
230 if getattr(sys, 'frozen', None) == 'macosx_app':
234 if getattr(sys, 'frozen', None) == 'macosx_app':
231 # Env variable set by py2app
235 # Env variable set by py2app
232 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
236 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
233 else:
237 else:
234 _sethgexecutable(pycompat.sysexecutable)
238 _sethgexecutable(pycompat.sysexecutable)
235 elif (os.path.basename(
239 elif (os.path.basename(
236 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
240 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
237 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
241 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
238 else:
242 else:
239 exe = findexe('hg') or os.path.basename(sys.argv[0])
243 exe = findexe('hg') or os.path.basename(sys.argv[0])
240 _sethgexecutable(exe)
244 _sethgexecutable(exe)
241 return _hgexecutable
245 return _hgexecutable
242
246
243 def _sethgexecutable(path):
247 def _sethgexecutable(path):
244 """set location of the 'hg' executable"""
248 """set location of the 'hg' executable"""
245 global _hgexecutable
249 global _hgexecutable
246 _hgexecutable = path
250 _hgexecutable = path
247
251
248 def _testfileno(f, stdf):
252 def _testfileno(f, stdf):
249 fileno = getattr(f, 'fileno', None)
253 fileno = getattr(f, 'fileno', None)
250 try:
254 try:
251 return fileno and fileno() == stdf.fileno()
255 return fileno and fileno() == stdf.fileno()
252 except io.UnsupportedOperation:
256 except io.UnsupportedOperation:
253 return False # fileno() raised UnsupportedOperation
257 return False # fileno() raised UnsupportedOperation
254
258
255 def isstdin(f):
259 def isstdin(f):
256 return _testfileno(f, sys.__stdin__)
260 return _testfileno(f, sys.__stdin__)
257
261
258 def isstdout(f):
262 def isstdout(f):
259 return _testfileno(f, sys.__stdout__)
263 return _testfileno(f, sys.__stdout__)
260
264
261 def protectstdio(uin, uout):
265 def protectstdio(uin, uout):
262 """Duplicate streams and redirect original if (uin, uout) are stdio
266 """Duplicate streams and redirect original if (uin, uout) are stdio
263
267
264 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
268 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
265 redirected to stderr so the output is still readable.
269 redirected to stderr so the output is still readable.
266
270
267 Returns (fin, fout) which point to the original (uin, uout) fds, but
271 Returns (fin, fout) which point to the original (uin, uout) fds, but
268 may be copy of (uin, uout). The returned streams can be considered
272 may be copy of (uin, uout). The returned streams can be considered
269 "owned" in that print(), exec(), etc. never reach to them.
273 "owned" in that print(), exec(), etc. never reach to them.
270 """
274 """
271 uout.flush()
275 uout.flush()
272 fin, fout = uin, uout
276 fin, fout = uin, uout
273 if uin is stdin:
277 if uin is stdin:
274 newfd = os.dup(uin.fileno())
278 newfd = os.dup(uin.fileno())
275 nullfd = os.open(os.devnull, os.O_RDONLY)
279 nullfd = os.open(os.devnull, os.O_RDONLY)
276 os.dup2(nullfd, uin.fileno())
280 os.dup2(nullfd, uin.fileno())
277 os.close(nullfd)
281 os.close(nullfd)
278 fin = os.fdopen(newfd, r'rb')
282 fin = os.fdopen(newfd, r'rb')
279 if uout is stdout:
283 if uout is stdout:
280 newfd = os.dup(uout.fileno())
284 newfd = os.dup(uout.fileno())
281 os.dup2(stderr.fileno(), uout.fileno())
285 os.dup2(stderr.fileno(), uout.fileno())
282 fout = os.fdopen(newfd, r'wb')
286 fout = os.fdopen(newfd, r'wb')
283 return fin, fout
287 return fin, fout
284
288
285 def restorestdio(uin, uout, fin, fout):
289 def restorestdio(uin, uout, fin, fout):
286 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
290 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
287 uout.flush()
291 uout.flush()
288 for f, uif in [(fin, uin), (fout, uout)]:
292 for f, uif in [(fin, uin), (fout, uout)]:
289 if f is not uif:
293 if f is not uif:
290 os.dup2(f.fileno(), uif.fileno())
294 os.dup2(f.fileno(), uif.fileno())
291 f.close()
295 f.close()
292
296
293 @contextlib.contextmanager
297 @contextlib.contextmanager
294 def protectedstdio(uin, uout):
298 def protectedstdio(uin, uout):
295 """Run code block with protected standard streams"""
299 """Run code block with protected standard streams"""
296 fin, fout = protectstdio(uin, uout)
300 fin, fout = protectstdio(uin, uout)
297 try:
301 try:
298 yield fin, fout
302 yield fin, fout
299 finally:
303 finally:
300 restorestdio(uin, uout, fin, fout)
304 restorestdio(uin, uout, fin, fout)
301
305
302 def shellenviron(environ=None):
306 def shellenviron(environ=None):
303 """return environ with optional override, useful for shelling out"""
307 """return environ with optional override, useful for shelling out"""
304 def py2shell(val):
308 def py2shell(val):
305 'convert python object into string that is useful to shell'
309 'convert python object into string that is useful to shell'
306 if val is None or val is False:
310 if val is None or val is False:
307 return '0'
311 return '0'
308 if val is True:
312 if val is True:
309 return '1'
313 return '1'
310 return pycompat.bytestr(val)
314 return pycompat.bytestr(val)
311 env = dict(encoding.environ)
315 env = dict(encoding.environ)
312 if environ:
316 if environ:
313 env.update((k, py2shell(v)) for k, v in environ.iteritems())
317 env.update((k, py2shell(v)) for k, v in environ.iteritems())
314 env['HG'] = hgexecutable()
318 env['HG'] = hgexecutable()
315 return env
319 return env
316
320
317 def system(cmd, environ=None, cwd=None, out=None):
321 def system(cmd, environ=None, cwd=None, out=None):
318 '''enhanced shell command execution.
322 '''enhanced shell command execution.
319 run with environment maybe modified, maybe in different dir.
323 run with environment maybe modified, maybe in different dir.
320
324
321 if out is specified, it is assumed to be a file-like object that has a
325 if out is specified, it is assumed to be a file-like object that has a
322 write() method. stdout and stderr will be redirected to out.'''
326 write() method. stdout and stderr will be redirected to out.'''
323 try:
327 try:
324 stdout.flush()
328 stdout.flush()
325 except Exception:
329 except Exception:
326 pass
330 pass
327 cmd = quotecommand(cmd)
331 cmd = quotecommand(cmd)
328 env = shellenviron(environ)
332 env = shellenviron(environ)
329 if out is None or isstdout(out):
333 if out is None or isstdout(out):
330 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
334 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
331 env=env, cwd=cwd)
335 env=env, cwd=cwd)
332 else:
336 else:
333 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
337 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
334 env=env, cwd=cwd, stdout=subprocess.PIPE,
338 env=env, cwd=cwd, stdout=subprocess.PIPE,
335 stderr=subprocess.STDOUT)
339 stderr=subprocess.STDOUT)
336 for line in iter(proc.stdout.readline, ''):
340 for line in iter(proc.stdout.readline, ''):
337 out.write(line)
341 out.write(line)
338 proc.wait()
342 proc.wait()
339 rc = proc.returncode
343 rc = proc.returncode
340 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
344 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
341 rc = 0
345 rc = 0
342 return rc
346 return rc
343
347
344 def gui():
348 def gui():
345 '''Are we running in a GUI?'''
349 '''Are we running in a GUI?'''
346 if pycompat.isdarwin:
350 if pycompat.isdarwin:
347 if 'SSH_CONNECTION' in encoding.environ:
351 if 'SSH_CONNECTION' in encoding.environ:
348 # handle SSH access to a box where the user is logged in
352 # handle SSH access to a box where the user is logged in
349 return False
353 return False
350 elif getattr(osutil, 'isgui', None):
354 elif getattr(osutil, 'isgui', None):
351 # check if a CoreGraphics session is available
355 # check if a CoreGraphics session is available
352 return osutil.isgui()
356 return osutil.isgui()
353 else:
357 else:
354 # pure build; use a safe default
358 # pure build; use a safe default
355 return True
359 return True
356 else:
360 else:
357 return pycompat.iswindows or encoding.environ.get("DISPLAY")
361 return pycompat.iswindows or encoding.environ.get("DISPLAY")
358
362
359 def hgcmd():
363 def hgcmd():
360 """Return the command used to execute current hg
364 """Return the command used to execute current hg
361
365
362 This is different from hgexecutable() because on Windows we want
366 This is different from hgexecutable() because on Windows we want
363 to avoid things opening new shell windows like batch files, so we
367 to avoid things opening new shell windows like batch files, so we
364 get either the python call or current executable.
368 get either the python call or current executable.
365 """
369 """
366 if mainfrozen():
370 if mainfrozen():
367 if getattr(sys, 'frozen', None) == 'macosx_app':
371 if getattr(sys, 'frozen', None) == 'macosx_app':
368 # Env variable set by py2app
372 # Env variable set by py2app
369 return [encoding.environ['EXECUTABLEPATH']]
373 return [encoding.environ['EXECUTABLEPATH']]
370 else:
374 else:
371 return [pycompat.sysexecutable]
375 return [pycompat.sysexecutable]
372 return _gethgcmd()
376 return _gethgcmd()
373
377
374 def rundetached(args, condfn):
378 def rundetached(args, condfn):
375 """Execute the argument list in a detached process.
379 """Execute the argument list in a detached process.
376
380
377 condfn is a callable which is called repeatedly and should return
381 condfn is a callable which is called repeatedly and should return
378 True once the child process is known to have started successfully.
382 True once the child process is known to have started successfully.
379 At this point, the child process PID is returned. If the child
383 At this point, the child process PID is returned. If the child
380 process fails to start or finishes before condfn() evaluates to
384 process fails to start or finishes before condfn() evaluates to
381 True, return -1.
385 True, return -1.
382 """
386 """
383 # Windows case is easier because the child process is either
387 # Windows case is easier because the child process is either
384 # successfully starting and validating the condition or exiting
388 # successfully starting and validating the condition or exiting
385 # on failure. We just poll on its PID. On Unix, if the child
389 # on failure. We just poll on its PID. On Unix, if the child
386 # process fails to start, it will be left in a zombie state until
390 # process fails to start, it will be left in a zombie state until
387 # the parent wait on it, which we cannot do since we expect a long
391 # the parent wait on it, which we cannot do since we expect a long
388 # running process on success. Instead we listen for SIGCHLD telling
392 # running process on success. Instead we listen for SIGCHLD telling
389 # us our child process terminated.
393 # us our child process terminated.
390 terminated = set()
394 terminated = set()
391 def handler(signum, frame):
395 def handler(signum, frame):
392 terminated.add(os.wait())
396 terminated.add(os.wait())
393 prevhandler = None
397 prevhandler = None
394 SIGCHLD = getattr(signal, 'SIGCHLD', None)
398 SIGCHLD = getattr(signal, 'SIGCHLD', None)
395 if SIGCHLD is not None:
399 if SIGCHLD is not None:
396 prevhandler = signal.signal(SIGCHLD, handler)
400 prevhandler = signal.signal(SIGCHLD, handler)
397 try:
401 try:
398 pid = spawndetached(args)
402 pid = spawndetached(args)
399 while not condfn():
403 while not condfn():
400 if ((pid in terminated or not testpid(pid))
404 if ((pid in terminated or not testpid(pid))
401 and not condfn()):
405 and not condfn()):
402 return -1
406 return -1
403 time.sleep(0.1)
407 time.sleep(0.1)
404 return pid
408 return pid
405 finally:
409 finally:
406 if prevhandler is not None:
410 if prevhandler is not None:
407 signal.signal(signal.SIGCHLD, prevhandler)
411 signal.signal(signal.SIGCHLD, prevhandler)
General Comments 0
You need to be logged in to leave comments. Login now