mercurial/peer.py
author Gregory Szorc <gregory.szorc@gmail.com>
Thu, 01 Mar 2018 08:24:54 -0800
changeset 36557 72e487851a53
parent 34733 115efdd97088
permissions -rw-r--r--
debugcommands: add debugwireproto command We currently don't have a low-level mechanism for sending arbitrary wire protocol commands. Having a generic and robust mechanism for sending wire protocol commands, examining wire data, etc would make it vastly easier to test the wire protocol and debug server operation. This is a problem I've wanted a solution for numerous times, especially recently as I've been hacking on a new version of the wire protocol. This commit establishes a `hg debugwireproto` command for sending data to a peer. The command invents a mini language for specifying actions to take. This will enable a lot of flexibility for issuing commands and testing variations for how commands are sent. Right now, we only support low-level raw sends and receives. These are probably the least valuable commands to intended users of this command. But they are the most useful commands to implement to bootstrap the feature (I've chosen to reimplement test-ssh-proto.t using this command to prove its usefulness). My eventual goal of `hg debugwireproto` is to allow calling wire protocol commands with a human-friendly interface. Essentially, people can type in a command name and arguments and `hg debugwireproto` will figure out how to send that on the wire. I'd love to eventually be able to save the server's raw response to a file. This would allow us to e.g. call "getbundle" wire protocol commands easily. test-ssh-proto.t has been updated to use the new command in lieu of piping directly to a server process. As part of the transition, test behavior improved. Before, we piped all request data to the server at once. Now, we have explicit control over the ordering of operations. e.g. we can send one command, receive its response, then send another command. This will allow us to more robustly test race conditions, buffering behavior, etc. There were some subtle changes in test behavior. For example, previous behavior would often send trailing newlines to the server. The new mechanism doesn't treat literal newlines specially and requires newlines be escaped in the payload. Because the new logging code is very low level, it is easy to introduce race conditions in tests. For example, the number of bytes returned by a read() may vary depending on load. This is why tests make heavy use of "readline" for consuming data: the result of that operation should be deterministic and not subject to race conditions. There are still some uses of "readavailable." However, those are only for reading from stderr. I was able to reproduce timing issues with my system under load when using "readavailable" globally. But if I "readline" to grab stdout, "readavailable" appears to work deterministically for stderr. I think this is because the server writes to stderr first. As long as the OS delivers writes to pipes in the same order they were made, this should work. If there are timing issues, we can introduce a mechanism to readline from stderr. Differential Revision: https://phab.mercurial-scm.org/D2392
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
17192
1ac628cd7113 peer: introduce real peer classes
Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
parents: 17191
diff changeset
     1
# peer.py - repository base classes for mercurial
1089
142b5d5ec9cc Break apart hg.py
mpm@selenic.com
parents:
diff changeset
     2
#
4635
63b9d2deed48 Updated copyright notices and add "and others" to "hg version"
Thomas Arendsen Hein <thomas@intevation.de>
parents: 3930
diff changeset
     3
# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
2859
345bac2bc4ec update copyrights.
Vadim Gelfer <vadim.gelfer@gmail.com>
parents: 2612
diff changeset
     4
# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
1089
142b5d5ec9cc Break apart hg.py
mpm@selenic.com
parents:
diff changeset
     5
#
8225
46293a0c7e9f updated license to be explicit about GPL version 2
Martin Geisler <mg@lazybytes.net>
parents: 7873
diff changeset
     6
# This software may be used and distributed according to the terms of the
10263
25e572394f5c Update license to GPLv2+
Matt Mackall <mpm@selenic.com>
parents: 9436
diff changeset
     7
# GNU General Public License version 2 or any later version.
1089
142b5d5ec9cc Break apart hg.py
mpm@selenic.com
parents:
diff changeset
     8
25965
e6b56b2c1f26 peer: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25912
diff changeset
     9
from __future__ import absolute_import
e6b56b2c1f26 peer: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25912
diff changeset
    10
e6b56b2c1f26 peer: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25912
diff changeset
    11
from . import (
e6b56b2c1f26 peer: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25912
diff changeset
    12
    error,
34733
115efdd97088 peer: ensure command names are always ascii bytestrs
Augie Fackler <augie@google.com>
parents: 34727
diff changeset
    13
    pycompat,
25965
e6b56b2c1f26 peer: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25912
diff changeset
    14
    util,
e6b56b2c1f26 peer: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25912
diff changeset
    15
)
25912
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    16
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    17
# abstract batching support
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    18
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    19
class future(object):
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    20
    '''placeholder for a value to be set later'''
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    21
    def set(self, value):
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    22
        if util.safehasattr(self, 'value'):
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    23
            raise error.RepoError("future is already set")
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    24
        self.value = value
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    25
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    26
class batcher(object):
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    27
    '''base class for batches of commands submittable in a single request
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    28
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    29
    All methods invoked on instances of this class are simply queued and
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    30
    return a a future for the result. Once you call submit(), all the queued
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    31
    calls are performed and the results set in their respective futures.
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    32
    '''
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    33
    def __init__(self):
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    34
        self.calls = []
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    35
    def __getattr__(self, name):
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    36
        def call(*args, **opts):
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    37
            resref = future()
