diff -r ed919b90acda -r c537144fdbef mercurial/repository.py --- a/mercurial/repository.py Wed Sep 26 17:16:27 2018 -0700 +++ b/mercurial/repository.py Wed Sep 26 17:16:56 2018 -0700 @@ -1657,3 +1657,160 @@ class completelocalrepository(ilocalrepositorymain, ilocalrepositoryfilestorage): """Complete interface for a local repository.""" + +class iwireprotocolcommandcacher(interfaceutil.Interface): + """Represents a caching backend for wire protocol commands. + + Wire protocol version 2 supports transparent caching of many commands. + To leverage this caching, servers can activate objects that cache + command responses. Objects handle both cache writing and reading. + This interface defines how that response caching mechanism works. + + Wire protocol version 2 commands emit a series of objects that are + serialized and sent to the client. The caching layer exists between + the invocation of the command function and the sending of its output + objects to an output layer. + + Instances of this interface represent a binding to a cache that + can serve a response (in place of calling a command function) and/or + write responses to a cache for subsequent use. + + When a command request arrives, the following happens with regards + to this interface: + + 1. The server determines whether the command request is cacheable. + 2. If it is, an instance of this interface is spawned. + 3. The cacher is activated in a context manager (``__enter__`` is called). + 4. A cache *key* for that request is derived. This will call the + instance's ``adjustcachekeystate()`` method so the derivation + can be influenced. + 5. The cacher is informed of the derived cache key via a call to + ``setcachekey()``. + 6. The cacher's ``lookup()`` method is called to test for presence of + the derived key in the cache. + 7. If ``lookup()`` returns a hit, that cached result is used in place + of invoking the command function. ``__exit__`` is called and the instance + is discarded. + 8. The command function is invoked. + 9. ``onobject()`` is called for each object emitted by the command + function. + 10. After the final object is seen, ``onoutputfinished()`` is called. + 11. ``__exit__`` is called to signal the end of use of the instance. + + Cache *key* derivation can be influenced by the instance. + + Cache keys are initially derived by a deterministic representation of + the command request. This includes the command name, arguments, protocol + version, etc. This initial key derivation is performed by CBOR-encoding a + data structure and feeding that output into a hasher. + + Instances of this interface can influence this initial key derivation + via ``adjustcachekeystate()``. + + The instance is informed of the derived cache key via a call to + ``setcachekey()``. The instance must store the key locally so it can + be consulted on subsequent operations that may require it. + + When constructed, the instance has access to a callable that can be used + for encoding response objects. This callable receives as its single + argument an object emitted by a command function. It returns an iterable + of bytes chunks representing the encoded object. Unless the cacher is + caching native Python objects in memory or has a way of reconstructing + the original Python objects, implementations typically call this function + to produce bytes from the output objects and then store those bytes in + the cache. When it comes time to re-emit those bytes, they are wrapped + in a ``wireprototypes.encodedresponse`` instance to tell the output + layer that they are pre-encoded. + + When receiving the objects emitted by the command function, instances + can choose what to do with those objects. The simplest thing to do is + re-emit the original objects. They will be forwarded to the output + layer and will be processed as if the cacher did not exist. + + Implementations could also choose to not emit objects - instead locally + buffering objects or their encoded representation. They could then emit + a single "coalesced" object when ``onoutputfinished()`` is called. In + this way, the implementation would function as a filtering layer of + sorts. + + When caching objects, typically the encoded form of the object will + be stored. Keep in mind that if the original object is forwarded to + the output layer, it will need to be encoded there as well. For large + output, this redundant encoding could add overhead. Implementations + could wrap the encoded object data in ``wireprototypes.encodedresponse`` + instances to avoid this overhead. + """ + def __enter__(): + """Marks the instance as active. + + Should return self. + """ + + def __exit__(exctype, excvalue, exctb): + """Called when cacher is no longer used. + + This can be used by implementations to perform cleanup actions (e.g. + disconnecting network sockets, aborting a partially cached response. + """ + + def adjustcachekeystate(state): + """Influences cache key derivation by adjusting state to derive key. + + A dict defining the state used to derive the cache key is passed. + + Implementations can modify this dict to record additional state that + is wanted to influence key derivation. + + Implementations are *highly* encouraged to not modify or delete + existing keys. + """ + + def setcachekey(key): + """Record the derived cache key for this request. + + Instances may mutate the key for internal usage, as desired. e.g. + instances may wish to prepend the repo name, introduce path + components for filesystem or URL addressing, etc. Behavior is up to + the cache. + + Returns a bool indicating if the request is cacheable by this + instance. + """ + + def lookup(): + """Attempt to resolve an entry in the cache. + + The instance is instructed to look for the cache key that it was + informed about via the call to ``setcachekey()``. + + If there's no cache hit or the cacher doesn't wish to use the cached + entry, ``None`` should be returned. + + Else, a dict defining the cached result should be returned. The + dict may have the following keys: + + objs + An iterable of objects that should be sent to the client. That + iterable of objects is expected to be what the command function + would return if invoked or an equivalent representation thereof. + """ + + def onobject(obj): + """Called when a new object is emitted from the command function. + + Receives as its argument the object that was emitted from the + command function. + + This method returns an iterator of objects to forward to the output + layer. The easiest implementation is a generator that just + ``yield obj``. + """ + + def onfinished(): + """Called after all objects have been emitted from the command function. + + Implementations should return an iterator of objects to forward to + the output layer. + + This method can be a generator. + """