Spaces:
Runtime error
Runtime error
| import collections | |
| import subprocess | |
| import warnings | |
| from . import protocols | |
| from . import transports | |
| from .log import logger | |
| class BaseSubprocessTransport(transports.SubprocessTransport): | |
| def __init__(self, loop, protocol, args, shell, | |
| stdin, stdout, stderr, bufsize, | |
| waiter=None, extra=None, **kwargs): | |
| super().__init__(extra) | |
| self._closed = False | |
| self._protocol = protocol | |
| self._loop = loop | |
| self._proc = None | |
| self._pid = None | |
| self._returncode = None | |
| self._exit_waiters = [] | |
| self._pending_calls = collections.deque() | |
| self._pipes = {} | |
| self._finished = False | |
| if stdin == subprocess.PIPE: | |
| self._pipes[0] = None | |
| if stdout == subprocess.PIPE: | |
| self._pipes[1] = None | |
| if stderr == subprocess.PIPE: | |
| self._pipes[2] = None | |
| # Create the child process: set the _proc attribute | |
| try: | |
| self._start(args=args, shell=shell, stdin=stdin, stdout=stdout, | |
| stderr=stderr, bufsize=bufsize, **kwargs) | |
| except: | |
| self.close() | |
| raise | |
| self._pid = self._proc.pid | |
| self._extra['subprocess'] = self._proc | |
| if self._loop.get_debug(): | |
| if isinstance(args, (bytes, str)): | |
| program = args | |
| else: | |
| program = args[0] | |
| logger.debug('process %r created: pid %s', | |
| program, self._pid) | |
| self._loop.create_task(self._connect_pipes(waiter)) | |
| def __repr__(self): | |
| info = [self.__class__.__name__] | |
| if self._closed: | |
| info.append('closed') | |
| if self._pid is not None: | |
| info.append(f'pid={self._pid}') | |
| if self._returncode is not None: | |
| info.append(f'returncode={self._returncode}') | |
| elif self._pid is not None: | |
| info.append('running') | |
| else: | |
| info.append('not started') | |
| stdin = self._pipes.get(0) | |
| if stdin is not None: | |
| info.append(f'stdin={stdin.pipe}') | |
| stdout = self._pipes.get(1) | |
| stderr = self._pipes.get(2) | |
| if stdout is not None and stderr is stdout: | |
| info.append(f'stdout=stderr={stdout.pipe}') | |
| else: | |
| if stdout is not None: | |
| info.append(f'stdout={stdout.pipe}') | |
| if stderr is not None: | |
| info.append(f'stderr={stderr.pipe}') | |
| return '<{}>'.format(' '.join(info)) | |
| def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs): | |
| raise NotImplementedError | |
| def set_protocol(self, protocol): | |
| self._protocol = protocol | |
| def get_protocol(self): | |
| return self._protocol | |
| def is_closing(self): | |
| return self._closed | |
| def close(self): | |
| if self._closed: | |
| return | |
| self._closed = True | |
| for proto in self._pipes.values(): | |
| if proto is None: | |
| continue | |
| proto.pipe.close() | |
| if (self._proc is not None and | |
| # has the child process finished? | |
| self._returncode is None and | |
| # the child process has finished, but the | |
| # transport hasn't been notified yet? | |
| self._proc.poll() is None): | |
| if self._loop.get_debug(): | |
| logger.warning('Close running child process: kill %r', self) | |
| try: | |
| self._proc.kill() | |
| except ProcessLookupError: | |
| pass | |
| # Don't clear the _proc reference yet: _post_init() may still run | |
| def __del__(self, _warn=warnings.warn): | |
| if not self._closed: | |
| _warn(f"unclosed transport {self!r}", ResourceWarning, source=self) | |
| self.close() | |
| def get_pid(self): | |
| return self._pid | |
| def get_returncode(self): | |
| return self._returncode | |
| def get_pipe_transport(self, fd): | |
| if fd in self._pipes: | |
| return self._pipes[fd].pipe | |
| else: | |
| return None | |
| def _check_proc(self): | |
| if self._proc is None: | |
| raise ProcessLookupError() | |
| def send_signal(self, signal): | |
| self._check_proc() | |
| self._proc.send_signal(signal) | |
| def terminate(self): | |
| self._check_proc() | |
| self._proc.terminate() | |
| def kill(self): | |
| self._check_proc() | |
| self._proc.kill() | |
| async def _connect_pipes(self, waiter): | |
| try: | |
| proc = self._proc | |
| loop = self._loop | |
| if proc.stdin is not None: | |
| _, pipe = await loop.connect_write_pipe( | |
| lambda: WriteSubprocessPipeProto(self, 0), | |
| proc.stdin) | |
| self._pipes[0] = pipe | |
| if proc.stdout is not None: | |
| _, pipe = await loop.connect_read_pipe( | |
| lambda: ReadSubprocessPipeProto(self, 1), | |
| proc.stdout) | |
| self._pipes[1] = pipe | |
| if proc.stderr is not None: | |
| _, pipe = await loop.connect_read_pipe( | |
| lambda: ReadSubprocessPipeProto(self, 2), | |
| proc.stderr) | |
| self._pipes[2] = pipe | |
| assert self._pending_calls is not None | |
| loop.call_soon(self._protocol.connection_made, self) | |
| for callback, data in self._pending_calls: | |
| loop.call_soon(callback, *data) | |
| self._pending_calls = None | |
| except (SystemExit, KeyboardInterrupt): | |
| raise | |
| except BaseException as exc: | |
| if waiter is not None and not waiter.cancelled(): | |
| waiter.set_exception(exc) | |
| else: | |
| if waiter is not None and not waiter.cancelled(): | |
| waiter.set_result(None) | |
| def _call(self, cb, *data): | |
| if self._pending_calls is not None: | |
| self._pending_calls.append((cb, data)) | |
| else: | |
| self._loop.call_soon(cb, *data) | |
| def _pipe_connection_lost(self, fd, exc): | |
| self._call(self._protocol.pipe_connection_lost, fd, exc) | |
| self._try_finish() | |
| def _pipe_data_received(self, fd, data): | |
| self._call(self._protocol.pipe_data_received, fd, data) | |
| def _process_exited(self, returncode): | |
| assert returncode is not None, returncode | |
| assert self._returncode is None, self._returncode | |
| if self._loop.get_debug(): | |
| logger.info('%r exited with return code %r', self, returncode) | |
| self._returncode = returncode | |
| if self._proc.returncode is None: | |
| # asyncio uses a child watcher: copy the status into the Popen | |
| # object. On Python 3.6, it is required to avoid a ResourceWarning. | |
| self._proc.returncode = returncode | |
| self._call(self._protocol.process_exited) | |
| self._try_finish() | |
| # wake up futures waiting for wait() | |
| for waiter in self._exit_waiters: | |
| if not waiter.cancelled(): | |
| waiter.set_result(returncode) | |
| self._exit_waiters = None | |
| async def _wait(self): | |
| """Wait until the process exit and return the process return code. | |
| This method is a coroutine.""" | |
| if self._returncode is not None: | |
| return self._returncode | |
| waiter = self._loop.create_future() | |
| self._exit_waiters.append(waiter) | |
| return await waiter | |
| def _try_finish(self): | |
| assert not self._finished | |
| if self._returncode is None: | |
| return | |
| if all(p is not None and p.disconnected | |
| for p in self._pipes.values()): | |
| self._finished = True | |
| self._call(self._call_connection_lost, None) | |
| def _call_connection_lost(self, exc): | |
| try: | |
| self._protocol.connection_lost(exc) | |
| finally: | |
| self._loop = None | |
| self._proc = None | |
| self._protocol = None | |
| class WriteSubprocessPipeProto(protocols.BaseProtocol): | |
| def __init__(self, proc, fd): | |
| self.proc = proc | |
| self.fd = fd | |
| self.pipe = None | |
| self.disconnected = False | |
| def connection_made(self, transport): | |
| self.pipe = transport | |
| def __repr__(self): | |
| return f'<{self.__class__.__name__} fd={self.fd} pipe={self.pipe!r}>' | |
| def connection_lost(self, exc): | |
| self.disconnected = True | |
| self.proc._pipe_connection_lost(self.fd, exc) | |
| self.proc = None | |
| def pause_writing(self): | |
| self.proc._protocol.pause_writing() | |
| def resume_writing(self): | |
| self.proc._protocol.resume_writing() | |
| class ReadSubprocessPipeProto(WriteSubprocessPipeProto, | |
| protocols.Protocol): | |
| def data_received(self, data): | |
| self.proc._pipe_data_received(self.fd, data) | |