debugcommands: support for sending "batch" requests
authorGregory Szorc <gregory.szorc@gmail.com>
Fri, 23 Feb 2018 12:50:59 -0800
changeset 36560 097ad1079192
parent 36559 bde0bd50f368
child 36561 1138e5c0fbc9
debugcommands: support for sending "batch" requests Let's teach `hg debugwireproto` to send "batch" requests. The easiest way to implement this was as a pair of instructions to begin and end a batched operation. Otherwise, we would have to reinvent the parsing wheel or factor out the parsing code. To prove it works, we add a batched request to test-ssh-proto.t. Differential Revision: https://phab.mercurial-scm.org/D2408
mercurial/debugcommands.py
tests/test-ssh-proto.t
--- a/mercurial/debugcommands.py	Thu Mar 01 08:27:30 2018 -0800
+++ b/mercurial/debugcommands.py	Fri Feb 23 12:50:59 2018 -0800
@@ -2629,6 +2629,21 @@
     Values are interpreted as Python b'' literals. This allows encoding
     special byte sequences via backslash escaping.
 
+    batchbegin
+    ----------
+
+    Instruct the peer to begin a batched send.
+
+    All ``command`` blocks are queued for execution until the next
+    ``batchsubmit`` block.
+
+    batchsubmit
+    -----------
+
+    Submit previously queued ``command`` blocks as a batch request.
+
+    This action MUST be paired with a ``batchbegin`` action.
+
     close
     -----
 
@@ -2716,6 +2731,8 @@
     else:
         raise error.Abort(_('only --localssh is currently supported'))
 
+    batchedcommands = None
+
     # Now perform actions based on the parsed wire language instructions.
     for action, lines in blocks:
         if action in ('raw', 'raw+'):
@@ -2747,10 +2764,29 @@
 
                 args[key] = util.unescapestr(value)
 
+            if batchedcommands is not None:
+                batchedcommands.append((command, args))
+                continue
+
             ui.status(_('sending %s command\n') % command)
             res = peer._call(command, **args)
             ui.status(_('response: %s\n') % util.escapedata(res))
 
+        elif action == 'batchbegin':
+            if batchedcommands is not None:
+                raise error.Abort(_('nested batchbegin not allowed'))
+
+            batchedcommands = []
+        elif action == 'batchsubmit':
+            # There is a batching API we could go through. But it would be
+            # difficult to normalize requests into function calls. It is easier
+            # to bypass this layer and normalize to commands + args.
+            ui.status(_('sending batch with %d sub-commands\n') %
+                      len(batchedcommands))
+            for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
+                ui.status(_('response #%d: %s\n') % (i, util.escapedata(chunk)))
+
+            batchedcommands = None
         elif action == 'close':
             peer.close()
         elif action == 'readavailable':
@@ -2765,6 +2801,9 @@
         else:
             raise error.Abort(_('unknown action: %s') % action)
 
+    if batchedcommands is not None:
+        raise error.Abort(_('unclosed "batchbegin" request'))
+
     if peer:
         peer.close()
 
--- a/tests/test-ssh-proto.t	Thu Mar 01 08:27:30 2018 -0800
+++ b/tests/test-ssh-proto.t	Fri Feb 23 12:50:59 2018 -0800
@@ -1830,3 +1830,105 @@
   o>     15\n
   o> bufferedread(15) -> 15: publishing	True
   response: publishing	True
+
+  $ cd ..
+
+Test batching of requests
+
+  $ hg init batching
+  $ cd batching
+  $ echo 0 > foo
+  $ hg add foo
+  $ hg -q commit -m initial
+  $ hg phase --public
+  $ echo 1 > foo
+  $ hg commit -m 'commit 1'
+  $ hg -q up 0
+  $ echo 2 > foo
+  $ hg commit -m 'commit 2'
+  created new head
+  $ hg book -r 1 bookA
+  $ hg book -r 2 bookB
+
+  $ debugwireproto << EOF
+  > batchbegin
+  > command heads
+  > command listkeys
+  >     namespace bookmarks
+  > command listkeys
+  >     namespace phases
+  > batchsubmit
+  > EOF
+  testing ssh1
+  creating ssh peer from handshake results
+  i> write(104) -> None:
+  i>     hello\n
+  i>     between\n
+  i>     pairs 81\n
+  i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
+  i> flush() -> None
+  o> readline() -> 4:
+  o>     384\n
+  o> readline() -> 384:
+  o>     capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN\n
+  o> readline() -> 2:
+  o>     1\n
+  o> readline() -> 1:
+  o>     \n
+  sending batch with 3 sub-commands
+  i> write(6) -> None:
+  i>     batch\n
+  i> write(4) -> None:
+  i>     * 0\n
+  i> write(8) -> None:
+  i>     cmds 61\n
+  i> write(61) -> None: heads ;listkeys namespace=bookmarks;listkeys namespace=phases
+  i> flush() -> None
+  o> bufferedreadline() -> 4:
+  o>     278\n
+  o> bufferedread(278) -> 278:
+  o>     bfebe6bd38eebc6f8202e419c1171268987ea6a6 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\n
+  o>     ;bookA	4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\n
+  o>     bookB	bfebe6bd38eebc6f8202e419c1171268987ea6a6;4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab	1\n
+  o>     bfebe6bd38eebc6f8202e419c1171268987ea6a6	1\n
+  o>     publishing	True
+  response #0: bfebe6bd38eebc6f8202e419c1171268987ea6a6 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\n
+  response #1: bookA	4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\nbookB	bfebe6bd38eebc6f8202e419c1171268987ea6a6
+  response #2: 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab	1\nbfebe6bd38eebc6f8202e419c1171268987ea6a6	1\npublishing	True
+  
+  testing ssh2
+  creating ssh peer from handshake results
+  i> write(171) -> None:
+  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     hello\n
+  i>     between\n
+  i>     pairs 81\n
+  i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
+  i> flush() -> None
+  o> readline() -> 62:
+  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o> readline() -> 4:
+  o>     383\n
+  o> read(383) -> 383: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN
+  o> read(1) -> 1:
+  o>     \n
+  sending batch with 3 sub-commands
+  i> write(6) -> None:
+  i>     batch\n
+  i> write(4) -> None:
+  i>     * 0\n
+  i> write(8) -> None:
+  i>     cmds 61\n
+  i> write(61) -> None: heads ;listkeys namespace=bookmarks;listkeys namespace=phases
+  i> flush() -> None
+  o> bufferedreadline() -> 4:
+  o>     278\n
+  o> bufferedread(278) -> 278:
+  o>     bfebe6bd38eebc6f8202e419c1171268987ea6a6 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\n
+  o>     ;bookA	4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\n
+  o>     bookB	bfebe6bd38eebc6f8202e419c1171268987ea6a6;4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab	1\n
+  o>     bfebe6bd38eebc6f8202e419c1171268987ea6a6	1\n
+  o>     publishing	True
+  response #0: bfebe6bd38eebc6f8202e419c1171268987ea6a6 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\n
+  response #1: bookA	4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\nbookB	bfebe6bd38eebc6f8202e419c1171268987ea6a6
+  response #2: 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab	1\nbfebe6bd38eebc6f8202e419c1171268987ea6a6	1\npublishing	True