546 # Using subprocess.CREATE_NEW_CONSOLE might helps. |
546 # Using subprocess.CREATE_NEW_CONSOLE might helps. |
547 # See https://phab.mercurial-scm.org/D1701 for discussion |
547 # See https://phab.mercurial-scm.org/D1701 for discussion |
548 _creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP |
548 _creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP |
549 |
549 |
550 def runbgcommand( |
550 def runbgcommand( |
551 script, env, shell=False, stdout=None, stderr=None, ensurestart=True |
551 script, |
|
552 env, |
|
553 shell=False, |
|
554 stdout=None, |
|
555 stderr=None, |
|
556 ensurestart=True, |
|
557 record_wait=None, |
552 ): |
558 ): |
553 '''Spawn a command without waiting for it to finish.''' |
559 '''Spawn a command without waiting for it to finish.''' |
554 # we can't use close_fds *and* redirect stdin. I'm not sure that we |
560 # we can't use close_fds *and* redirect stdin. I'm not sure that we |
555 # need to because the detached process has no console connection. |
561 # need to because the detached process has no console connection. |
556 subprocess.Popen( |
562 p = subprocess.Popen( |
557 tonativestr(script), |
563 tonativestr(script), |
558 shell=shell, |
564 shell=shell, |
559 env=tonativeenv(env), |
565 env=tonativeenv(env), |
560 close_fds=True, |
566 close_fds=True, |
561 creationflags=_creationflags, |
567 creationflags=_creationflags, |
562 stdout=stdout, |
568 stdout=stdout, |
563 stderr=stderr, |
569 stderr=stderr, |
564 ) |
570 ) |
|
571 if record_wait is not None: |
|
572 record_wait(p.wait) |
565 |
573 |
566 |
574 |
567 else: |
575 else: |
568 |
576 |
569 def runbgcommand( |
577 def runbgcommand( |
570 cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True |
578 cmd, |
|
579 env, |
|
580 shell=False, |
|
581 stdout=None, |
|
582 stderr=None, |
|
583 ensurestart=True, |
|
584 record_wait=None, |
571 ): |
585 ): |
572 '''Spawn a command without waiting for it to finish.''' |
586 '''Spawn a command without waiting for it to finish. |
|
587 |
|
588 |
|
589 When `record_wait` is not None, the spawned process will not be fully |
|
590 detached and the `record_wait` argument will be called with a the |
|
591 `Subprocess.wait` function for the spawned process. This is mostly |
|
592 useful for developers that need to make sure the spawned process |
|
593 finished before a certain point. (eg: writing test)''' |
573 # double-fork to completely detach from the parent process |
594 # double-fork to completely detach from the parent process |
574 # based on http://code.activestate.com/recipes/278731 |
595 # based on http://code.activestate.com/recipes/278731 |
575 pid = os.fork() |
596 if record_wait is None: |
576 if pid: |
597 pid = os.fork() |
577 if not ensurestart: |
598 if pid: |
|
599 if not ensurestart: |
|
600 return |
|
601 # Parent process |
|
602 (_pid, status) = os.waitpid(pid, 0) |
|
603 if os.WIFEXITED(status): |
|
604 returncode = os.WEXITSTATUS(status) |
|
605 else: |
|
606 returncode = -(os.WTERMSIG(status)) |
|
607 if returncode != 0: |
|
608 # The child process's return code is 0 on success, an errno |
|
609 # value on failure, or 255 if we don't have a valid errno |
|
610 # value. |
|
611 # |
|
612 # (It would be slightly nicer to return the full exception info |
|
613 # over a pipe as the subprocess module does. For now it |
|
614 # doesn't seem worth adding that complexity here, though.) |
|
615 if returncode == 255: |
|
616 returncode = errno.EINVAL |
|
617 raise OSError( |
|
618 returncode, |
|
619 b'error running %r: %s' |
|
620 % (cmd, os.strerror(returncode)), |
|
621 ) |
578 return |
622 return |
579 # Parent process |
|
580 (_pid, status) = os.waitpid(pid, 0) |
|
581 if os.WIFEXITED(status): |
|
582 returncode = os.WEXITSTATUS(status) |
|
583 else: |
|
584 returncode = -(os.WTERMSIG(status)) |
|
585 if returncode != 0: |
|
586 # The child process's return code is 0 on success, an errno |
|
587 # value on failure, or 255 if we don't have a valid errno |
|
588 # value. |
|
589 # |
|
590 # (It would be slightly nicer to return the full exception info |
|
591 # over a pipe as the subprocess module does. For now it |
|
592 # doesn't seem worth adding that complexity here, though.) |
|
593 if returncode == 255: |
|
594 returncode = errno.EINVAL |
|
595 raise OSError( |
|
596 returncode, |
|
597 b'error running %r: %s' % (cmd, os.strerror(returncode)), |
|
598 ) |
|
599 return |
|
600 |
623 |
601 returncode = 255 |
624 returncode = 255 |
602 try: |
625 try: |
603 # Start a new session |
626 if record_wait is None: |
604 os.setsid() |
627 # Start a new session |
|
628 os.setsid() |
605 |
629 |
606 stdin = open(os.devnull, b'r') |
630 stdin = open(os.devnull, b'r') |
607 if stdout is None: |
631 if stdout is None: |
608 stdout = open(os.devnull, b'w') |
632 stdout = open(os.devnull, b'w') |
609 if stderr is None: |
633 if stderr is None: |
610 stderr = open(os.devnull, b'w') |
634 stderr = open(os.devnull, b'w') |
611 |
635 |
612 # connect stdin to devnull to make sure the subprocess can't |
636 # connect stdin to devnull to make sure the subprocess can't |
613 # muck up that stream for mercurial. |
637 # muck up that stream for mercurial. |
614 subprocess.Popen( |
638 p = subprocess.Popen( |
615 cmd, |
639 cmd, |
616 shell=shell, |
640 shell=shell, |
617 env=env, |
641 env=env, |
618 close_fds=True, |
642 close_fds=True, |
619 stdin=stdin, |
643 stdin=stdin, |
620 stdout=stdout, |
644 stdout=stdout, |
621 stderr=stderr, |
645 stderr=stderr, |
622 ) |
646 ) |
|
647 if record_wait is not None: |
|
648 record_wait(p.wait) |
623 returncode = 0 |
649 returncode = 0 |
624 except EnvironmentError as ex: |
650 except EnvironmentError as ex: |
625 returncode = ex.errno & 0xFF |
651 returncode = ex.errno & 0xFF |
626 if returncode == 0: |
652 if returncode == 0: |
627 # This shouldn't happen, but just in case make sure the |
653 # This shouldn't happen, but just in case make sure the |