base_subprocess.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import collections
  2. import subprocess
  3. import sys
  4. import warnings
  5. from . import futures
  6. from . import protocols
  7. from . import transports
  8. from .coroutines import coroutine
  9. from .log import logger
  10. class BaseSubprocessTransport(transports.SubprocessTransport):
  11. def __init__(self, loop, protocol, args, shell,
  12. stdin, stdout, stderr, bufsize,
  13. waiter=None, extra=None, **kwargs):
  14. super().__init__(extra)
  15. self._closed = False
  16. self._protocol = protocol
  17. self._loop = loop
  18. self._proc = None
  19. self._pid = None
  20. self._returncode = None
  21. self._exit_waiters = []
  22. self._pending_calls = collections.deque()
  23. self._pipes = {}
  24. self._finished = False
  25. if stdin == subprocess.PIPE:
  26. self._pipes[0] = None
  27. if stdout == subprocess.PIPE:
  28. self._pipes[1] = None
  29. if stderr == subprocess.PIPE:
  30. self._pipes[2] = None
  31. # Create the child process: set the _proc attribute
  32. self._start(args=args, shell=shell, stdin=stdin, stdout=stdout,
  33. stderr=stderr, bufsize=bufsize, **kwargs)
  34. self._pid = self._proc.pid
  35. self._extra['subprocess'] = self._proc
  36. if self._loop.get_debug():
  37. if isinstance(args, (bytes, str)):
  38. program = args
  39. else:
  40. program = args[0]
  41. logger.debug('process %r created: pid %s',
  42. program, self._pid)
  43. self._loop.create_task(self._connect_pipes(waiter))
  44. def __repr__(self):
  45. info = [self.__class__.__name__]
  46. if self._closed:
  47. info.append('closed')
  48. info.append('pid=%s' % self._pid)
  49. if self._returncode is not None:
  50. info.append('returncode=%s' % self._returncode)
  51. stdin = self._pipes.get(0)
  52. if stdin is not None:
  53. info.append('stdin=%s' % stdin.pipe)
  54. stdout = self._pipes.get(1)
  55. stderr = self._pipes.get(2)
  56. if stdout is not None and stderr is stdout:
  57. info.append('stdout=stderr=%s' % stdout.pipe)
  58. else:
  59. if stdout is not None:
  60. info.append('stdout=%s' % stdout.pipe)
  61. if stderr is not None:
  62. info.append('stderr=%s' % stderr.pipe)
  63. return '<%s>' % ' '.join(info)
  64. def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
  65. raise NotImplementedError
  66. def _make_write_subprocess_pipe_proto(self, fd):
  67. raise NotImplementedError
  68. def _make_read_subprocess_pipe_proto(self, fd):
  69. raise NotImplementedError
  70. def close(self):
  71. if self._closed:
  72. return
  73. self._closed = True
  74. for proto in self._pipes.values():
  75. if proto is None:
  76. continue
  77. proto.pipe.close()
  78. if self._proc is not None and self._returncode is None:
  79. if self._loop.get_debug():
  80. logger.warning('Close running child process: kill %r', self)
  81. try:
  82. self._proc.kill()
  83. except ProcessLookupError:
  84. pass
  85. # Don't clear the _proc reference yet: _post_init() may still run
  86. # On Python 3.3 and older, objects with a destructor part of a reference
  87. # cycle are never destroyed. It's not more the case on Python 3.4 thanks
  88. # to the PEP 442.
  89. if sys.version_info >= (3, 4):
  90. def __del__(self):
  91. if not self._closed:
  92. warnings.warn("unclosed transport %r" % self, ResourceWarning)
  93. self.close()
  94. def get_pid(self):
  95. return self._pid
  96. def get_returncode(self):
  97. return self._returncode
  98. def get_pipe_transport(self, fd):
  99. if fd in self._pipes:
  100. return self._pipes[fd].pipe
  101. else:
  102. return None
  103. def _check_proc(self):
  104. if self._proc is None:
  105. raise ProcessLookupError()
  106. def send_signal(self, signal):
  107. self._check_proc()
  108. self._proc.send_signal(signal)
  109. def terminate(self):
  110. self._check_proc()
  111. self._proc.terminate()
  112. def kill(self):
  113. self._check_proc()
  114. self._proc.kill()
  115. @coroutine
  116. def _connect_pipes(self, waiter):
  117. try:
  118. proc = self._proc
  119. loop = self._loop
  120. if proc.stdin is not None:
  121. _, pipe = yield from loop.connect_write_pipe(
  122. lambda: WriteSubprocessPipeProto(self, 0),
  123. proc.stdin)
  124. self._pipes[0] = pipe
  125. if proc.stdout is not None:
  126. _, pipe = yield from loop.connect_read_pipe(
  127. lambda: ReadSubprocessPipeProto(self, 1),
  128. proc.stdout)
  129. self._pipes[1] = pipe
  130. if proc.stderr is not None:
  131. _, pipe = yield from loop.connect_read_pipe(
  132. lambda: ReadSubprocessPipeProto(self, 2),
  133. proc.stderr)
  134. self._pipes[2] = pipe
  135. assert self._pending_calls is not None
  136. loop.call_soon(self._protocol.connection_made, self)
  137. for callback, data in self._pending_calls:
  138. loop.call_soon(callback, *data)
  139. self._pending_calls = None
  140. except Exception as exc:
  141. if waiter is not None and not waiter.cancelled():
  142. waiter.set_exception(exc)
  143. else:
  144. if waiter is not None and not waiter.cancelled():
  145. waiter.set_result(None)
  146. def _call(self, cb, *data):
  147. if self._pending_calls is not None:
  148. self._pending_calls.append((cb, data))
  149. else:
  150. self._loop.call_soon(cb, *data)
  151. def _pipe_connection_lost(self, fd, exc):
  152. self._call(self._protocol.pipe_connection_lost, fd, exc)
  153. self._try_finish()
  154. def _pipe_data_received(self, fd, data):
  155. self._call(self._protocol.pipe_data_received, fd, data)
  156. def _process_exited(self, returncode):
  157. assert returncode is not None, returncode
  158. assert self._returncode is None, self._returncode
  159. if self._loop.get_debug():
  160. logger.info('%r exited with return code %r',
  161. self, returncode)
  162. self._returncode = returncode
  163. self._call(self._protocol.process_exited)
  164. self._try_finish()
  165. # wake up futures waiting for wait()
  166. for waiter in self._exit_waiters:
  167. if not waiter.cancelled():
  168. waiter.set_result(returncode)
  169. self._exit_waiters = None
  170. def _wait(self):
  171. """Wait until the process exit and return the process return code.
  172. This method is a coroutine."""
  173. if self._returncode is not None:
  174. return self._returncode
  175. waiter = futures.Future(loop=self._loop)
  176. self._exit_waiters.append(waiter)
  177. return (yield from waiter)
  178. def _try_finish(self):
  179. assert not self._finished
  180. if self._returncode is None:
  181. return
  182. if all(p is not None and p.disconnected
  183. for p in self._pipes.values()):
  184. self._finished = True
  185. self._call(self._call_connection_lost, None)
  186. def _call_connection_lost(self, exc):
  187. try:
  188. self._protocol.connection_lost(exc)
  189. finally:
  190. self._loop = None
  191. self._proc = None
  192. self._protocol = None
  193. class WriteSubprocessPipeProto(protocols.BaseProtocol):
  194. def __init__(self, proc, fd):
  195. self.proc = proc
  196. self.fd = fd
  197. self.pipe = None
  198. self.disconnected = False
  199. def connection_made(self, transport):
  200. self.pipe = transport
  201. def __repr__(self):
  202. return ('<%s fd=%s pipe=%r>'
  203. % (self.__class__.__name__, self.fd, self.pipe))
  204. def connection_lost(self, exc):
  205. self.disconnected = True
  206. self.proc._pipe_connection_lost(self.fd, exc)
  207. self.proc = None
  208. def pause_writing(self):
  209. self.proc._protocol.pause_writing()
  210. def resume_writing(self):
  211. self.proc._protocol.resume_writing()
  212. class ReadSubprocessPipeProto(WriteSubprocessPipeProto,
  213. protocols.Protocol):
  214. def data_received(self, data):
  215. self.proc._pipe_data_received(self.fd, data)