7 from . import protocols
8 from . import transports
9 from .coroutines import coroutine, From, Return
10 from .log import logger
11 from .py33_exceptions import ProcessLookupError
14 class BaseSubprocessTransport(transports.SubprocessTransport):
16 def __init__(self, loop, protocol, args, shell,
17 stdin, stdout, stderr, bufsize,
18 waiter=None, extra=None, **kwargs):
19 super(BaseSubprocessTransport, self).__init__(extra)
21 self._protocol = protocol
25 self._returncode = None
26 self._exit_waiters = []
27 self._pending_calls = collections.deque()
29 self._finished = False
31 if stdin == subprocess.PIPE:
33 if stdout == subprocess.PIPE:
35 if stderr == subprocess.PIPE:
38 # Create the child process: set the _proc attribute
40 self._start(args=args, shell=shell, stdin=stdin, stdout=stdout,
41 stderr=stderr, bufsize=bufsize, **kwargs)
46 self._pid = self._proc.pid
47 self._extra['subprocess'] = self._proc
49 if self._loop.get_debug():
50 if isinstance(args, (bytes, str)):
54 logger.debug('process %r created: pid %s',
57 self._loop.create_task(self._connect_pipes(waiter))
60 info = [self.__class__.__name__]
63 if self._pid is not None:
64 info.append('pid=%s' % self._pid)
65 if self._returncode is not None:
66 info.append('returncode=%s' % self._returncode)
67 elif self._pid is not None:
68 info.append('running')
70 info.append('not started')
72 stdin = self._pipes.get(0)
74 info.append('stdin=%s' % stdin.pipe)
76 stdout = self._pipes.get(1)
77 stderr = self._pipes.get(2)
78 if stdout is not None and stderr is stdout:
79 info.append('stdout=stderr=%s' % stdout.pipe)
81 if stdout is not None:
82 info.append('stdout=%s' % stdout.pipe)
83 if stderr is not None:
84 info.append('stderr=%s' % stderr.pipe)
86 return '<%s>' % ' '.join(info)
88 def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
89 raise NotImplementedError
96 for proto in self._pipes.values():
101 if (self._proc is not None
102 # the child process finished?
103 and self._returncode is None
104 # the child process finished but the transport was not notified yet?
105 and self._proc.poll() is None
107 if self._loop.get_debug():
108 logger.warning('Close running child process: kill %r', self)
112 except ProcessLookupError:
115 # Don't clear the _proc reference yet: _post_init() may still run
117 # On Python 3.3 and older, objects with a destructor part of a reference
118 # cycle are never destroyed. It's not more the case on Python 3.4 thanks
123 warnings.warn("unclosed transport %r" % self, ResourceWarning)
129 def get_returncode(self):
130 return self._returncode
132 def get_pipe_transport(self, fd):
133 if fd in self._pipes:
134 return self._pipes[fd].pipe
138 def _check_proc(self):
139 if self._proc is None:
140 raise ProcessLookupError()
142 def send_signal(self, signal):
144 self._proc.send_signal(signal)
148 self._proc.terminate()
155 def _connect_pipes(self, waiter):
160 if proc.stdin is not None:
161 _, pipe = yield From(loop.connect_write_pipe(
162 lambda: WriteSubprocessPipeProto(self, 0),
164 self._pipes[0] = pipe
166 if proc.stdout is not None:
167 _, pipe = yield From(loop.connect_read_pipe(
168 lambda: ReadSubprocessPipeProto(self, 1),
170 self._pipes[1] = pipe
172 if proc.stderr is not None:
173 _, pipe = yield From(loop.connect_read_pipe(
174 lambda: ReadSubprocessPipeProto(self, 2),
176 self._pipes[2] = pipe
178 assert self._pending_calls is not None
180 loop.call_soon(self._protocol.connection_made, self)
181 for callback, data in self._pending_calls:
182 loop.call_soon(callback, *data)
183 self._pending_calls = None
184 except Exception as exc:
185 if waiter is not None and not waiter.cancelled():
186 waiter.set_exception(exc)
188 if waiter is not None and not waiter.cancelled():
189 waiter.set_result(None)
191 def _call(self, cb, *data):
192 if self._pending_calls is not None:
193 self._pending_calls.append((cb, data))
195 self._loop.call_soon(cb, *data)
197 def _pipe_connection_lost(self, fd, exc):
198 self._call(self._protocol.pipe_connection_lost, fd, exc)
201 def _pipe_data_received(self, fd, data):
202 self._call(self._protocol.pipe_data_received, fd, data)
204 def _process_exited(self, returncode):
205 assert returncode is not None, returncode
206 assert self._returncode is None, self._returncode
207 if self._loop.get_debug():
208 logger.info('%r exited with return code %r',
210 self._returncode = returncode
211 self._call(self._protocol.process_exited)
214 # wake up futures waiting for wait()
215 for waiter in self._exit_waiters:
216 if not waiter.cancelled():
217 waiter.set_result(returncode)
218 self._exit_waiters = None
222 """Wait until the process exit and return the process return code.
224 This method is a coroutine."""
225 if self._returncode is not None:
226 raise Return(self._returncode)
228 waiter = futures.Future(loop=self._loop)
229 self._exit_waiters.append(waiter)
230 returncode = yield From(waiter)
231 raise Return(returncode)
233 def _try_finish(self):
234 assert not self._finished
235 if self._returncode is None:
237 if all(p is not None and p.disconnected
238 for p in self._pipes.values()):
239 self._finished = True
240 self._call(self._call_connection_lost, None)
242 def _call_connection_lost(self, exc):
244 self._protocol.connection_lost(exc)
248 self._protocol = None
251 class WriteSubprocessPipeProto(protocols.BaseProtocol):
253 def __init__(self, proc, fd):
257 self.disconnected = False
259 def connection_made(self, transport):
260 self.pipe = transport
263 return ('<%s fd=%s pipe=%r>'
264 % (self.__class__.__name__, self.fd, self.pipe))
266 def connection_lost(self, exc):
267 self.disconnected = True
268 self.proc._pipe_connection_lost(self.fd, exc)
271 def pause_writing(self):
272 self.proc._protocol.pause_writing()
274 def resume_writing(self):
275 self.proc._protocol.resume_writing()
278 class ReadSubprocessPipeProto(WriteSubprocessPipeProto,
281 def data_received(self, data):
282 self.proc._pipe_data_received(self.fd, data)