# HG changeset patch # User Gregory Szorc # Date 2018-02-21 22:02:23 # Node ID 11ba1a96f946a6223b238047ef04e2b4344f6623 # Parent 066e6a9d52bb1b76cd51e00c320fb04a267de4cf sshpeer: defer pipe buffering and stderr sidechannel binding The doublepipe and bufferedinputpipe types facilitate polling multiple pipes without blocking and for automatically forwarding output from the SSH server's stderr pipe to the ui as "remote: " output. This all happens automatically and callers don't need to worry about reading from multiple pipes. An upcoming change to version 2 of the SSH wire protocol will eliminate the use of stderr and move side-channel output into the "main" pipe. The SSH wire protocol will use a pair of unidirectional pipes - just like the HTTP protocol. In this future world, the doublepipe primitive isn't necessary because the stderr pipe won't be used. To prepare for eventually not using doublepipe, we delay the construction of this primitive from immediately after connection establishment to inside construction of the peer instance. The handshake occurs between these two events. So we had to teach the handshake code to read from stderr so any stderr output from the server is still attended to early in the connection lifetime. Differential Revision: https://phab.mercurial-scm.org/D2383 diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py --- a/mercurial/sshpeer.py +++ b/mercurial/sshpeer.py @@ -156,13 +156,13 @@ def _makeconnection(ui, sshcmd, args, re # move to threading. stdin, stdout, stderr, proc = util.popen4(cmd, bufsize=0, env=sshenv) - stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr) - stdin = doublepipe(ui, stdin, stderr) - return proc, stdin, stdout, stderr def _performhandshake(ui, stdin, stdout, stderr): def badresponse(): + # Flush any output on stderr. + _forwardoutput(ui, stderr) + msg = _('no suitable response from remote hg') hint = ui.config('ui', 'ssherrorhint') raise error.RepoError(msg, hint=hint) @@ -331,6 +331,9 @@ def _performhandshake(ui, stdin, stdout, if not caps: badresponse() + # Flush any output on stderr before proceeding. + _forwardoutput(ui, stderr) + return protoname, caps class sshv1peer(wireproto.wirepeer): @@ -347,6 +350,12 @@ class sshv1peer(wireproto.wirepeer): # self._subprocess is unused. Keeping a handle on the process # holds a reference and prevents it from being garbage collected. self._subprocess = proc + + # And we hook up our "doublepipe" wrapper to allow querying + # stderr any time we perform I/O. + stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr) + stdin = doublepipe(ui, stdin, stderr) + self._pipeo = stdin self._pipei = stdout self._pipee = stderr diff --git a/tests/test-check-interfaces.py b/tests/test-check-interfaces.py --- a/tests/test-check-interfaces.py +++ b/tests/test-check-interfaces.py @@ -59,16 +59,20 @@ class badpeer(httppeer.httppeer): def badmethod(self): pass +class dummypipe(object): + def close(self): + pass + def main(): ui = uimod.ui() checkobject(badpeer()) checkobject(httppeer.httppeer(ui, 'http://localhost')) checkobject(localrepo.localpeer(dummyrepo())) - checkobject(sshpeer.sshv1peer(ui, 'ssh://localhost/foo', None, None, None, - None, None)) - checkobject(sshpeer.sshv2peer(ui, 'ssh://localhost/foo', None, None, None, - None, None)) + checkobject(sshpeer.sshv1peer(ui, 'ssh://localhost/foo', None, dummypipe(), + dummypipe(), None, None)) + checkobject(sshpeer.sshv2peer(ui, 'ssh://localhost/foo', None, dummypipe(), + dummypipe(), None, None)) checkobject(bundlerepo.bundlepeer(dummyrepo())) checkobject(statichttprepo.statichttppeer(dummyrepo())) checkobject(unionrepo.unionpeer(dummyrepo()))