mojo/public/rust/bindings/multiplex_router/README.md
This directory contains the Rust implementation of the MultiplexRouter type,
which is used to support associated interfaces.
This type is analogous to the C++ type of the same name,
but was designed and implemented from scratch. Its goal is to have the same
observable behavior as the C++ version, but its internals may work differently.
This directory exists to explain the implementation details of multiplex routers and associated interfaces. For usage examples, see the higher-level Rust Mojo docs.
The MultiplexRouter code is a submodule of the bindings crate because it is
fairly complex, but provides a relatively small API to the rest of the crate. We
don't split it into a separate crate because it's useful to allow circularity
between it and the rest of the bindings crate (e.g. the Registrar trait).
[TOC]
Note that there are definitions of common terms later in this section.
Regular Remotes and Receivers each hold one end of a message pipe. When they
are bound to a sequence, they create a MessagePipeWatcher which listens for
incoming messages on that pipe, and tell it to invoke a specific member function
("handler") when a message comes in. They can also specify a disconnect handler,
which will get run if the pipe is closed (meaning the other end was dropped).
Associated remotes and receivers provide exactly the same user interface (they
can be bound to a sequence, send and receive message, etc), but don't own their
own message pipe. Instead, they associate themselves with an existing
Remote/Receiver pair, and send their messages on that pair's pipe. The most
important property is that they guarantee that all messages on a given pipe will
be processed in the order they were sent. Therefore, association allows multiple
pairs of endpoints to communicate in a consistent manner.
The association step happens when one half of the associated pair is sent via
a Mojo message as a pending_associated_remote or _receiver. When that
occurs, both halves of the pair magically become associated with the pipe that
sends the message.
The existence of associated endpoints complicates the messaging process.
Normally, each message pipe sends messages for a single Mojom interface, and
has only one remote/receiver on each end, so the destination and message
type are unambiguous. Now there are multiple remotes and/or receivers per
end, and they might be sending different kinds of messages from each other, so
we need a way to distinguish between them.
This is the job of the MultiplexRouter. The router replaces the
MessagePipeWatcher for both primary and associated remotes and receivers. Each
end of each message pipe will have exactly one MultiplexRouter, which watches
for incoming messages. Like before, each remote and receiver on that end tells
the router which handlers to run when a message is destined for them, and also
which sequence to run it on (different endpoints may be bound to different
sequences).
It does this by assigning each remote/receiver pair an interface ID. Both halves of the pair will always have the same ID on both sides of the pipe. The router includes the ID s part of the header of each Mojom message, which allows the router on the other end to determine which endpoint should receive it (by invoking that endpoint's handler).
The first pair, which actually owns the pipe, get assigned interface ID 0, which is called the primary interface. Every subsequence interface ID is an associated interface.
The router is also responsible for ensuring that incoming messages are processed in FIFO order. This is typically easy -- just pass along each message as it comes in -- but with the catch that endpoints which aren't yet bound can't process messages. If a message arrives for an unbound endpoint, all incoming message will have to wait until that endpoint is either bound or dropped.
In order to make this easier, the multiplex router gives out a
MultiplexRouterHandle to each of its endpoints, which is just a reference to
the underlying router with an interface ID baked in.
This section tries to precisely define some common terms. Unfortunately, due to history and similar common meanings, some terms are overloaded. For terms where one meaning is much more common in the context of associated interfaces, that meaning is bolded.
interface
keyword. Each remote/receiver specifies a mojom interface, which determines
which messages it can send.SequencedTaskRunner which executes tasks in the order they were posted.
Each endpoint must be bound to one sequence; the same sequence can be used
for multiple endpoints (and arbitrary unrelated tasks) simultaneously.MultiplexRouter architectureThe MultiplexRouter class has two parts: its internal data (mutable,
thread-safe, shared), plus an underlying message pipe (immutable, owned by
the primary endpoint).
EndpointRegistryThe core of a MultiplexRouter is its EndpointRegistry. This is a map from
interface IDs to information about the endpoint with that ID (EndpointInfo).
Before an endpoint is bound, the registry maps its ID to None. After its is
bound, the EndpointInfo tracks:
The registry is also responsible for generating new interface IDs whenever a new endpoint pair is registered. In order to prevent collisions, one side of the pipe always generates IDs with the high bit set to 1, and the other generates IDs with the high bit set to 0.
Each MultiplexRouter maintains a queue of incoming Tasks (messages or
disconnect notifications) in order to provide FIFO ordering. It is only used
to maintain FIFO ordering if the router receives a task for an unbound endpoint.
In that case, it needs to buffer all incoming tasks until the endpoint is ready
to process messages.
The router checks the task queue whenever a message comes in, a new endpoint is bound, or it receives a disconnect notification.
MessagePipeWatcherThis wraps the actual message pipe endpoint, and invokes the router's handler whenever a notification arrives at the pipe. The router's handler simply does a lookup in the endpoint registry, then either schedules the task or queues it as appropriate.
Unlike the other two components, the watcher (and its underlying pipe endpoint) is owned only by the primary endpoint. If an associated endpoint is dropped, any other endpoints on that end can continue communicating normally. If the primary endpoint is dropped, then all other endpoints become disconnected.
However, ArcOrWeak class exists to abstract over this distinction and allow
all the router functions to work regardless of whether the watcher is owned or
not.
ResponseSenderThe handlers which remotes and receivers provide to the router do not generally
contain a reference to the entire Remote or Receiver object, to avoid ref
cycles. Therefore, they need a way to send a response, as well as a way to
register any new associated endpoints that they send or receive from within a
handler.
The ResponseSender class provides these capabilities; it's essentially a
reference to the underlying router, with a more restricted API. A
ResponseSender is passed as an argument to each remote/receiver handler that
the router invokes.
MultiplexRouterHandleA MultiplexRouterHandle provides the public API of MultiplexRouter. Remotes
and receivers don't touch the router directly; instead, they interact with it
through a handle, which is just a combination of a reference to a router and
an interface ID. The handle can be used both for sending messages, and for
registering new associated endpoints.