예제 #1
0
    def __init__(self, popen, stdin, stdout, stderr):
        self._proc = popen
        self.stdin = stdin  # type: Optional[SendStream]
        self.stdout = stdout  # type: Optional[ReceiveStream]
        self.stderr = stderr  # type: Optional[ReceiveStream]

        self.stdio = None  # type: Optional[StapledStream]
        if self.stdin is not None and self.stdout is not None:
            self.stdio = StapledStream(self.stdin, self.stdout)

        self._wait_lock = Lock()

        self._pidfd = None
        if can_try_pidfd_open:
            try:
                fd = pidfd_open(self._proc.pid, 0)
            except OSError:
                # Well, we tried, but it didn't work (probably because we're
                # running on an older kernel, or in an older sandbox, that
                # hasn't been updated to support pidfd_open). We'll fall back
                # on waitid instead.
                pass
            else:
                # It worked! Wrap the raw fd up in a Python file object to
                # make sure it'll get closed.
                self._pidfd = open(fd)

        self.args = self._proc.args
        self.pid = self._proc.pid
예제 #2
0
 def has_pidfd_support():
     if not hasattr(os, 'pidfd_open'):
         return False
     try:
         os.close(os.pidfd_open(os.getpid()))
     except OSError:
         return False
     return True
예제 #3
0
 def add_child_handler(self, pid, callback, *args):
     existing = self._callbacks.get(pid)
     if existing is not None:
         self._callbacks[pid] = existing[0], callback, args
     else:
         pidfd = os.pidfd_open(pid)
         self._loop._add_reader(pidfd, self._do_wait, pid)
         self._callbacks[pid] = pidfd, callback, args
예제 #4
0
    def wait(self,
             *,
             timeout: Union[int, float, None] = None) -> Optional[int]:
        # We check is_running() up front so we don't run into PID reuse.
        # After that, we can safely just check pid_exists() or os.waitpid().
        if not self.is_running():
            with self._lock, self._exitcode_lock:
                return self._exitcode

        start_time = time.monotonic(
        ) if timeout is not None and timeout > 0 else 0

        # Assume it's a child of the current process by default
        is_child = self._pid > 0

        if timeout is None and self._pid > 0:
            # Wait with no timeout

            # We don't lock on self._lock because this is blocking
            with self._exitcode_lock:
                try:
                    wstatus = os.waitpid(self._pid, 0)[1]
                except ChildProcessError:
                    # Not a child of the current process
                    # Fall through to the polling loop
                    is_child = False
                else:
                    self._dead = True
                    self._exitcode = (-os.WTERMSIG(wstatus)
                                      if os.WIFSIGNALED(wstatus) else
                                      os.WEXITSTATUS(wstatus))

                    return self._exitcode

        elif (  # pylint: disable=chained-comparison
                timeout is not None and timeout <= 0 and self._pid > 0):
            # Zero timeout
            try:
                if self._wait_child_poll():
                    return self._exitcode
                else:
                    raise TimeoutExpired(timeout, pid=self._pid)
            except ChildProcessError:
                # We already checked is_running(), so we know it's still running
                raise TimeoutExpired(timeout, pid=self._pid)  # pylint: disable=raise-missing-from

        while True:
            if is_child:
                try:
                    if self._wait_child_poll():
                        return self._exitcode
                except ChildProcessError:
                    # On Linux 5.3+ (and Python 3.9+), pidfd_open() may avoid a busy loop
                    if hasattr(os, "pidfd_open"):
                        assert self._pid > 0
                        assert timeout is not None

                        try:
                            pidfd = os.pidfd_open(self._pid)  # pylint: disable=no-member
                        except OSError:
                            pass
                        else:
                            remaining_time = ((start_time + timeout) -
                                              time.monotonic()
                                              if timeout > 0 else 0)

                            readfds, _, _ = select.select([pidfd], [], [],
                                                          max(
                                                              remaining_time,
                                                              0))
                            os.close(pidfd)
                            if not readfds:
                                # Timeout expired, and still not dead
                                raise TimeoutExpired(  # pylint: disable=raise-missing-from
                                    timeout,
                                    pid=self._pid)

                            # Dead, but now it may be a zombie, so we need to keep watching it
                            # Fall through to the normal monitoring code

                    # Switch to pid_exists()
                    is_child = False
                    # Restart the loop so it gets checked immediately, not 0.01 seconds from now
                    continue

            else:
                if not pid_exists(self._pid):
                    with self._lock, self._exitcode_lock:
                        return self._exitcode

            interval = 0.01
            if timeout is not None:
                remaining_time = (start_time + timeout
                                  ) - time.monotonic() if timeout > 0 else 0
                if remaining_time <= 0:
                    raise TimeoutExpired(timeout, pid=self._pid)

                interval = min(interval, remaining_time)

            time.sleep(interval)
