##// END OF EJS Templates
procutil: delete Python 2 support code...
Gregory Szorc -
r49765:3681b4c5 default
parent child Browse files
Show More
@@ -1,914 +1,768 b''
1 1 # procutil.py - utility for managing processes and executable environment
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10
11 11 import contextlib
12 12 import errno
13 13 import io
14 14 import os
15 15 import signal
16 16 import subprocess
17 17 import sys
18 18 import threading
19 19 import time
20 20
21 21 from ..i18n import _
22 22 from ..pycompat import (
23 23 getattr,
24 24 open,
25 25 )
26 26
27 27 from .. import (
28 28 encoding,
29 29 error,
30 30 policy,
31 31 pycompat,
32 32 )
33 33
34 34 # Import like this to keep import-checker happy
35 35 from ..utils import resourceutil
36 36
37 37 osutil = policy.importmod('osutil')
38 38
39 39 if pycompat.iswindows:
40 40 from .. import windows as platform
41 41 else:
42 42 from .. import posix as platform
43 43
44 44
45 45 def isatty(fp):
46 46 try:
47 47 return fp.isatty()
48 48 except AttributeError:
49 49 return False
50 50
51 51
52 52 class BadFile(io.RawIOBase):
53 53 """Dummy file object to simulate closed stdio behavior"""
54 54
55 55 def readinto(self, b):
56 56 raise IOError(errno.EBADF, 'Bad file descriptor')
57 57
58 58 def write(self, b):
59 59 raise IOError(errno.EBADF, 'Bad file descriptor')
60 60
61 61
62 62 class LineBufferedWrapper(object):
63 63 def __init__(self, orig):
64 64 self.orig = orig
65 65
66 66 def __getattr__(self, attr):
67 67 return getattr(self.orig, attr)
68 68
69 69 def write(self, s):
70 70 orig = self.orig
71 71 res = orig.write(s)
72 72 if s.endswith(b'\n'):
73 73 orig.flush()
74 74 return res
75 75
76 76
77 77 # pytype: disable=attribute-error
78 78 io.BufferedIOBase.register(LineBufferedWrapper)
79 79 # pytype: enable=attribute-error
80 80
81 81
82 82 def make_line_buffered(stream):
83 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
83 if not isinstance(stream, io.BufferedIOBase):
84 84 # On Python 3, buffered streams can be expected to subclass
85 85 # BufferedIOBase. This is definitively the case for the streams
86 86 # initialized by the interpreter. For unbuffered streams, we don't need
87 87 # to emulate line buffering.
88 88 return stream
89 89 if isinstance(stream, LineBufferedWrapper):
90 90 return stream
91 91 return LineBufferedWrapper(stream)
92 92
93 93
94 94 def unwrap_line_buffered(stream):
95 95 if isinstance(stream, LineBufferedWrapper):
96 96 assert not isinstance(stream.orig, LineBufferedWrapper)
97 97 return stream.orig
98 98 return stream
99 99
100 100
101 101 class WriteAllWrapper(object):
102 102 def __init__(self, orig):
103 103 self.orig = orig
104 104
105 105 def __getattr__(self, attr):
106 106 return getattr(self.orig, attr)
107 107
108 108 def write(self, s):
109 109 write1 = self.orig.write
110 110 m = memoryview(s)
111 111 total_to_write = len(s)
112 112 total_written = 0
113 113 while total_written < total_to_write:
114 114 total_written += write1(m[total_written:])
115 115 return total_written
116 116
117 117
118 118 # pytype: disable=attribute-error
119 119 io.IOBase.register(WriteAllWrapper)
120 120 # pytype: enable=attribute-error
121 121
122 122
123 123 def _make_write_all(stream):
124 assert pycompat.ispy3
125 124 if isinstance(stream, WriteAllWrapper):
126 125 return stream
127 126 if isinstance(stream, io.BufferedIOBase):
128 127 # The io.BufferedIOBase.write() contract guarantees that all data is
129 128 # written.
130 129 return stream
131 130 # In general, the write() method of streams is free to write only part of
132 131 # the data.
133 132 return WriteAllWrapper(stream)
134 133
135 134
136 if pycompat.ispy3:
137 # Python 3 implements its own I/O streams. Unlike stdio of C library,
138 # sys.stdin/stdout/stderr may be None if underlying fd is closed.
139
140 # TODO: .buffer might not exist if std streams were replaced; we'll need
141 # a silly wrapper to make a bytes stream backed by a unicode one.
135 # Python 3 implements its own I/O streams. Unlike stdio of C library,
136 # sys.stdin/stdout/stderr may be None if underlying fd is closed.
142 137
143 if sys.stdin is None:
144 stdin = BadFile()
145 else:
146 stdin = sys.stdin.buffer
147 if sys.stdout is None:
148 stdout = BadFile()
149 else:
150 stdout = _make_write_all(sys.stdout.buffer)
151 if sys.stderr is None:
152 stderr = BadFile()
153 else:
154 stderr = _make_write_all(sys.stderr.buffer)
138 # TODO: .buffer might not exist if std streams were replaced; we'll need
139 # a silly wrapper to make a bytes stream backed by a unicode one.
155 140
156 if pycompat.iswindows:
157 # Work around Windows bugs.
158 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
159 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
160 if isatty(stdout):
161 # The standard library doesn't offer line-buffered binary streams.
162 stdout = make_line_buffered(stdout)
141 if sys.stdin is None:
142 stdin = BadFile()
143 else:
144 stdin = sys.stdin.buffer
145 if sys.stdout is None:
146 stdout = BadFile()
163 147 else:
164 # Python 2 uses the I/O streams provided by the C library.
165 stdin = sys.stdin
166 stdout = sys.stdout
167 stderr = sys.stderr
168 if pycompat.iswindows:
169 # Work around Windows bugs.
170 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
171 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
172 if isatty(stdout):
173 if pycompat.iswindows:
174 # The Windows C runtime library doesn't support line buffering.
175 stdout = make_line_buffered(stdout)
176 else:
177 # glibc determines buffering on first write to stdout - if we
178 # replace a TTY destined stdout with a pipe destined stdout (e.g.
179 # pager), we want line buffering.
180 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
148 stdout = _make_write_all(sys.stdout.buffer)
149 if sys.stderr is None:
150 stderr = BadFile()
151 else:
152 stderr = _make_write_all(sys.stderr.buffer)
181 153
154 if pycompat.iswindows:
155 # Work around Windows bugs.
156 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
157 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
158 if isatty(stdout):
159 # The standard library doesn't offer line-buffered binary streams.
160 stdout = make_line_buffered(stdout)
182 161
183 162 findexe = platform.findexe
184 163 _gethgcmd = platform.gethgcmd
185 164 getuser = platform.getuser
186 165 getpid = os.getpid
187 166 hidewindow = platform.hidewindow
188 167 readpipe = platform.readpipe
189 168 setbinary = platform.setbinary
190 169 setsignalhandler = platform.setsignalhandler
191 170 shellquote = platform.shellquote
192 171 shellsplit = platform.shellsplit
193 172 spawndetached = platform.spawndetached
194 173 sshargs = platform.sshargs
195 174 testpid = platform.testpid
196 175
197 176 try:
198 177 setprocname = osutil.setprocname
199 178 except AttributeError:
200 179 pass
201 180 try:
202 181 unblocksignal = osutil.unblocksignal
203 182 except AttributeError:
204 183 pass
205 184
206 185 closefds = pycompat.isposix
207 186
208 187
209 188 def explainexit(code):
210 189 """return a message describing a subprocess status
211 190 (codes from kill are negative - not os.system/wait encoding)"""
212 191 if code >= 0:
213 192 return _(b"exited with status %d") % code
214 193 return _(b"killed by signal %d") % -code
215 194
216 195
217 196 class _pfile(object):
218 197 """File-like wrapper for a stream opened by subprocess.Popen()"""
219 198
220 199 def __init__(self, proc, fp):
221 200 self._proc = proc
222 201 self._fp = fp
223 202
224 203 def close(self):
225 204 # unlike os.popen(), this returns an integer in subprocess coding
226 205 self._fp.close()
227 206 return self._proc.wait()
228 207
229 208 def __iter__(self):
230 209 return iter(self._fp)
231 210
232 211 def __getattr__(self, attr):
233 212 return getattr(self._fp, attr)
234 213
235 214 def __enter__(self):
236 215 return self
237 216
238 217 def __exit__(self, exc_type, exc_value, exc_tb):
239 218 self.close()
240 219
241 220
242 221 def popen(cmd, mode=b'rb', bufsize=-1):
243 222 if mode == b'rb':
244 223 return _popenreader(cmd, bufsize)
245 224 elif mode == b'wb':
246 225 return _popenwriter(cmd, bufsize)
247 226 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
248 227
249 228
250 229 def _popenreader(cmd, bufsize):
251 230 p = subprocess.Popen(
252 231 tonativestr(cmd),
253 232 shell=True,
254 233 bufsize=bufsize,
255 234 close_fds=closefds,
256 235 stdout=subprocess.PIPE,
257 236 )
258 237 return _pfile(p, p.stdout)
259 238
260 239
261 240 def _popenwriter(cmd, bufsize):
262 241 p = subprocess.Popen(
263 242 tonativestr(cmd),
264 243 shell=True,
265 244 bufsize=bufsize,
266 245 close_fds=closefds,
267 246 stdin=subprocess.PIPE,
268 247 )
269 248 return _pfile(p, p.stdin)
270 249
271 250
272 251 def popen2(cmd, env=None):
273 252 # Setting bufsize to -1 lets the system decide the buffer size.
274 253 # The default for bufsize is 0, meaning unbuffered. This leads to
275 254 # poor performance on Mac OS X: http://bugs.python.org/issue4194
276 255 p = subprocess.Popen(
277 256 tonativestr(cmd),
278 257 shell=True,
279 258 bufsize=-1,
280 259 close_fds=closefds,
281 260 stdin=subprocess.PIPE,
282 261 stdout=subprocess.PIPE,
283 262 env=tonativeenv(env),
284 263 )
285 264 return p.stdin, p.stdout
286 265
287 266
288 267 def popen3(cmd, env=None):
289 268 stdin, stdout, stderr, p = popen4(cmd, env)
290 269 return stdin, stdout, stderr
291 270
292 271
293 272 def popen4(cmd, env=None, bufsize=-1):
294 273 p = subprocess.Popen(
295 274 tonativestr(cmd),
296 275 shell=True,
297 276 bufsize=bufsize,
298 277 close_fds=closefds,
299 278 stdin=subprocess.PIPE,
300 279 stdout=subprocess.PIPE,
301 280 stderr=subprocess.PIPE,
302 281 env=tonativeenv(env),
303 282 )
304 283 return p.stdin, p.stdout, p.stderr, p
305 284
306 285
307 286 def pipefilter(s, cmd):
308 287 '''filter string S through command CMD, returning its output'''
309 288 p = subprocess.Popen(
310 289 tonativestr(cmd),
311 290 shell=True,
312 291 close_fds=closefds,
313 292 stdin=subprocess.PIPE,
314 293 stdout=subprocess.PIPE,
315 294 )
316 295 pout, perr = p.communicate(s)
317 296 return pout
318 297
319 298
320 299 def tempfilter(s, cmd):
321 300 """filter string S through a pair of temporary files with CMD.
322 301 CMD is used as a template to create the real command to be run,
323 302 with the strings INFILE and OUTFILE replaced by the real names of
324 303 the temporary files generated."""
325 304 inname, outname = None, None
326 305 try:
327 306 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
328 307 fp = os.fdopen(infd, 'wb')
329 308 fp.write(s)
330 309 fp.close()
331 310 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
332 311 os.close(outfd)
333 312 cmd = cmd.replace(b'INFILE', inname)
334 313 cmd = cmd.replace(b'OUTFILE', outname)
335 314 code = system(cmd)
336 315 if pycompat.sysplatform == b'OpenVMS' and code & 1:
337 316 code = 0
338 317 if code:
339 318 raise error.Abort(
340 319 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
341 320 )
342 321 with open(outname, b'rb') as fp:
343 322 return fp.read()
344 323 finally:
345 324 try:
346 325 if inname:
347 326 os.unlink(inname)
348 327 except OSError:
349 328 pass
350 329 try:
351 330 if outname:
352 331 os.unlink(outname)
353 332 except OSError:
354 333 pass
355 334
356 335
357 336 _filtertable = {
358 337 b'tempfile:': tempfilter,
359 338 b'pipe:': pipefilter,
360 339 }
361 340
362 341
363 342 def filter(s, cmd):
364 343 """filter a string through a command that transforms its input to its
365 344 output"""
366 345 for name, fn in pycompat.iteritems(_filtertable):
367 346 if cmd.startswith(name):
368 347 return fn(s, cmd[len(name) :].lstrip())
369 348 return pipefilter(s, cmd)
370 349
371 350
372 351 _hgexecutable = None
373 352
374 353
375 354 def hgexecutable():
376 355 """return location of the 'hg' executable.
377 356
378 357 Defaults to $HG or 'hg' in the search path.
379 358 """
380 359 if _hgexecutable is None:
381 360 hg = encoding.environ.get(b'HG')
382 361 mainmod = sys.modules['__main__']
383 362 if hg:
384 363 _sethgexecutable(hg)
385 364 elif resourceutil.mainfrozen():
386 365 if getattr(sys, 'frozen', None) == 'macosx_app':
387 366 # Env variable set by py2app
388 367 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
389 368 else:
390 369 _sethgexecutable(pycompat.sysexecutable)
391 370 elif (
392 371 not pycompat.iswindows
393 372 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
394 373 ):
395 374 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
396 375 else:
397 376 _sethgexecutable(
398 377 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
399 378 )
400 379 return _hgexecutable
401 380
402 381
403 382 def _sethgexecutable(path):
404 383 """set location of the 'hg' executable"""
405 384 global _hgexecutable
406 385 _hgexecutable = path
407 386
408 387
409 388 def _testfileno(f, stdf):
410 389 fileno = getattr(f, 'fileno', None)
411 390 try:
412 391 return fileno and fileno() == stdf.fileno()
413 392 except io.UnsupportedOperation:
414 393 return False # fileno() raised UnsupportedOperation
415 394
416 395
417 396 def isstdin(f):
418 397 return _testfileno(f, sys.__stdin__)
419 398
420 399
421 400 def isstdout(f):
422 401 return _testfileno(f, sys.__stdout__)
423 402
424 403
425 404 def protectstdio(uin, uout):
426 405 """Duplicate streams and redirect original if (uin, uout) are stdio
427 406
428 407 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
429 408 redirected to stderr so the output is still readable.
430 409
431 410 Returns (fin, fout) which point to the original (uin, uout) fds, but
432 411 may be copy of (uin, uout). The returned streams can be considered
433 412 "owned" in that print(), exec(), etc. never reach to them.
434 413 """
435 414 uout.flush()
436 415 fin, fout = uin, uout
437 416 if _testfileno(uin, stdin):
438 417 newfd = os.dup(uin.fileno())
439 418 nullfd = os.open(os.devnull, os.O_RDONLY)
440 419 os.dup2(nullfd, uin.fileno())
441 420 os.close(nullfd)
442 421 fin = os.fdopen(newfd, 'rb')
443 422 if _testfileno(uout, stdout):
444 423 newfd = os.dup(uout.fileno())
445 424 os.dup2(stderr.fileno(), uout.fileno())
446 425 fout = os.fdopen(newfd, 'wb')
447 426 return fin, fout
448 427
449 428
450 429 def restorestdio(uin, uout, fin, fout):
451 430 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
452 431 uout.flush()
453 432 for f, uif in [(fin, uin), (fout, uout)]:
454 433 if f is not uif:
455 434 os.dup2(f.fileno(), uif.fileno())
456 435 f.close()
457 436
458 437
459 438 def shellenviron(environ=None):
460 439 """return environ with optional override, useful for shelling out"""
461 440
462 441 def py2shell(val):
463 442 """convert python object into string that is useful to shell"""
464 443 if val is None or val is False:
465 444 return b'0'
466 445 if val is True:
467 446 return b'1'
468 447 return pycompat.bytestr(val)
469 448
470 449 env = dict(encoding.environ)
471 450 if environ:
472 451 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
473 452 env[b'HG'] = hgexecutable()
474 453 return env
475 454
476 455
477 456 if pycompat.iswindows:
478 457
479 458 def shelltonative(cmd, env):
480 459 return platform.shelltocmdexe( # pytype: disable=module-attr
481 460 cmd, shellenviron(env)
482 461 )
483 462
484 463 tonativestr = encoding.strfromlocal
485 464 else:
486 465
487 466 def shelltonative(cmd, env):
488 467 return cmd
489 468
490 469 tonativestr = pycompat.identity
491 470
492 471
493 472 def tonativeenv(env):
494 473 """convert the environment from bytes to strings suitable for Popen(), etc."""
495 474 return pycompat.rapply(tonativestr, env)
496 475
497 476
498 477 def system(cmd, environ=None, cwd=None, out=None):
499 478 """enhanced shell command execution.
500 479 run with environment maybe modified, maybe in different dir.
501 480
502 481 if out is specified, it is assumed to be a file-like object that has a
503 482 write() method. stdout and stderr will be redirected to out."""
504 483 try:
505 484 stdout.flush()
506 485 except Exception:
507 486 pass
508 487 env = shellenviron(environ)
509 488 if out is None or isstdout(out):
510 489 rc = subprocess.call(
511 490 tonativestr(cmd),
512 491 shell=True,
513 492 close_fds=closefds,
514 493 env=tonativeenv(env),
515 494 cwd=pycompat.rapply(tonativestr, cwd),
516 495 )
517 496 else:
518 497 proc = subprocess.Popen(
519 498 tonativestr(cmd),
520 499 shell=True,
521 500 close_fds=closefds,
522 501 env=tonativeenv(env),
523 502 cwd=pycompat.rapply(tonativestr, cwd),
524 503 stdout=subprocess.PIPE,
525 504 stderr=subprocess.STDOUT,
526 505 )
527 506 for line in iter(proc.stdout.readline, b''):
528 507 out.write(line)
529 508 proc.wait()
530 509 rc = proc.returncode
531 510 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
532 511 rc = 0
533 512 return rc
534 513
535 514
536 515 _is_gui = None
537 516
538 517
539 518 def _gui():
540 519 '''Are we running in a GUI?'''
541 520 if pycompat.isdarwin:
542 521 if b'SSH_CONNECTION' in encoding.environ:
543 522 # handle SSH access to a box where the user is logged in
544 523 return False
545 524 elif getattr(osutil, 'isgui', None):
546 525 # check if a CoreGraphics session is available
547 526 return osutil.isgui()
548 527 else:
549 528 # pure build; use a safe default
550 529 return True
551 530 else:
552 531 return (
553 532 pycompat.iswindows
554 533 or encoding.environ.get(b"DISPLAY")
555 534 or encoding.environ.get(b"WAYLAND_DISPLAY")
556 535 )
557 536
558 537
559 538 def gui():
560 539 global _is_gui
561 540 if _is_gui is None:
562 541 _is_gui = _gui()
563 542 return _is_gui
564 543
565 544
566 545 def hgcmd():
567 546 """Return the command used to execute current hg
568 547
569 548 This is different from hgexecutable() because on Windows we want
570 549 to avoid things opening new shell windows like batch files, so we
571 550 get either the python call or current executable.
572 551 """
573 552 if resourceutil.mainfrozen():
574 553 if getattr(sys, 'frozen', None) == 'macosx_app':
575 554 # Env variable set by py2app
576 555 return [encoding.environ[b'EXECUTABLEPATH']]
577 556 else:
578 557 return [pycompat.sysexecutable]
579 558 return _gethgcmd()
580 559
581 560
582 561 def rundetached(args, condfn):
583 562 """Execute the argument list in a detached process.
584 563
585 564 condfn is a callable which is called repeatedly and should return
586 565 True once the child process is known to have started successfully.
587 566 At this point, the child process PID is returned. If the child
588 567 process fails to start or finishes before condfn() evaluates to
589 568 True, return -1.
590 569 """
591 570 # Windows case is easier because the child process is either
592 571 # successfully starting and validating the condition or exiting
593 572 # on failure. We just poll on its PID. On Unix, if the child
594 573 # process fails to start, it will be left in a zombie state until
595 574 # the parent wait on it, which we cannot do since we expect a long
596 575 # running process on success. Instead we listen for SIGCHLD telling
597 576 # us our child process terminated.
598 577 terminated = set()
599 578
600 579 def handler(signum, frame):
601 580 terminated.add(os.wait())
602 581
603 582 prevhandler = None
604 583 SIGCHLD = getattr(signal, 'SIGCHLD', None)
605 584 if SIGCHLD is not None:
606 585 prevhandler = signal.signal(SIGCHLD, handler)
607 586 try:
608 587 pid = spawndetached(args)
609 588 while not condfn():
610 589 if (pid in terminated or not testpid(pid)) and not condfn():
611 590 return -1
612 591 time.sleep(0.1)
613 592 return pid
614 593 finally:
615 594 if prevhandler is not None:
616 595 signal.signal(signal.SIGCHLD, prevhandler)
617 596
618 597
619 598 @contextlib.contextmanager
620 599 def uninterruptible(warn):
621 600 """Inhibit SIGINT handling on a region of code.
622 601
623 602 Note that if this is called in a non-main thread, it turns into a no-op.
624 603
625 604 Args:
626 605 warn: A callable which takes no arguments, and returns True if the
627 606 previous signal handling should be restored.
628 607 """
629 608
630 609 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
631 610 shouldbail = []
632 611
633 612 def disabledsiginthandler(*args):
634 613 if warn():
635 614 signal.signal(signal.SIGINT, oldsiginthandler[0])
636 615 del oldsiginthandler[0]
637 616 shouldbail.append(True)
638 617
639 618 try:
640 619 try:
641 620 signal.signal(signal.SIGINT, disabledsiginthandler)
642 621 except ValueError:
643 622 # wrong thread, oh well, we tried
644 623 del oldsiginthandler[0]
645 624 yield
646 625 finally:
647 626 if oldsiginthandler:
648 627 signal.signal(signal.SIGINT, oldsiginthandler[0])
649 628 if shouldbail:
650 629 raise KeyboardInterrupt
651 630
652 631
653 632 if pycompat.iswindows:
654 633 # no fork on Windows, but we can create a detached process
655 634 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
656 635 # No stdlib constant exists for this value
657 636 DETACHED_PROCESS = 0x00000008
658 637 # Following creation flags might create a console GUI window.
659 638 # Using subprocess.CREATE_NEW_CONSOLE might helps.
660 639 # See https://phab.mercurial-scm.org/D1701 for discussion
661 640 _creationflags = (
662 641 DETACHED_PROCESS
663 642 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
664 643 )
665 644
666 645 def runbgcommand(
667 646 script,
668 647 env,
669 648 shell=False,
670 649 stdout=None,
671 650 stderr=None,
672 651 ensurestart=True,
673 652 record_wait=None,
674 653 stdin_bytes=None,
675 654 ):
676 655 '''Spawn a command without waiting for it to finish.'''
677 656 # we can't use close_fds *and* redirect stdin. I'm not sure that we
678 657 # need to because the detached process has no console connection.
679 658
680 659 try:
681 660 stdin = None
682 661 if stdin_bytes is not None:
683 662 stdin = pycompat.unnamedtempfile()
684 663 stdin.write(stdin_bytes)
685 664 stdin.flush()
686 665 stdin.seek(0)
687 666
688 667 p = subprocess.Popen(
689 668 pycompat.rapply(tonativestr, script),
690 669 shell=shell,
691 670 env=tonativeenv(env),
692 671 close_fds=True,
693 672 creationflags=_creationflags,
694 673 stdin=stdin,
695 674 stdout=stdout,
696 675 stderr=stderr,
697 676 )
698 677 if record_wait is not None:
699 678 record_wait(p.wait)
700 679 finally:
701 680 if stdin is not None:
702 681 stdin.close()
703 682
704 683
705 684 else:
706 685
707 def runbgcommandpy3(
686 def runbgcommand(
708 687 cmd,
709 688 env,
710 689 shell=False,
711 690 stdout=None,
712 691 stderr=None,
713 692 ensurestart=True,
714 693 record_wait=None,
715 694 stdin_bytes=None,
716 695 ):
717 696 """Spawn a command without waiting for it to finish.
718 697
719 698
720 699 When `record_wait` is not None, the spawned process will not be fully
721 700 detached and the `record_wait` argument will be called with a the
722 701 `Subprocess.wait` function for the spawned process. This is mostly
723 702 useful for developers that need to make sure the spawned process
724 703 finished before a certain point. (eg: writing test)"""
725 704 if pycompat.isdarwin:
726 705 # avoid crash in CoreFoundation in case another thread
727 706 # calls gui() while we're calling fork().
728 707 gui()
729 708
730 709 if shell:
731 710 script = cmd
732 711 else:
733 712 if isinstance(cmd, bytes):
734 713 cmd = [cmd]
735 714 script = b' '.join(shellquote(x) for x in cmd)
736 715 if record_wait is None:
737 716 # double-fork to completely detach from the parent process
738 717 script = b'( %s ) &' % script
739 718 start_new_session = True
740 719 else:
741 720 start_new_session = False
742 721 ensurestart = True
743 722
744 723 stdin = None
745 724
746 725 try:
747 726 if stdin_bytes is None:
748 727 stdin = subprocess.DEVNULL
749 728 else:
750 729 stdin = pycompat.unnamedtempfile()
751 730 stdin.write(stdin_bytes)
752 731 stdin.flush()
753 732 stdin.seek(0)
754 733 if stdout is None:
755 734 stdout = subprocess.DEVNULL
756 735 if stderr is None:
757 736 stderr = subprocess.DEVNULL
758 737
759 738 p = subprocess.Popen(
760 739 script,
761 740 shell=True,
762 741 env=env,
763 742 close_fds=True,
764 743 stdin=stdin,
765 744 stdout=stdout,
766 745 stderr=stderr,
767 746 start_new_session=start_new_session,
768 747 )
769 748 except Exception:
770 749 if record_wait is not None:
771 750 record_wait(255)
772 751 raise
773 752 finally:
774 753 if stdin_bytes is not None and stdin is not None:
775 754 assert not isinstance(stdin, int)
776 755 stdin.close()
777 756 if not ensurestart:
778 757 # Even though we're not waiting on the child process,
779 758 # we still must call waitpid() on it at some point so
780 759 # it's not a zombie/defunct. This is especially relevant for
781 760 # chg since the parent process won't die anytime soon.
782 761 # We use a thread to make the overhead tiny.
783 762 t = threading.Thread(target=lambda: p.wait)
784 763 t.daemon = True
785 764 t.start()
786 765 else:
787 766 returncode = p.wait
788 767 if record_wait is not None:
789 768 record_wait(returncode)
790
791 def runbgcommandpy2(
792 cmd,
793 env,
794 shell=False,
795 stdout=None,
796 stderr=None,
797 ensurestart=True,
798 record_wait=None,
799 stdin_bytes=None,
800 ):
801 """Spawn a command without waiting for it to finish.
802
803
804 When `record_wait` is not None, the spawned process will not be fully
805 detached and the `record_wait` argument will be called with a the
806 `Subprocess.wait` function for the spawned process. This is mostly
807 useful for developers that need to make sure the spawned process
808 finished before a certain point. (eg: writing test)"""
809 if pycompat.isdarwin:
810 # avoid crash in CoreFoundation in case another thread
811 # calls gui() while we're calling fork().
812 gui()
813
814 # double-fork to completely detach from the parent process
815 # based on http://code.activestate.com/recipes/278731
816 if record_wait is None:
817 pid = os.fork()
818 if pid:
819 if not ensurestart:
820 # Even though we're not waiting on the child process,
821 # we still must call waitpid() on it at some point so
822 # it's not a zombie/defunct. This is especially relevant for
823 # chg since the parent process won't die anytime soon.
824 # We use a thread to make the overhead tiny.
825 def _do_wait():
826 os.waitpid(pid, 0)
827
828 t = threading.Thread(target=_do_wait)
829 t.daemon = True
830 t.start()
831 return
832 # Parent process
833 (_pid, status) = os.waitpid(pid, 0)
834 if os.WIFEXITED(status):
835 returncode = os.WEXITSTATUS(status)
836 else:
837 returncode = -(os.WTERMSIG(status))
838 if returncode != 0:
839 # The child process's return code is 0 on success, an errno
840 # value on failure, or 255 if we don't have a valid errno
841 # value.
842 #
843 # (It would be slightly nicer to return the full exception info
844 # over a pipe as the subprocess module does. For now it
845 # doesn't seem worth adding that complexity here, though.)
846 if returncode == 255:
847 returncode = errno.EINVAL
848 raise OSError(
849 returncode,
850 b'error running %r: %s'
851 % (cmd, os.strerror(returncode)),
852 )
853 return
854
855 returncode = 255
856 stdin = None
857
858 try:
859 if record_wait is None:
860 # Start a new session
861 os.setsid()
862 # connect stdin to devnull to make sure the subprocess can't
863 # muck up that stream for mercurial.
864 if stdin_bytes is None:
865 stdin = open(os.devnull, b'r')
866 else:
867 stdin = pycompat.unnamedtempfile()
868 stdin.write(stdin_bytes)
869 stdin.flush()
870 stdin.seek(0)
871
872 if stdout is None:
873 stdout = open(os.devnull, b'w')
874 if stderr is None:
875 stderr = open(os.devnull, b'w')
876
877 p = subprocess.Popen(
878 cmd,
879 shell=shell,
880 env=env,
881 close_fds=True,
882 stdin=stdin,
883 stdout=stdout,
884 stderr=stderr,
885 )
886 if record_wait is not None:
887 record_wait(p.wait)
888 returncode = 0
889 except EnvironmentError as ex:
890 returncode = ex.errno & 0xFF
891 if returncode == 0:
892 # This shouldn't happen, but just in case make sure the
893 # return code is never 0 here.
894 returncode = 255
895 except Exception:
896 returncode = 255
897 finally:
898 # mission accomplished, this child needs to exit and not
899 # continue the hg process here.
900 if stdin is not None:
901 stdin.close()
902 if record_wait is None:
903 os._exit(returncode)
904
905 if pycompat.ispy3:
906 # This branch is more robust, because it avoids running python
907 # code (hence gc finalizers, like sshpeer.__del__, which
908 # blocks). But we can't easily do the equivalent in py2,
909 # because of the lack of start_new_session=True flag. Given
910 # that the py2 branch should die soon, the short-lived
911 # duplication seems acceptable.
912 runbgcommand = runbgcommandpy3
913 else:
914 runbgcommand = runbgcommandpy2
General Comments 0
You need to be logged in to leave comments. Login now