34727
a652b7763f66 peer: when collecting method names for batch calls, bytes-ify __name__
Augie Fackler <augie@google.com>
parents: 34693
diff changeset
    38
            # Please don't invent non-ascii method names, or you will
a652b7763f66 peer: when collecting method names for batch calls, bytes-ify __name__
Augie Fackler <augie@google.com>
parents: 34693
diff changeset
    39
            # give core hg a very sad time.
a652b7763f66 peer: when collecting method names for batch calls, bytes-ify __name__
Augie Fackler <augie@google.com>
parents: 34693
diff changeset
    40
            self.calls.append((name.encode('ascii'), args, opts, resref,))
25912
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    41
            return resref
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    42
        return call
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    43
    def submit(self):
28434
d549cbb5503d peer: raise NotImplementedError for abstract submit() method
Augie Fackler <augie@google.com>
parents: 25965
diff changeset
    44
        raise NotImplementedError()
25912
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    45
28436
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    46
class iterbatcher(batcher):
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    47
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    48
    def submit(self):
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    49
        raise NotImplementedError()
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    50
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    51
    def results(self):
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    52
        raise NotImplementedError()
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    53
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    54
class localiterbatcher(iterbatcher):
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    55
    def __init__(self, local):
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    56
        super(iterbatcher, self).__init__()
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    57
        self.local = local
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    58
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    59
    def submit(self):
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    60
        # submit for a local iter batcher is a noop
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    61
        pass
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    62
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    63
    def results(self):
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    64
        for name, args, opts, resref in self.calls:
33787
4c706037adef wireproto: overhaul iterating batcher code (API)
Gregory Szorc <gregory.szorc@gmail.com>
parents: 33786
diff changeset
    65
            resref.set(getattr(self.local, name)(*args, **opts))
4c706037adef wireproto: overhaul iterating batcher code (API)
Gregory Szorc <gregory.szorc@gmail.com>
parents: 33786
diff changeset
    66
            yield resref.value
28436
8d38eab2777a peer: add an iterbatcher interface
Augie Fackler <augie@google.com>
parents: 28434
diff changeset
    67
25912
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    68
def batchable(f):
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    69
    '''annotation for batchable methods
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    70
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    71
    Such methods must implement a coroutine as follows:
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    72
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    73
    @batchable
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    74
    def sample(self, one, two=None):
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    75
        # Build list of encoded arguments suitable for your wire protocol:
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    76
        encargs = [('one', encode(one),), ('two', encode(two),)]
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    77
        # Create future for injection of encoded result:
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    78
        encresref = future()
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    79
        # Return encoded arguments and future:
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    80
        yield encargs, encresref
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    81
        # Assuming the future to be filled with the result from the batched
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    82
        # request now. Decode it:
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    83
        yield decode(encresref.value)
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    84
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    85
    The decorator returns a function which wraps this coroutine as a plain
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    86
    method, but adds the original method as an attribute called "batchable",
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    87
    which is used by remotebatch to split the call into separate encoding and
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    88
    decoding phases.
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    89
    '''
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    90
    def plain(*args, **opts):
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    91
        batchable = f(*args, **opts)
29216
ead25aa27a43 py3: convert to next() function
timeless <timeless@mozdev.org>
parents: 28436
diff changeset
    92
        encargsorres, encresref = next(batchable)
25912
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    93
        if not encresref:
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    94
            return encargsorres # a local result in this case
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    95
        self = args[0]
34733
115efdd97088 peer: ensure command names are always ascii bytestrs
Augie Fackler <augie@google.com>
parents: 34727
diff changeset
    96
        cmd = pycompat.bytesurl(f.__name__)  # ensure cmd is ascii bytestr
115efdd97088 peer: ensure command names are always ascii bytestrs
Augie Fackler <augie@google.com>
parents: 34727
diff changeset
    97
        encresref.set(self._submitone(cmd, encargsorres))
29216
ead25aa27a43 py3: convert to next() function
timeless <timeless@mozdev.org>
parents: 28436
diff changeset
    98
        return next(batchable)
25912
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
    99
    setattr(plain, 'batchable', f)
cbbdd085c991 batching: migrate basic noop batching into peer.peer
Augie Fackler <augie@google.com>
parents: 17273
diff changeset
   100
    return plain