Commit acdb782a authored by Victor Stinner's avatar Victor Stinner

asyncio: sync with Tulip

* Tulip issue #184: Log subprocess events in debug mode

  - Log stdin, stdout and stderr transports and protocols
  - Log process identifier (pid)
  - Log connection of pipes
  - Log process exit
  - Log Process.communicate() tasks: feed stdin, read stdout and stderr
  - Add __repr__() method to many classes related to subprocesses


* Add BaseSubprocessTransport._pid attribute. Store the pid so it is still
  accessible after the process exited. It's more convinient for debug.

* create_connection(): add the socket in the "connected to" debug log

* Clean up some docstrings and comments. Remove unused unimplemented
  _read_from_self().
parent b1ebfddd
This diff is collapsed.
...@@ -4,6 +4,7 @@ import subprocess ...@@ -4,6 +4,7 @@ import subprocess
from . import protocols from . import protocols
from . import transports from . import transports
from .coroutines import coroutine from .coroutines import coroutine
from .log import logger
class BaseSubprocessTransport(transports.SubprocessTransport): class BaseSubprocessTransport(transports.SubprocessTransport):
...@@ -14,6 +15,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport): ...@@ -14,6 +15,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
super().__init__(extra) super().__init__(extra)
self._protocol = protocol self._protocol = protocol
self._loop = loop self._loop = loop
self._pid = None
self._pipes = {} self._pipes = {}
if stdin == subprocess.PIPE: if stdin == subprocess.PIPE:
...@@ -27,7 +29,36 @@ class BaseSubprocessTransport(transports.SubprocessTransport): ...@@ -27,7 +29,36 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
self._returncode = None self._returncode = None
self._start(args=args, shell=shell, stdin=stdin, stdout=stdout, self._start(args=args, shell=shell, stdin=stdin, stdout=stdout,
stderr=stderr, bufsize=bufsize, **kwargs) stderr=stderr, bufsize=bufsize, **kwargs)
self._pid = self._proc.pid
self._extra['subprocess'] = self._proc 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)
def __repr__(self):
info = [self.__class__.__name__, 'pid=%s' % self._pid]
if self._returncode is not None:
info.append('returncode=%s' % self._returncode)
stdin = self._pipes.get(0)
if stdin is not None:
info.append('stdin=%s' % stdin.pipe)
stdout = self._pipes.get(1)
stderr = self._pipes.get(2)
if stdout is not None and stderr is stdout:
info.append('stdout=stderr=%s' % stdout.pipe)
else:
if stdout is not None:
info.append('stdout=%s' % stdout.pipe)
if stderr is not None:
info.append('stderr=%s' % stderr.pipe)
return '<%s>' % ' '.join(info)
def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs): def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
raise NotImplementedError raise NotImplementedError
...@@ -45,7 +76,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport): ...@@ -45,7 +76,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
self.terminate() self.terminate()
def get_pid(self): def get_pid(self):
return self._proc.pid return self._pid
def get_returncode(self): def get_returncode(self):
return self._returncode return self._returncode
...@@ -108,6 +139,9 @@ class BaseSubprocessTransport(transports.SubprocessTransport): ...@@ -108,6 +139,9 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
def _process_exited(self, returncode): def _process_exited(self, returncode):
assert returncode is not None, returncode assert returncode is not None, returncode
assert self._returncode is None, self._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 self._returncode = returncode
self._call(self._protocol.process_exited) self._call(self._protocol.process_exited)
self._try_finish() self._try_finish()
...@@ -141,6 +175,10 @@ class WriteSubprocessPipeProto(protocols.BaseProtocol): ...@@ -141,6 +175,10 @@ class WriteSubprocessPipeProto(protocols.BaseProtocol):
def connection_made(self, transport): def connection_made(self, transport):
self.pipe = transport self.pipe = transport
def __repr__(self):
return ('<%s fd=%s pipe=%r>'
% (self.__class__.__name__, self.fd, self.pipe))
def connection_lost(self, exc): def connection_lost(self, exc):
self.disconnected = True self.disconnected = True
self.proc._pipe_connection_lost(self.fd, exc) self.proc._pipe_connection_lost(self.fd, exc)
......
...@@ -15,6 +15,7 @@ from . import events ...@@ -15,6 +15,7 @@ from . import events
from . import futures from . import futures
from . import protocols from . import protocols
from .coroutines import coroutine from .coroutines import coroutine
from .log import logger
_DEFAULT_LIMIT = 2**16 _DEFAULT_LIMIT = 2**16
...@@ -153,10 +154,15 @@ class FlowControlMixin(protocols.Protocol): ...@@ -153,10 +154,15 @@ class FlowControlMixin(protocols.Protocol):
def pause_writing(self): def pause_writing(self):
assert not self._paused assert not self._paused
self._paused = True self._paused = True
if self._loop.get_debug():
logger.debug("%r pauses writing", self)
def resume_writing(self): def resume_writing(self):
assert self._paused assert self._paused
self._paused = False self._paused = False
if self._loop.get_debug():
logger.debug("%r resumes writing", self)
waiter = self._drain_waiter waiter = self._drain_waiter
if waiter is not None: if waiter is not None:
self._drain_waiter = None self._drain_waiter = None
...@@ -244,6 +250,12 @@ class StreamWriter: ...@@ -244,6 +250,12 @@ class StreamWriter:
self._reader = reader self._reader = reader
self._loop = loop self._loop = loop
def __repr__(self):
info = [self.__class__.__name__, 'transport=%r' % self._transport]
if self._reader is not None:
info.append('reader=%r' % self._reader)
return '<%s>' % ' '.join(info)
@property @property
def transport(self): def transport(self):
return self._transport return self._transport
......
...@@ -9,6 +9,7 @@ from . import protocols ...@@ -9,6 +9,7 @@ from . import protocols
from . import streams from . import streams
from . import tasks from . import tasks
from .coroutines import coroutine from .coroutines import coroutine
from .log import logger
PIPE = subprocess.PIPE PIPE = subprocess.PIPE
...@@ -28,6 +29,16 @@ class SubprocessStreamProtocol(streams.FlowControlMixin, ...@@ -28,6 +29,16 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
self._waiters = collections.deque() self._waiters = collections.deque()
self._transport = None self._transport = None
def __repr__(self):
info = [self.__class__.__name__]
if self.stdin is not None:
info.append('stdin=%r' % self.stdin)
if self.stdout is not None:
info.append('stdout=%r' % self.stdout)
if self.stderr is not None:
info.append('stderr=%r' % self.stderr)
return '<%s>' % ' '.join(info)
def connection_made(self, transport): def connection_made(self, transport):
self._transport = transport self._transport = transport
if transport.get_pipe_transport(1): if transport.get_pipe_transport(1):
...@@ -91,6 +102,9 @@ class Process: ...@@ -91,6 +102,9 @@ class Process:
self.stderr = protocol.stderr self.stderr = protocol.stderr
self.pid = transport.get_pid() self.pid = transport.get_pid()
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, self.pid)
@property @property
def returncode(self): def returncode(self):
return self._transport.get_returncode() return self._transport.get_returncode()
...@@ -126,7 +140,13 @@ class Process: ...@@ -126,7 +140,13 @@ class Process:
@coroutine @coroutine
def _feed_stdin(self, input): def _feed_stdin(self, input):
self.stdin.write(input) self.stdin.write(input)
if self._loop.get_debug():
logger.debug('%r communicate: feed stdin (%s bytes)',
self, len(input))
yield from self.stdin.drain() yield from self.stdin.drain()
if self._loop.get_debug():
logger.debug('%r communicate: close stdin', self)
self.stdin.close() self.stdin.close()
@coroutine @coroutine
...@@ -141,7 +161,13 @@ class Process: ...@@ -141,7 +161,13 @@ class Process:
else: else:
assert fd == 1 assert fd == 1
stream = self.stdout stream = self.stdout
if self._loop.get_debug():
name = 'stdout' if fd == 1 else 'stderr'
logger.debug('%r communicate: read %s', self, name)
output = yield from stream.read() output = yield from stream.read()
if self._loop.get_debug():
name = 'stdout' if fd == 1 else 'stderr'
logger.debug('%r communicate: close %s', self, name)
transport.close() transport.close()
return output return output
......
...@@ -565,7 +565,7 @@ class AbstractChildWatcher: ...@@ -565,7 +565,7 @@ class AbstractChildWatcher:
process 'pid' terminates. Specifying another callback for the same process 'pid' terminates. Specifying another callback for the same
process replaces the previous handler. process replaces the previous handler.
Note: callback() must be thread-safe Note: callback() must be thread-safe.
""" """
raise NotImplementedError() raise NotImplementedError()
...@@ -721,6 +721,9 @@ class SafeChildWatcher(BaseChildWatcher): ...@@ -721,6 +721,9 @@ class SafeChildWatcher(BaseChildWatcher):
return return
returncode = self._compute_returncode(status) returncode = self._compute_returncode(status)
if self._loop.get_debug():
logger.debug('process %s exited with returncode %s',
expected_pid, returncode)
try: try:
callback, args = self._callbacks.pop(pid) callback, args = self._callbacks.pop(pid)
...@@ -818,8 +821,16 @@ class FastChildWatcher(BaseChildWatcher): ...@@ -818,8 +821,16 @@ class FastChildWatcher(BaseChildWatcher):
if self._forks: if self._forks:
# It may not be registered yet. # It may not be registered yet.
self._zombies[pid] = returncode self._zombies[pid] = returncode
if self._loop.get_debug():
logger.debug('unknown process %s exited '
'with returncode %s',
pid, returncode)
continue continue
callback = None callback = None
else:
if self._loop.get_debug():
logger.debug('process %s exited with returncode %s',
pid, returncode)
if callback is None: if callback is None:
logger.warning( logger.warning(
......
...@@ -43,8 +43,6 @@ class BaseEventLoopTests(test_utils.TestCase): ...@@ -43,8 +43,6 @@ class BaseEventLoopTests(test_utils.TestCase):
NotImplementedError, self.loop._process_events, []) NotImplementedError, self.loop._process_events, [])
self.assertRaises( self.assertRaises(
NotImplementedError, self.loop._write_to_self) NotImplementedError, self.loop._write_to_self)
self.assertRaises(
NotImplementedError, self.loop._read_from_self)
self.assertRaises( self.assertRaises(
NotImplementedError, NotImplementedError,
self.loop._make_read_pipe_transport, m, m) self.loop._make_read_pipe_transport, m, m)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment