core: rearrange stdio setup to cope with buffering; closes #422

This commit is contained in:
David Wilson 2018-11-06 01:47:36 +00:00
parent 704e6c0b2c
commit 01c4f3fee1
2 changed files with 28 additions and 20 deletions

View File

@ -245,6 +245,10 @@ Core Library
execution of its :keyword:`finally` block was delayed on Python 3. Now execution of its :keyword:`finally` block was delayed on Python 3. Now
callers explicitly close the generator when finished. callers explicitly close the generator when finished.
* `#421 <https://github.com/dw/mitogen/issues/421>`_: the fork method could
fail to start if :data:`sys.stdout` was opened in block buffered mode, and
buffered data was pending in the parent prior to fork.
* `16ca111e <https://github.com/dw/mitogen/commit/16ca111e>`_: handle OpenSSH * `16ca111e <https://github.com/dw/mitogen/commit/16ca111e>`_: handle OpenSSH
7.5 permission denied prompts when ``~/.ssh/config`` rewrites are present. 7.5 permission denied prompts when ``~/.ssh/config`` rewrites are present.

View File

@ -2808,28 +2808,32 @@ class ExternalContext(object):
mitogen.parent_ids = self.config['parent_ids'][:] mitogen.parent_ids = self.config['parent_ids'][:]
mitogen.parent_id = mitogen.parent_ids[0] mitogen.parent_id = mitogen.parent_ids[0]
def _setup_stdio(self): def _nullify_stdio(self):
# We must open this prior to closing stdout, otherwise it will recycle """
# a standard handle, the dup2() will not error, and on closing it, we Open /dev/null to replace stdin, and stdout/stderr temporarily. In case
# lose a standrd handle, causing later code to again recycle a standard of odd startup, assume we may be allocated a standard handle.
# handle. """
fp = open('/dev/null') fd = os.open('/dev/null', os.O_RDWR)
# When sys.stdout was opened by the runtime, overwriting it will not
# cause close to be called. However when forking from a child that
# previously used fdopen, overwriting it /will/ cause close to be
# called. So we must explicitly close it before IoLogger overwrites the
# file descriptor, otherwise the assignment below will cause stdout to
# be closed.
sys.stdout.close()
sys.stdout = None
try: try:
os.dup2(fp.fileno(), 0) for stdfd in (0, 1, 2):
os.dup2(fp.fileno(), 1) if fd != stdfd:
os.dup2(fp.fileno(), 2) os.dup2(fd, stdfd)
finally: finally:
fp.close() if fd not in (0, 1, 2):
os.close(fd)
def _setup_stdio(self):
# When sys.stdout was opened by the runtime, overwriting it will not
# close FD 1. However when forking from a child that previously used
# fdopen(), overwriting it /will/ close FD 1. So we must swallow the
# close before IoLogger overwrites FD 1, otherwise its new FD 1 will be
# clobbered. Additionally, stdout must be replaced with /dev/null prior
# to stdout.close(), since if block buffering was active in the parent,
# any pre-fork buffered data will be flushed on close(), corrupting the
# connection to the parent.
self._nullify_stdio()
sys.stdout.close()
self._nullify_stdio()
self.stdout_log = IoLogger(self.broker, 'stdout', 1) self.stdout_log = IoLogger(self.broker, 'stdout', 1)
self.stderr_log = IoLogger(self.broker, 'stderr', 2) self.stderr_log = IoLogger(self.broker, 'stderr', 2)