Mercurial > hg
view mercurial/wireprotov2peer.py @ 39314:7f5e6d3e9032
manifest: proxy to revlog instance instead of inheriting
Previously, manifestrevlog inherited revlog.revlog and therefore
exposed all its APIs. This inevitably resulted in consumers calling
low-level revlog APIs.
As part of abstracting storage, we want to formalize the interface
for manifest storage. The revlog API is much too large to define as
the interface.
Like we did for filelog, this commit divorces the manifest class
from revlog so that we can standardize on a smaller API surface.
The way I went about this commit was I broke the inheritance, ran
tests, and added proxies until all tests passed. Like filelog, there
are a handful of attributes that don't belong on the interface.
And like filelog, we'll tease these out in the future.
As part of this, we formalize an interface for manifest storage and
add checks that manifestrevlog conforms to the interface.
Adding proxies will introduce some overhead due to extra attribute
lookups and function calls. On the mozilla-unified repository:
$ hg verify
before: real 627.220 secs (user 525.870+0.000 sys 18.800+0.000)
after: real 628.930 secs (user 532.050+0.000 sys 18.320+0.000)
$ hg serve (for a clone)
before: user 223.580+0.000 sys 14.270+0.000
after: user 227.720+0.000 sys 13.920+0.000
$ hg clone
before: user 506.390+0.000 sys 29.720+0.000
after: user 513.080+0.000 sys 28.280+0.000
There appears to be some overhead here. But it appears to be 1-2%.
I think that is an appropriate price to pay for storage abstraction,
which will eventually let us have much nicer things. If the overhead
is noticed in other operations (whose CPU time isn't likely dwarfed by
fulltext resolution) or if we want to cut down on the overhead, we
could dynamically build up a type whose methods are effectively
aliased to a revlog instance's. I'm inclined to punt on that problem
for now. We may have to do it for the changelog. At which point it
could be implemented in a generic way and ported to filelog and
manifestrevlog easily enough I would think.
.. api:: manifest.manifestrevlog no longer inherits from revlog
The manifestrevlog class now wraps a revlog instance instead of
inheriting from revlog. Various attributes and methods on instances
are no longer available.
Differential Revision: https://phab.mercurial-scm.org/D4386
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Mon, 27 Aug 2018 10:15:15 -0700 |
parents | 3ea8323d6f95 |
children | 1467b6c27ff9 |
line wrap: on
line source
# wireprotov2peer.py - client side code for wire protocol version 2 # # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import from .i18n import _ from .thirdparty import ( cbor, ) from . import ( encoding, error, util, wireprotoframing, ) def formatrichmessage(atoms): """Format an encoded message from the framing protocol.""" chunks = [] for atom in atoms: msg = _(atom[b'msg']) if b'args' in atom: msg = msg % atom[b'args'] chunks.append(msg) return b''.join(chunks) class commandresponse(object): """Represents the response to a command request.""" def __init__(self, requestid, command): self.requestid = requestid self.command = command self.b = util.bytesio() def cborobjects(self): """Obtain decoded CBOR objects from this response.""" size = self.b.tell() self.b.seek(0) decoder = cbor.CBORDecoder(self.b) while self.b.tell() < size: yield decoder.decode() class clienthandler(object): """Object to handle higher-level client activities. The ``clientreactor`` is used to hold low-level state about the frame-based protocol, such as which requests and streams are active. This type is used for higher-level operations, such as reading frames from a socket, exposing and managing a higher-level primitive for representing command responses, etc. This class is what peers should probably use to bridge wire activity with the higher-level peer API. """ def __init__(self, ui, clientreactor): self._ui = ui self._reactor = clientreactor self._requests = {} self._futures = {} self._responses = {} def callcommand(self, command, args, f): """Register a request to call a command. Returns an iterable of frames that should be sent over the wire. """ request, action, meta = self._reactor.callcommand(command, args) if action != 'noop': raise error.ProgrammingError('%s not yet supported' % action) rid = request.requestid self._requests[rid] = request self._futures[rid] = f self._responses[rid] = commandresponse(rid, command) return iter(()) def flushcommands(self): """Flush all queued commands. Returns an iterable of frames that should be sent over the wire. """ action, meta = self._reactor.flushcommands() if action != 'sendframes': raise error.ProgrammingError('%s not yet supported' % action) return meta['framegen'] def readframe(self, fh): """Attempt to read and process a frame. Returns None if no frame was read. Presumably this means EOF. """ frame = wireprotoframing.readframe(fh) if frame is None: # TODO tell reactor? return self._ui.note(_('received %r\n') % frame) self._processframe(frame) return True def _processframe(self, frame): """Process a single read frame.""" action, meta = self._reactor.onframerecv(frame) if action == 'error': e = error.RepoError(meta['message']) if frame.requestid in self._futures: self._futures[frame.requestid].set_exception(e) else: raise e if frame.requestid not in self._requests: raise error.ProgrammingError( 'received frame for unknown request; this is either a bug in ' 'the clientreactor not screening for this or this instance was ' 'never told about this request: %r' % frame) response = self._responses[frame.requestid] if action == 'responsedata': response.b.write(meta['data']) if meta['eos']: # If the command has a decoder, resolve the future to the # decoded value. Otherwise resolve to the rich response object. decoder = COMMAND_DECODERS.get(response.command) # TODO consider always resolving the overall status map. if decoder: objs = response.cborobjects() overall = next(objs) if overall['status'] == 'ok': self._futures[frame.requestid].set_result(decoder(objs)) else: e = error.RepoError( formatrichmessage(overall['error']['message'])) self._futures[frame.requestid].set_exception(e) else: self._futures[frame.requestid].set_result(response) del self._requests[frame.requestid] del self._futures[frame.requestid] else: raise error.ProgrammingError( 'unhandled action from clientreactor: %s' % action) def decodebranchmap(objs): # Response should be a single CBOR map of branch name to array of nodes. bm = next(objs) return {encoding.tolocal(k): v for k, v in bm.items()} def decodeheads(objs): # Array of node bytestrings. return next(objs) def decodeknown(objs): # Bytestring where each byte is a 0 or 1. raw = next(objs) return [True if c == '1' else False for c in raw] def decodelistkeys(objs): # Map with bytestring keys and values. return next(objs) def decodelookup(objs): return next(objs) def decodepushkey(objs): return next(objs) COMMAND_DECODERS = { 'branchmap': decodebranchmap, 'heads': decodeheads, 'known': decodeknown, 'listkeys': decodelistkeys, 'lookup': decodelookup, 'pushkey': decodepushkey, }