예제 #5
0
# Run jailer/firecracker and wait for the process to finish but store a reference to the child
# process.
firecracker_process = subprocess.Popen(jailer_cmd)
firecracker_process.communicate()

if args.new_pid_ns:
    # With --new-pid-ns, jailer forks and therefore does not block until firecracker terminates.
    # We need to wait for firecracker before we can exit and clean up the chroot directory.
    with Path(instance_chroot / 'firecracker.pid').open('r') as f:
        # Get the PID of the firecracker process from the file firecracker.pid in the root of the
        # chroot directory.
        firecracker_pid = int(f.read())
        try:
            # pidfd_open is superior but requires Python 3.9+ and Linux 5.3+.
            # If this fails, fall back to traditional process management.
            firecracker_pid = open(os.pidfd_open(firecracker_pid))
        except Exception:
            pass
        while True:
            try:
                # Send signal 0 (no signal), to check if the process is still alive.
                if type(firecracker_pid) == int:
                    os.kill(firecracker_pid, 0)
                else:
                    signal.pidfd_send_signal(firecracker_pid.fileno(), 0)
            except ProcessLookupError:
                # The firecracker process has exited, we can clean up now.
                if type(firecracker_pid) != int:
                    firecracker_pid.close()
                break
            time.sleep(0.25)
예제 #6
0
    def _init(self,
              command,
              *,
              stdin=None,
              stdout=None,
              stderr=None,
              **options):
        for key in ('universal_newlines', 'text', 'encoding', 'errors',
                    'bufsize'):
            if options.get(key):
                raise TypeError(
                    "trio.Process only supports communicating over "
                    "unbuffered byte streams; the '{}' option is not supported"
                    .format(key))

        self.stdin = None  # type: Optional[SendStream]
        self.stdout = None  # type: Optional[ReceiveStream]
        self.stderr = None  # type: Optional[ReceiveStream]
        self.stdio = None  # type: Optional[StapledStream]

        if os.name == "posix":
            if isinstance(command, str) and not options.get("shell"):
                raise TypeError(
                    "command must be a sequence (not a string) if shell=False "
                    "on UNIX systems")
            if not isinstance(command, str) and options.get("shell"):
                raise TypeError(
                    "command must be a string (not a sequence) if shell=True "
                    "on UNIX systems")

        self._wait_lock = Lock()

        if stdin == subprocess.PIPE:
            self.stdin, stdin = create_pipe_to_child_stdin()
        if stdout == subprocess.PIPE:
            self.stdout, stdout = create_pipe_from_child_output()
        if stderr == subprocess.STDOUT:
            # If we created a pipe for stdout, pass the same pipe for
            # stderr.  If stdout was some non-pipe thing (DEVNULL or a
            # given FD), pass the same thing. If stdout was passed as
            # None, keep stderr as STDOUT to allow subprocess to dup
            # our stdout. Regardless of which of these is applicable,
            # don't create a new Trio stream for stderr -- if stdout
            # is piped, stderr will be intermixed on the stdout stream.
            if stdout is not None:
                stderr = stdout
        elif stderr == subprocess.PIPE:
            self.stderr, stderr = create_pipe_from_child_output()

        try:
            self._proc = subprocess.Popen(command,
                                          stdin=stdin,
                                          stdout=stdout,
                                          stderr=stderr,
                                          **options)
        finally:
            # Close the parent's handle for each child side of a pipe;
            # we want the child to have the only copy, so that when
            # it exits we can read EOF on our side.
            if self.stdin is not None:
                os.close(stdin)
            if self.stdout is not None:
                os.close(stdout)
            if self.stderr is not None:
                os.close(stderr)

        self._pidfd = None
        if can_try_pidfd_open:
            try:
                fd = pidfd_open(self._proc.pid, 0)
            except OSError:
                # Well, we tried, but it didn't work (probably because we're
                # running on an older kernel, or in an older sandbox, that
                # hasn't been updated to support pidfd_open). We'll fall back
                # on waitid instead.
                pass
            else:
                # It worked! Wrap the raw fd up in a Python file object to
                # make sure it'll get closed.
                self._pidfd = open(fd)

        if self.stdin is not None and self.stdout is not None:
            self.stdio = StapledStream(self.stdin, self.stdout)

        self.args = self._proc.args
        self.pid = self._proc.pid