Patchwork [2,of,4] sshpeer: introduce a "doublepipe" class

login
register
mail settings
Submitter Pierre-Yves David
Date June 3, 2015, 8:26 p.m.
Message ID <c27dfc2cf0a3bd00d5c6.1433363191@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/9462/
State Superseded
Commit 3dd3ccf7b8074b982d6a95888b4e4df10901a253
Headers show

Comments

Pierre-Yves David - June 3, 2015, 8:26 p.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1432309691 18000
#      Fri May 22 10:48:11 2015 -0500
# Node ID c27dfc2cf0a3bd00d5c6037d1cdb0960bbb50070
# Parent  0521446e1c5934bf377d8c6bd8a48baad6b57ae1
sshpeer: introduce a "doublepipe" class

This class is responsible for ensuring we still process the server output
streamed through the ssh's 'stderr' pipe during the initial wait for other
protocol streams.

It currently only works on posix system because of its use of 'select.select'.

Patch

diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
--- a/mercurial/sshpeer.py
+++ b/mercurial/sshpeer.py
@@ -34,10 +34,75 @@  def _forwardoutput(ui, pipe):
     s = util.readpipe(pipe)
     if s:
         for l in s.splitlines():
             ui.status(_("remote: "), l, '\n')
 
+class doublepipe(object):
+    """Operate a side-channel pipe in addition of a main one
+
+    The side-channel pipe contains server output to be forwarded to the user
+    input. The double pipe will behave as the "main" pipe, but will ensure the
+    content of the "side" pipe is properly processed while we wait for blocking
+    call on the "main" pipe.
+
+    If large amounts of data are read from "main", the forward will cease after
+    the first bytes start to appear. This simplifies the implementation
+    without affecting actual output of sshpeer too much as we rarely issue
+    large read for data not yet emitted by the server.
+
+    The main pipe is expected to be a 'bufferedinputpipe' from the util module
+    that handle all the os specific bites. This class lives in this module
+    because it focus on behavior specifig to the ssh protocol."""
+
+    def __init__(self, ui, main, side):
+        self._ui = ui
+        self._main = main
+        self._side = side
+
+    def _wait(self):
+        """wait until some data are available on main or side
+
+        return a pair of boolean (ismainready, issideready)
+
+        (This will only wait for data if the setup is supported by `util.poll`)
+        """
+        if self._main.hasbuffer:
+            return (True, True) # main has data, assume side is worth poking at.
+        fds = [self._main.fileno(), self._side.fileno()]
+        try:
+            act = util.poll(fds)
+        except NotImplementedError:
+            # non supported yet case, assume all have data.
+            act = fds
+        return (self._main.fileno() in act, self._side.fileno() in act)
+
+    def read(self, size):
+        return self._call('read', size)
+
+    def readline(self):
+        return self._call('readline')
+
+    def _call(self, methname, size=None):
+        """call <methname> on "main", forward output of "side" while blocking
+        """
+        if size == 0 or self._main.closed:
+            _forwardoutput(self._ui, self._side)
+            return ''
+        while True:
+            mainready, sideready = self._wait()
+            if sideready:
+                _forwardoutput(self._ui, self._side)
+            if mainready:
+                meth = getattr(self._main, methname)
+                if size is None:
+                    return meth()
+                else:
+                    return meth(size)
+
+    def close(self):
+        return self._main.close()
+
 class sshpeer(wireproto.wirepeer):
     def __init__(self, ui, path, create=False):
         self._url = path
         self.ui = ui
         self.pipeo = self.pipei = self.pipee = None