Fix OpenSSH-related ssh process exit race

Mitigate the effects of observing the ssh process still running
after seeing an EOF on stdout when using OpenSSH with
ControlPersist, since it does not close the stderr file descriptor
in this case.
This commit is contained in:
jasdeep-hundal 2016-10-27 12:36:56 -07:00 committed by Toshio Kuratomi
parent 66d4bb840d
commit 679da00236

View file

@ -451,6 +451,14 @@ class Connection(ConnectionBase):
b_chunk = p.stdout.read() b_chunk = p.stdout.read()
if b_chunk == b'': if b_chunk == b'':
rpipes.remove(p.stdout) rpipes.remove(p.stdout)
# When ssh has ControlMaster (+ControlPath/Persist) enabled, the
# first connection goes into the background and we never see EOF
# on stderr. If we see EOF on stdout, lower the select timeout
# to reduce the time wasted selecting on stderr if we observe
# that the process has not yet existed after this EOF. Otherwise
# we may spend a long timeout period waiting for an EOF that is
# not going to arrive until the persisted connection closes.
timeout = 1
b_tmp_stdout += b_chunk b_tmp_stdout += b_chunk
display.debug("stdout chunk (state=%s):\n>>>%s<<<\n" % (state, to_text(b_chunk))) display.debug("stdout chunk (state=%s):\n>>>%s<<<\n" % (state, to_text(b_chunk)))
@ -534,18 +542,11 @@ class Connection(ConnectionBase):
if p.poll() is not None: if p.poll() is not None:
if not rpipes or not rfd: if not rpipes or not rfd:
break break
# We should not see further writes to the stdout/stderr file
# When ssh has ControlMaster (+ControlPath/Persist) enabled, the # descriptors after the process has closed, set the select
# first connection goes into the background and we never see EOF # timeout to gather any last writes we may have missed.
# on stderr. If we see EOF on stdout and the process has exited, timeout = 0
# we're probably done. We call select again with a zero timeout, continue
# just to make certain we don't miss anything that may have been
# written to stderr between the time we called select() and when
# we learned that the process had finished.
if p.stdout not in rpipes:
timeout = 0
continue
# If the process has not yet exited, but we've already read EOF from # If the process has not yet exited, but we've already read EOF from
# its stdout and stderr (and thus removed both from rpipes), we can # its stdout and stderr (and thus removed both from rpipes), we can