##// END OF EJS Templates
wireproto: add streams to frame-based protocol...
wireproto: add streams to frame-based protocol Previously, the frame-based protocol was just a series of frames, with each frame associated with a request ID. In order to scale the protocol, we'll want to enable the use of compression. While it is possible to enable compression at the socket/pipe level, this has its disadvantages. The big one is it undermines the point of frames being standalone, atomic units that can be read and written: if you add compression above the framing protocol, you are back to having a stream-based protocol as opposed to something frame-based. So in order to preserve frames, compression needs to occur at the frame payload level. Compressing each frame's payload individually will limit compression ratios because the window size of the compressor will be limited by the max frame size, which is 32-64kb as currently defined. It will also add CPU overhead, as it is more efficient for compressors to operate on fewer, larger blocks of data than more, smaller blocks. So compressing each frame independently is out. This means we need to compress each frame's payload as if it is part of a larger stream. The simplest approach is to have 1 stream per connection. This could certainly work. However, it has disadvantages (documented below). We could also have 1 stream per RPC/command invocation. (This is the model HTTP/2 goes with.) This also has disadvantages. The main disadvantage to one global stream is that it has the very real potential to create CPU bottlenecks doing compression. Networks are only getting faster and the performance of single CPU cores has been relatively flat. Newer compression formats like zstandard offer better CPU cycle efficiency than predecessors like zlib. But it still all too common to saturate your CPU with compression overhead long before you saturate the network pipe. The main disadvantage with streams per request is that you can't reap the benefits of the compression context for multiple requests. For example, if you send 1000 RPC requests (or HTTP/2 requests for that matter), the response to each would have its own compression context. The overall size of the raw responses would be larger because compression contexts wouldn't be able to reference data from another request or response. The approach for streams as implemented in this commit is to support N streams per connection and for streams to potentially span requests and responses. As explained by the added internals docs, this facilitates servers and clients delegating independent streams and compression to independent threads / CPU cores. This helps alleviate the CPU bottleneck of compression. This design also allows compression contexts to be reused across requests/responses. This can result in improved compression ratios and less overhead for compressors and decompressors having to build new contexts. Another feature that was defined was the ability for individual frames within a stream to declare whether that individual frame's payload uses the content encoding (read: compression) defined by the stream. The idea here is that some servers may serve data from a combination of caches and dynamic resolution. Data coming from caches may be pre-compressed. We want to facilitate servers being able to essentially stream bytes from caches to the wire with minimal overhead. Being able to mix and match with frames are compressed within a stream enables these types of advanced server functionality. This commit defines the new streams mechanism. Basic code for supporting streams in frames has been added. But that code is seriously lacking and doesn't fully conform to the defined protocol. For example, we don't close any streams. And support for content encoding within streams is not yet implemented. The change was rather invasive and I didn't think it would be reasonable to implement the entire feature in a single commit. For the record, I would have loved to reuse an existing multiplexing protocol to build the new wire protocol on top of. However, I couldn't find a protocol that offers the performance and scaling characteristics that I desired. Namely, it should support multiple compression contexts to facilitate scaling out to multiple CPU cores and compression contexts should be able to live longer than single RPC requests. HTTP/2 *almost* fits the bill. But the semantics of HTTP message exchange state that streams can only live for a single request-response. We /could/ tunnel on top of HTTP/2 streams and frames with HEADER and DATA frames. But there's no guarantee that HTTP/2 libraries and proxies would allow us to use HTTP/2 streams and frames without the HTTP message exchange semantics defined in RFC 7540 Section 8. Other RPC protocols like gRPC tunnel are built on top of HTTP/2 and thus preserve its semantics of stream per RPC invocation. Even QUIC does this. We could attempt to invent a higher-level stream that spans HTTP/2 streams. But this would be violating HTTP/2 because there is no guarantee that HTTP/2 streams are routed to the same server. The best we can do - which is what this protocol does - is shoehorn all request and response data into a single HTTP message and create streams within. At that point, we've defined a Content-Type in HTTP parlance. It just so happens our media type can also work as a standalone, stream-based protocol, without leaning on HTTP or similar protocol. Differential Revision: https://phab.mercurial-scm.org/D2907

File last commit:

