Patchwork D2383: sshpeer: defer pipe buffering and stderr sidechannel binding

login
register
mail settings
Submitter phabricator
Date Feb. 21, 2018, 10:08 p.m.
Message ID <differential-rev-PHID-DREV-ux245cnkw7r6xegblrtt-req@phab.mercurial-scm.org>
Download mbox | patch
Permalink /patch/28216/
State Superseded
Headers show

Comments

phabricator - Feb. 21, 2018, 10:08 p.m.
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  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.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2383

AFFECTED FILES
  mercurial/sshpeer.py
  tests/test-check-interfaces.py

CHANGE DETAILS




To: indygreg, #hg-reviewers
Cc: mercurial-devel

Patch

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 @@ 
     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()))
diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
--- a/mercurial/sshpeer.py
+++ b/mercurial/sshpeer.py
@@ -156,13 +156,13 @@ 
     # 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 @@ 
     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 @@ 
         # 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