repository: define new interface for running commands
authorGregory Szorc <gregory.szorc@gmail.com>
Fri, 13 Apr 2018 10:23:05 -0700
changeset 37629 fa0382088993
parent 37628 8da30ceae88f
child 37630 e1b32dc4646c
repository: define new interface for running commands Today, the peer interface exposes methods for each command that can be executed. In addition, there is an iterbatch() API that allows commands to be issued in batches and provides an iterator over the results. This is a glorified wrapper around the "batch" wire command. Wire protocol version 2 supports nicer things (such as batching any command and out-of-order replies). It will require a more flexible API for executing commands. This commit introduces a new peer interface for making command requests. In the new world, you can't simply call a method on the peer to execute a command: you need to obtain an object to be used for executing commands. That object can be used to issue a single command or it can batch multiple requests. In the case of full duplex peers, the command may even be sent out over the wire immediately. There are no per-command methods. Instead, there is a generic method to call a command. The implementation can then perform domain specific processing for specific commands. This includes passing data via a specially named argument. Arguments are also passed as a dictionary instead of using **kwargs. While **kwargs is nicer to use, we've historically gotten into trouble using it because there will inevitably be a conflict between the name of an argument to a wire protocol command and an argument we want to pass into a function. Instead of a command returning a value, it returns a future which will resolve to a value. This opens the door for out-of-order response handling and concurrent response handling in the version 2 protocol. Differential Revision: https://phab.mercurial-scm.org/D3267
mercurial/repository.py
--- a/mercurial/repository.py	Mon Apr 09 12:28:57 2018 -0700
+++ b/mercurial/repository.py	Fri Apr 13 10:23:05 2018 -0700
@@ -196,6 +196,88 @@
     def changegroupsubset(bases, heads, kind):
         pass
 
+class ipeercommandexecutor(zi.Interface):
+    """Represents a mechanism to execute remote commands.
+
+    This is the primary interface for requesting that wire protocol commands
+    be executed. Instances of this interface are active in a context manager
+    and have a well-defined lifetime. When the context manager exits, all
+    outstanding requests are waited on.
+    """
+
+    def callcommand(name, args):
+        """Request that a named command be executed.
+
+        Receives the command name and a dictionary of command arguments.
+
+        Returns a ``concurrent.futures.Future`` that will resolve to the
+        result of that command request. That exact value is left up to
+        the implementation and possibly varies by command.
+
+        Not all commands can coexist with other commands in an executor
+        instance: it depends on the underlying wire protocol transport being
+        used and the command itself.
+
+        Implementations MAY call ``sendcommands()`` automatically if the
+        requested command can not coexist with other commands in this executor.
+
+        Implementations MAY call ``sendcommands()`` automatically when the
+        future's ``result()`` is called. So, consumers using multiple
+        commands with an executor MUST ensure that ``result()`` is not called
+        until all command requests have been issued.
+        """
+
+    def sendcommands():
+        """Trigger submission of queued command requests.
+
+        Not all transports submit commands as soon as they are requested to
+        run. When called, this method forces queued command requests to be
+        issued. It will no-op if all commands have already been sent.
+
+        When called, no more new commands may be issued with this executor.
+        """
+
+    def close():
+        """Signal that this command request is finished.
+
+        When called, no more new commands may be issued. All outstanding
+        commands that have previously been issued are waited on before
+        returning. This not only includes waiting for the futures to resolve,
+        but also waiting for all response data to arrive. In other words,
+        calling this waits for all on-wire state for issued command requests
+        to finish.
+
+        When used as a context manager, this method is called when exiting the
+        context manager.
+
+        This method may call ``sendcommands()`` if there are buffered commands.
+        """
+
+class ipeerrequests(zi.Interface):
+    """Interface for executing commands on a peer."""
+
+    def commandexecutor():
+        """A context manager that resolves to an ipeercommandexecutor.
+
+        The object this resolves to can be used to issue command requests
+        to the peer.
+
+        Callers should call its ``callcommand`` method to issue command
+        requests.
+
+        A new executor should be obtained for each distinct set of commands
+        (possibly just a single command) that the consumer wants to execute
+        as part of a single operation or round trip. This is because some
+        peers are half-duplex and/or don't support persistent connections.
+        e.g. in the case of HTTP peers, commands sent to an executor represent
+        a single HTTP request. While some peers may support multiple command
+        sends over the wire per executor, consumers need to code to the least
+        capable peer. So it should be assumed that command executors buffer
+        called commands until they are told to send them and that each
+        command executor could result in a new connection or wire-level request
+        being issued.
+        """
+
 class ipeerbase(ipeerconnection, ipeercapabilities, ipeercommands):
     """Unified interface for peer repositories.