xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/asyncio/base_subprocess.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Workerimport collections
2*cda5da8dSAndroid Build Coastguard Workerimport subprocess
3*cda5da8dSAndroid Build Coastguard Workerimport warnings
4*cda5da8dSAndroid Build Coastguard Worker
5*cda5da8dSAndroid Build Coastguard Workerfrom . import protocols
6*cda5da8dSAndroid Build Coastguard Workerfrom . import transports
7*cda5da8dSAndroid Build Coastguard Workerfrom .log import logger
8*cda5da8dSAndroid Build Coastguard Worker
9*cda5da8dSAndroid Build Coastguard Worker
10*cda5da8dSAndroid Build Coastguard Workerclass BaseSubprocessTransport(transports.SubprocessTransport):
11*cda5da8dSAndroid Build Coastguard Worker
12*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, loop, protocol, args, shell,
13*cda5da8dSAndroid Build Coastguard Worker                 stdin, stdout, stderr, bufsize,
14*cda5da8dSAndroid Build Coastguard Worker                 waiter=None, extra=None, **kwargs):
15*cda5da8dSAndroid Build Coastguard Worker        super().__init__(extra)
16*cda5da8dSAndroid Build Coastguard Worker        self._closed = False
17*cda5da8dSAndroid Build Coastguard Worker        self._protocol = protocol
18*cda5da8dSAndroid Build Coastguard Worker        self._loop = loop
19*cda5da8dSAndroid Build Coastguard Worker        self._proc = None
20*cda5da8dSAndroid Build Coastguard Worker        self._pid = None
21*cda5da8dSAndroid Build Coastguard Worker        self._returncode = None
22*cda5da8dSAndroid Build Coastguard Worker        self._exit_waiters = []
23*cda5da8dSAndroid Build Coastguard Worker        self._pending_calls = collections.deque()
24*cda5da8dSAndroid Build Coastguard Worker        self._pipes = {}
25*cda5da8dSAndroid Build Coastguard Worker        self._finished = False
26*cda5da8dSAndroid Build Coastguard Worker
27*cda5da8dSAndroid Build Coastguard Worker        if stdin == subprocess.PIPE:
28*cda5da8dSAndroid Build Coastguard Worker            self._pipes[0] = None
29*cda5da8dSAndroid Build Coastguard Worker        if stdout == subprocess.PIPE:
30*cda5da8dSAndroid Build Coastguard Worker            self._pipes[1] = None
31*cda5da8dSAndroid Build Coastguard Worker        if stderr == subprocess.PIPE:
32*cda5da8dSAndroid Build Coastguard Worker            self._pipes[2] = None
33*cda5da8dSAndroid Build Coastguard Worker
34*cda5da8dSAndroid Build Coastguard Worker        # Create the child process: set the _proc attribute
35*cda5da8dSAndroid Build Coastguard Worker        try:
36*cda5da8dSAndroid Build Coastguard Worker            self._start(args=args, shell=shell, stdin=stdin, stdout=stdout,
37*cda5da8dSAndroid Build Coastguard Worker                        stderr=stderr, bufsize=bufsize, **kwargs)
38*cda5da8dSAndroid Build Coastguard Worker        except:
39*cda5da8dSAndroid Build Coastguard Worker            self.close()
40*cda5da8dSAndroid Build Coastguard Worker            raise
41*cda5da8dSAndroid Build Coastguard Worker
42*cda5da8dSAndroid Build Coastguard Worker        self._pid = self._proc.pid
43*cda5da8dSAndroid Build Coastguard Worker        self._extra['subprocess'] = self._proc
44*cda5da8dSAndroid Build Coastguard Worker
45*cda5da8dSAndroid Build Coastguard Worker        if self._loop.get_debug():
46*cda5da8dSAndroid Build Coastguard Worker            if isinstance(args, (bytes, str)):
47*cda5da8dSAndroid Build Coastguard Worker                program = args
48*cda5da8dSAndroid Build Coastguard Worker            else:
49*cda5da8dSAndroid Build Coastguard Worker                program = args[0]
50*cda5da8dSAndroid Build Coastguard Worker            logger.debug('process %r created: pid %s',
51*cda5da8dSAndroid Build Coastguard Worker                         program, self._pid)
52*cda5da8dSAndroid Build Coastguard Worker
53*cda5da8dSAndroid Build Coastguard Worker        self._loop.create_task(self._connect_pipes(waiter))
54*cda5da8dSAndroid Build Coastguard Worker
55*cda5da8dSAndroid Build Coastguard Worker    def __repr__(self):
56*cda5da8dSAndroid Build Coastguard Worker        info = [self.__class__.__name__]
57*cda5da8dSAndroid Build Coastguard Worker        if self._closed:
58*cda5da8dSAndroid Build Coastguard Worker            info.append('closed')
59*cda5da8dSAndroid Build Coastguard Worker        if self._pid is not None:
60*cda5da8dSAndroid Build Coastguard Worker            info.append(f'pid={self._pid}')
61*cda5da8dSAndroid Build Coastguard Worker        if self._returncode is not None:
62*cda5da8dSAndroid Build Coastguard Worker            info.append(f'returncode={self._returncode}')
63*cda5da8dSAndroid Build Coastguard Worker        elif self._pid is not None:
64*cda5da8dSAndroid Build Coastguard Worker            info.append('running')
65*cda5da8dSAndroid Build Coastguard Worker        else:
66*cda5da8dSAndroid Build Coastguard Worker            info.append('not started')
67*cda5da8dSAndroid Build Coastguard Worker
68*cda5da8dSAndroid Build Coastguard Worker        stdin = self._pipes.get(0)
69*cda5da8dSAndroid Build Coastguard Worker        if stdin is not None:
70*cda5da8dSAndroid Build Coastguard Worker            info.append(f'stdin={stdin.pipe}')
71*cda5da8dSAndroid Build Coastguard Worker
72*cda5da8dSAndroid Build Coastguard Worker        stdout = self._pipes.get(1)
73*cda5da8dSAndroid Build Coastguard Worker        stderr = self._pipes.get(2)
74*cda5da8dSAndroid Build Coastguard Worker        if stdout is not None and stderr is stdout:
75*cda5da8dSAndroid Build Coastguard Worker            info.append(f'stdout=stderr={stdout.pipe}')
76*cda5da8dSAndroid Build Coastguard Worker        else:
77*cda5da8dSAndroid Build Coastguard Worker            if stdout is not None:
78*cda5da8dSAndroid Build Coastguard Worker                info.append(f'stdout={stdout.pipe}')
79*cda5da8dSAndroid Build Coastguard Worker            if stderr is not None:
80*cda5da8dSAndroid Build Coastguard Worker                info.append(f'stderr={stderr.pipe}')
81*cda5da8dSAndroid Build Coastguard Worker
82*cda5da8dSAndroid Build Coastguard Worker        return '<{}>'.format(' '.join(info))
83*cda5da8dSAndroid Build Coastguard Worker
84*cda5da8dSAndroid Build Coastguard Worker    def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
85*cda5da8dSAndroid Build Coastguard Worker        raise NotImplementedError
86*cda5da8dSAndroid Build Coastguard Worker
87*cda5da8dSAndroid Build Coastguard Worker    def set_protocol(self, protocol):
88*cda5da8dSAndroid Build Coastguard Worker        self._protocol = protocol
89*cda5da8dSAndroid Build Coastguard Worker
90*cda5da8dSAndroid Build Coastguard Worker    def get_protocol(self):
91*cda5da8dSAndroid Build Coastguard Worker        return self._protocol
92*cda5da8dSAndroid Build Coastguard Worker
93*cda5da8dSAndroid Build Coastguard Worker    def is_closing(self):
94*cda5da8dSAndroid Build Coastguard Worker        return self._closed
95*cda5da8dSAndroid Build Coastguard Worker
96*cda5da8dSAndroid Build Coastguard Worker    def close(self):
97*cda5da8dSAndroid Build Coastguard Worker        if self._closed:
98*cda5da8dSAndroid Build Coastguard Worker            return
99*cda5da8dSAndroid Build Coastguard Worker        self._closed = True
100*cda5da8dSAndroid Build Coastguard Worker
101*cda5da8dSAndroid Build Coastguard Worker        for proto in self._pipes.values():
102*cda5da8dSAndroid Build Coastguard Worker            if proto is None:
103*cda5da8dSAndroid Build Coastguard Worker                continue
104*cda5da8dSAndroid Build Coastguard Worker            proto.pipe.close()
105*cda5da8dSAndroid Build Coastguard Worker
106*cda5da8dSAndroid Build Coastguard Worker        if (self._proc is not None and
107*cda5da8dSAndroid Build Coastguard Worker                # has the child process finished?
108*cda5da8dSAndroid Build Coastguard Worker                self._returncode is None and
109*cda5da8dSAndroid Build Coastguard Worker                # the child process has finished, but the
110*cda5da8dSAndroid Build Coastguard Worker                # transport hasn't been notified yet?
111*cda5da8dSAndroid Build Coastguard Worker                self._proc.poll() is None):
112*cda5da8dSAndroid Build Coastguard Worker
113*cda5da8dSAndroid Build Coastguard Worker            if self._loop.get_debug():
114*cda5da8dSAndroid Build Coastguard Worker                logger.warning('Close running child process: kill %r', self)
115*cda5da8dSAndroid Build Coastguard Worker
116*cda5da8dSAndroid Build Coastguard Worker            try:
117*cda5da8dSAndroid Build Coastguard Worker                self._proc.kill()
118*cda5da8dSAndroid Build Coastguard Worker            except ProcessLookupError:
119*cda5da8dSAndroid Build Coastguard Worker                pass
120*cda5da8dSAndroid Build Coastguard Worker
121*cda5da8dSAndroid Build Coastguard Worker            # Don't clear the _proc reference yet: _post_init() may still run
122*cda5da8dSAndroid Build Coastguard Worker
123*cda5da8dSAndroid Build Coastguard Worker    def __del__(self, _warn=warnings.warn):
124*cda5da8dSAndroid Build Coastguard Worker        if not self._closed:
125*cda5da8dSAndroid Build Coastguard Worker            _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
126*cda5da8dSAndroid Build Coastguard Worker            self.close()
127*cda5da8dSAndroid Build Coastguard Worker
128*cda5da8dSAndroid Build Coastguard Worker    def get_pid(self):
129*cda5da8dSAndroid Build Coastguard Worker        return self._pid
130*cda5da8dSAndroid Build Coastguard Worker
131*cda5da8dSAndroid Build Coastguard Worker    def get_returncode(self):
132*cda5da8dSAndroid Build Coastguard Worker        return self._returncode
133*cda5da8dSAndroid Build Coastguard Worker
134*cda5da8dSAndroid Build Coastguard Worker    def get_pipe_transport(self, fd):
135*cda5da8dSAndroid Build Coastguard Worker        if fd in self._pipes:
136*cda5da8dSAndroid Build Coastguard Worker            return self._pipes[fd].pipe
137*cda5da8dSAndroid Build Coastguard Worker        else:
138*cda5da8dSAndroid Build Coastguard Worker            return None
139*cda5da8dSAndroid Build Coastguard Worker
140*cda5da8dSAndroid Build Coastguard Worker    def _check_proc(self):
141*cda5da8dSAndroid Build Coastguard Worker        if self._proc is None:
142*cda5da8dSAndroid Build Coastguard Worker            raise ProcessLookupError()
143*cda5da8dSAndroid Build Coastguard Worker
144*cda5da8dSAndroid Build Coastguard Worker    def send_signal(self, signal):
145*cda5da8dSAndroid Build Coastguard Worker        self._check_proc()
146*cda5da8dSAndroid Build Coastguard Worker        self._proc.send_signal(signal)
147*cda5da8dSAndroid Build Coastguard Worker
148*cda5da8dSAndroid Build Coastguard Worker    def terminate(self):
149*cda5da8dSAndroid Build Coastguard Worker        self._check_proc()
150*cda5da8dSAndroid Build Coastguard Worker        self._proc.terminate()
151*cda5da8dSAndroid Build Coastguard Worker
152*cda5da8dSAndroid Build Coastguard Worker    def kill(self):
153*cda5da8dSAndroid Build Coastguard Worker        self._check_proc()
154*cda5da8dSAndroid Build Coastguard Worker        self._proc.kill()
155*cda5da8dSAndroid Build Coastguard Worker
156*cda5da8dSAndroid Build Coastguard Worker    async def _connect_pipes(self, waiter):
157*cda5da8dSAndroid Build Coastguard Worker        try:
158*cda5da8dSAndroid Build Coastguard Worker            proc = self._proc
159*cda5da8dSAndroid Build Coastguard Worker            loop = self._loop
160*cda5da8dSAndroid Build Coastguard Worker
161*cda5da8dSAndroid Build Coastguard Worker            if proc.stdin is not None:
162*cda5da8dSAndroid Build Coastguard Worker                _, pipe = await loop.connect_write_pipe(
163*cda5da8dSAndroid Build Coastguard Worker                    lambda: WriteSubprocessPipeProto(self, 0),
164*cda5da8dSAndroid Build Coastguard Worker                    proc.stdin)
165*cda5da8dSAndroid Build Coastguard Worker                self._pipes[0] = pipe
166*cda5da8dSAndroid Build Coastguard Worker
167*cda5da8dSAndroid Build Coastguard Worker            if proc.stdout is not None:
168*cda5da8dSAndroid Build Coastguard Worker                _, pipe = await loop.connect_read_pipe(
169*cda5da8dSAndroid Build Coastguard Worker                    lambda: ReadSubprocessPipeProto(self, 1),
170*cda5da8dSAndroid Build Coastguard Worker                    proc.stdout)
171*cda5da8dSAndroid Build Coastguard Worker                self._pipes[1] = pipe
172*cda5da8dSAndroid Build Coastguard Worker
173*cda5da8dSAndroid Build Coastguard Worker            if proc.stderr is not None:
174*cda5da8dSAndroid Build Coastguard Worker                _, pipe = await loop.connect_read_pipe(
175*cda5da8dSAndroid Build Coastguard Worker                    lambda: ReadSubprocessPipeProto(self, 2),
176*cda5da8dSAndroid Build Coastguard Worker                    proc.stderr)
177*cda5da8dSAndroid Build Coastguard Worker                self._pipes[2] = pipe
178*cda5da8dSAndroid Build Coastguard Worker
179*cda5da8dSAndroid Build Coastguard Worker            assert self._pending_calls is not None
180*cda5da8dSAndroid Build Coastguard Worker
181*cda5da8dSAndroid Build Coastguard Worker            loop.call_soon(self._protocol.connection_made, self)
182*cda5da8dSAndroid Build Coastguard Worker            for callback, data in self._pending_calls:
183*cda5da8dSAndroid Build Coastguard Worker                loop.call_soon(callback, *data)
184*cda5da8dSAndroid Build Coastguard Worker            self._pending_calls = None
185*cda5da8dSAndroid Build Coastguard Worker        except (SystemExit, KeyboardInterrupt):
186*cda5da8dSAndroid Build Coastguard Worker            raise
187*cda5da8dSAndroid Build Coastguard Worker        except BaseException as exc:
188*cda5da8dSAndroid Build Coastguard Worker            if waiter is not None and not waiter.cancelled():
189*cda5da8dSAndroid Build Coastguard Worker                waiter.set_exception(exc)
190*cda5da8dSAndroid Build Coastguard Worker        else:
191*cda5da8dSAndroid Build Coastguard Worker            if waiter is not None and not waiter.cancelled():
192*cda5da8dSAndroid Build Coastguard Worker                waiter.set_result(None)
193*cda5da8dSAndroid Build Coastguard Worker
194*cda5da8dSAndroid Build Coastguard Worker    def _call(self, cb, *data):
195*cda5da8dSAndroid Build Coastguard Worker        if self._pending_calls is not None:
196*cda5da8dSAndroid Build Coastguard Worker            self._pending_calls.append((cb, data))
197*cda5da8dSAndroid Build Coastguard Worker        else:
198*cda5da8dSAndroid Build Coastguard Worker            self._loop.call_soon(cb, *data)
199*cda5da8dSAndroid Build Coastguard Worker
200*cda5da8dSAndroid Build Coastguard Worker    def _pipe_connection_lost(self, fd, exc):
201*cda5da8dSAndroid Build Coastguard Worker        self._call(self._protocol.pipe_connection_lost, fd, exc)
202*cda5da8dSAndroid Build Coastguard Worker        self._try_finish()
203*cda5da8dSAndroid Build Coastguard Worker
204*cda5da8dSAndroid Build Coastguard Worker    def _pipe_data_received(self, fd, data):
205*cda5da8dSAndroid Build Coastguard Worker        self._call(self._protocol.pipe_data_received, fd, data)
206*cda5da8dSAndroid Build Coastguard Worker
207*cda5da8dSAndroid Build Coastguard Worker    def _process_exited(self, returncode):
208*cda5da8dSAndroid Build Coastguard Worker        assert returncode is not None, returncode
209*cda5da8dSAndroid Build Coastguard Worker        assert self._returncode is None, self._returncode
210*cda5da8dSAndroid Build Coastguard Worker        if self._loop.get_debug():
211*cda5da8dSAndroid Build Coastguard Worker            logger.info('%r exited with return code %r', self, returncode)
212*cda5da8dSAndroid Build Coastguard Worker        self._returncode = returncode
213*cda5da8dSAndroid Build Coastguard Worker        if self._proc.returncode is None:
214*cda5da8dSAndroid Build Coastguard Worker            # asyncio uses a child watcher: copy the status into the Popen
215*cda5da8dSAndroid Build Coastguard Worker            # object. On Python 3.6, it is required to avoid a ResourceWarning.
216*cda5da8dSAndroid Build Coastguard Worker            self._proc.returncode = returncode
217*cda5da8dSAndroid Build Coastguard Worker        self._call(self._protocol.process_exited)
218*cda5da8dSAndroid Build Coastguard Worker
219*cda5da8dSAndroid Build Coastguard Worker        self._try_finish()
220*cda5da8dSAndroid Build Coastguard Worker
221*cda5da8dSAndroid Build Coastguard Worker    async def _wait(self):
222*cda5da8dSAndroid Build Coastguard Worker        """Wait until the process exit and return the process return code.
223*cda5da8dSAndroid Build Coastguard Worker
224*cda5da8dSAndroid Build Coastguard Worker        This method is a coroutine."""
225*cda5da8dSAndroid Build Coastguard Worker        if self._returncode is not None:
226*cda5da8dSAndroid Build Coastguard Worker            return self._returncode
227*cda5da8dSAndroid Build Coastguard Worker
228*cda5da8dSAndroid Build Coastguard Worker        waiter = self._loop.create_future()
229*cda5da8dSAndroid Build Coastguard Worker        self._exit_waiters.append(waiter)
230*cda5da8dSAndroid Build Coastguard Worker        return await waiter
231*cda5da8dSAndroid Build Coastguard Worker
232*cda5da8dSAndroid Build Coastguard Worker    def _try_finish(self):
233*cda5da8dSAndroid Build Coastguard Worker        assert not self._finished
234*cda5da8dSAndroid Build Coastguard Worker        if self._returncode is None:
235*cda5da8dSAndroid Build Coastguard Worker            return
236*cda5da8dSAndroid Build Coastguard Worker        if all(p is not None and p.disconnected
237*cda5da8dSAndroid Build Coastguard Worker               for p in self._pipes.values()):
238*cda5da8dSAndroid Build Coastguard Worker            self._finished = True
239*cda5da8dSAndroid Build Coastguard Worker            self._call(self._call_connection_lost, None)
240*cda5da8dSAndroid Build Coastguard Worker
241*cda5da8dSAndroid Build Coastguard Worker    def _call_connection_lost(self, exc):
242*cda5da8dSAndroid Build Coastguard Worker        try:
243*cda5da8dSAndroid Build Coastguard Worker            self._protocol.connection_lost(exc)
244*cda5da8dSAndroid Build Coastguard Worker        finally:
245*cda5da8dSAndroid Build Coastguard Worker            # wake up futures waiting for wait()
246*cda5da8dSAndroid Build Coastguard Worker            for waiter in self._exit_waiters:
247*cda5da8dSAndroid Build Coastguard Worker                if not waiter.cancelled():
248*cda5da8dSAndroid Build Coastguard Worker                    waiter.set_result(self._returncode)
249*cda5da8dSAndroid Build Coastguard Worker            self._exit_waiters = None
250*cda5da8dSAndroid Build Coastguard Worker            self._loop = None
251*cda5da8dSAndroid Build Coastguard Worker            self._proc = None
252*cda5da8dSAndroid Build Coastguard Worker            self._protocol = None
253*cda5da8dSAndroid Build Coastguard Worker
254*cda5da8dSAndroid Build Coastguard Worker
255*cda5da8dSAndroid Build Coastguard Workerclass WriteSubprocessPipeProto(protocols.BaseProtocol):
256*cda5da8dSAndroid Build Coastguard Worker
257*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, proc, fd):
258*cda5da8dSAndroid Build Coastguard Worker        self.proc = proc
259*cda5da8dSAndroid Build Coastguard Worker        self.fd = fd
260*cda5da8dSAndroid Build Coastguard Worker        self.pipe = None
261*cda5da8dSAndroid Build Coastguard Worker        self.disconnected = False
262*cda5da8dSAndroid Build Coastguard Worker
263*cda5da8dSAndroid Build Coastguard Worker    def connection_made(self, transport):
264*cda5da8dSAndroid Build Coastguard Worker        self.pipe = transport
265*cda5da8dSAndroid Build Coastguard Worker
266*cda5da8dSAndroid Build Coastguard Worker    def __repr__(self):
267*cda5da8dSAndroid Build Coastguard Worker        return f'<{self.__class__.__name__} fd={self.fd} pipe={self.pipe!r}>'
268*cda5da8dSAndroid Build Coastguard Worker
269*cda5da8dSAndroid Build Coastguard Worker    def connection_lost(self, exc):
270*cda5da8dSAndroid Build Coastguard Worker        self.disconnected = True
271*cda5da8dSAndroid Build Coastguard Worker        self.proc._pipe_connection_lost(self.fd, exc)
272*cda5da8dSAndroid Build Coastguard Worker        self.proc = None
273*cda5da8dSAndroid Build Coastguard Worker
274*cda5da8dSAndroid Build Coastguard Worker    def pause_writing(self):
275*cda5da8dSAndroid Build Coastguard Worker        self.proc._protocol.pause_writing()
276*cda5da8dSAndroid Build Coastguard Worker
277*cda5da8dSAndroid Build Coastguard Worker    def resume_writing(self):
278*cda5da8dSAndroid Build Coastguard Worker        self.proc._protocol.resume_writing()
279*cda5da8dSAndroid Build Coastguard Worker
280*cda5da8dSAndroid Build Coastguard Worker
281*cda5da8dSAndroid Build Coastguard Workerclass ReadSubprocessPipeProto(WriteSubprocessPipeProto,
282*cda5da8dSAndroid Build Coastguard Worker                              protocols.Protocol):
283*cda5da8dSAndroid Build Coastguard Worker
284*cda5da8dSAndroid Build Coastguard Worker    def data_received(self, data):
285*cda5da8dSAndroid Build Coastguard Worker        self.proc._pipe_data_received(self.fd, data)
286