r34950:ff178743 stable
r37304:9bfcbe4f default
Show More
revisions.txt
223 lines | 6.8 KiB | text/plain | TextLexer
Mercurial supports several ways to specify revisions.
Specifying single revisions
===========================
A plain integer is treated as a revision number. Negative integers are
treated as sequential offsets from the tip, with -1 denoting the tip,
-2 denoting the revision prior to the tip, and so forth.
A 40-digit hexadecimal string is treated as a unique revision identifier.
A hexadecimal string less than 40 characters long is treated as a
unique revision identifier and is referred to as a short-form
identifier. A short-form identifier is only valid if it is the prefix
of exactly one full-length identifier.
Any other string is treated as a bookmark, tag, or branch name. A
bookmark is a movable pointer to a revision. A tag is a permanent name
associated with a revision. A branch name denotes the tipmost open branch head
of that branch - or if they are all closed, the tipmost closed head of the
branch. Bookmark, tag, and branch names must not contain the ":" character.
The reserved name "tip" always identifies the most recent revision.
The reserved name "null" indicates the null revision. This is the
revision of an empty repository, and the parent of revision 0.
The reserved name "." indicates the working directory parent. If no
working directory is checked out, it is equivalent to null. If an
uncommitted merge is in progress, "." is the revision of the first
parent.
Finally, commands that expect a single revision (like ``hg update``) also
accept revsets (see below for details). When given a revset, they use the
last revision of the revset. A few commands accept two single revisions
(like ``hg diff``). When given a revset, they use the first and the last
revisions of the revset.
Specifying multiple revisions
=============================
Mercurial supports a functional language for selecting a set of
revisions. Expressions in this language are called revsets.
The language supports a number of predicates which are joined by infix
operators. Parenthesis can be used for grouping.
Identifiers such as branch names may need quoting with single or
double quotes if they contain characters like ``-`` or if they match
one of the predefined predicates.
Special characters can be used in quoted identifiers by escaping them,
e.g., ``\n`` is interpreted as a newline. To prevent them from being
interpreted, strings can be prefixed with ``r``, e.g. ``r'...'``.
Operators
=========
There is a single prefix operator:
``not x``
Changesets not in x. Short form is ``! x``.
These are the supported infix operators:
``x::y``
A DAG range, meaning all changesets that are descendants of x and
ancestors of y, including x and y themselves. If the first endpoint
is left out, this is equivalent to ``ancestors(y)``, if the second
is left out it is equivalent to ``descendants(x)``.
An alternative syntax is ``x..y``.
``x:y``
All changesets with revision numbers between x and y, both
inclusive. Either endpoint can be left out, they default to 0 and
tip.
``x and y``
The intersection of changesets in x and y. Short form is ``x & y``.
``x or y``
The union of changesets in x and y. There are two alternative short
forms: ``x | y`` and ``x + y``.
``x - y``
Changesets in x but not in y.
``x % y``
Changesets that are ancestors of x but not ancestors of y (i.e. ::x - ::y).
This is shorthand notation for ``only(x, y)`` (see below). The second
argument is optional and, if left out, is equivalent to ``only(x)``.
``x^n``
The nth parent of x, n == 0, 1, or 2.
For n == 0, x; for n == 1, the first parent of each changeset in x;
for n == 2, the second parent of changeset in x.
``x~n``
The nth first ancestor of x; ``x~0`` is x; ``x~3`` is ``x^^^``.
For n < 0, the nth unambiguous descendent of x.
``x ## y``
Concatenate strings and identifiers into one string.
All other prefix, infix and postfix operators have lower priority than
``##``. For example, ``a1 ## a2~2`` is equivalent to ``(a1 ## a2)~2``.
For example::
[revsetalias]
issue(a1) = grep(r'\bissue[ :]?' ## a1 ## r'\b|\bbug\(' ## a1 ## r'\)')
``issue(1234)`` is equivalent to
``grep(r'\bissue[ :]?1234\b|\bbug\(1234\)')``
in this case. This matches against all of "issue 1234", "issue:1234",
"issue1234" and "bug(1234)".
There is a single postfix operator:
``x^``
Equivalent to ``x^1``, the first parent of each changeset in x.
Patterns
========
Where noted, predicates that perform string matching can accept a pattern
string. The pattern may be either a literal, or a regular expression. If the
pattern starts with ``re:``, the remainder of the pattern is treated as a
regular expression. Otherwise, it is treated as a literal. To match a pattern
that actually starts with ``re:``, use the prefix ``literal:``.
Matching is case-sensitive, unless otherwise noted. To perform a case-
insensitive match on a case-sensitive predicate, use a regular expression,
prefixed with ``(?i)``.
For example, ``tag(r're:(?i)release')`` matches "release" or "RELEASE"
or "Release", etc.
Predicates
==========
The following predicates are supported:
.. predicatesmarker
Aliases
=======
New predicates (known as "aliases") can be defined, using any combination of
existing predicates or other aliases. An alias definition looks like::
<alias> = <definition>
in the ``revsetalias`` section of a Mercurial configuration file. Arguments
of the form `a1`, `a2`, etc. are substituted from the alias into the
definition.
For example,
::
[revsetalias]
h = heads()
d(s) = sort(s, date)
rs(s, k) = reverse(sort(s, k))
defines three aliases, ``h``, ``d``, and ``rs``. ``rs(0:tip, author)`` is
exactly equivalent to ``reverse(sort(0:tip, author))``.
Equivalents
===========
Command line equivalents for :hg:`log`::
-f -> ::.
-d x -> date(x)
-k x -> keyword(x)
-m -> merge()
-u x -> user(x)
-b x -> branch(x)
-P x -> !::x
-l x -> limit(expr, x)
Examples
========
Some sample queries:
- Changesets on the default branch::
hg log -r "branch(default)"
- Changesets on the default branch since tag 1.5 (excluding merges)::
hg log -r "branch(default) and 1.5:: and not merge()"
- Open branch heads::
hg log -r "head() and not closed()"
- Changesets between tags 1.3 and 1.5 mentioning "bug" that affect
``hgext/*``::
hg log -r "1.3::1.5 and keyword(bug) and file('hgext/*')"
- Changesets committed in May 2008, sorted by user::
hg log -r "sort(date('May 2008'), user)"
- Changesets mentioning "bug" or "issue" that are not in a tagged
release::
hg log -r "(keyword(bug) or keyword(issue)) and not ancestors(tag())"
- Update to the commit that bookmark @ is pointing to, without activating the
bookmark (this works because the last revision of the revset is used)::
hg update :@
- Show diff between tags 1.3 and 1.5 (this works because the first and the
last revisions of the revset are used)::
hg diff -r 1.3::1.5