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