|
| 1 | +# Multiplexers |
| 2 | + |
| 3 | +**Muxers** (short for multiplexers) form the first level of indirection in the mailbox subsystem. While a `Mailbox` delivers messages to typed ports within a single actor, a `MailboxMuxer` delivers messages to the correct mailbox instance given an `ActorId`. |
| 4 | + |
| 5 | +It acts as a dynamic registry, allowing multiple mailboxes to be addressed through a single posting interface. |
| 6 | + |
| 7 | +This page introduces the `MailboxMuxer` and its role in: |
| 8 | +- Aggregating multiple mailbox instances |
| 9 | +- Dispatching incoming messages to the appropriate `MailboxSender` |
| 10 | +- Supporting dynamic binding and unbinding of mailboxes |
| 11 | + |
| 12 | +Let's begin by looking at the core structure of `MailboxMuxer`: |
| 13 | +```rust |
| 14 | +pub struct MailboxMuxer { |
| 15 | + mailboxes: Arc<DashMap<ActorId, Box<dyn MailboxSender + Send + Sync>>>, |
| 16 | +} |
| 17 | +``` |
| 18 | +The `MailboxMuxer` maintains a thread-safe, concurrent map from `ActorId` to `MailboxSender` trait objects. Each entry represents a live binding to a mailbox capable of receiving messages for a specific actor. This allows the muxer to act as a single dispatch point for delivering messages to any number of registered actors, abstracting over the details of how and where each mailbox is implemented. |
| 19 | + |
| 20 | +To register a mailbox with the muxer, callers use the `bind` method: |
| 21 | +```rust |
| 22 | +impl MailboxMuxer { |
| 23 | + pub fn bind(&self, actor_id: ActorId, sender: impl MailboxSender + 'static) -> bool { |
| 24 | + match self.mailboxes.entry(actor_id) { |
| 25 | + Entry::Occupied(_) => false, |
| 26 | + Entry::Vacant(entry) => { |
| 27 | + entry.insert(Box::new(sender)); |
| 28 | + true |
| 29 | + } |
| 30 | + } |
| 31 | + } |
| 32 | + |
| 33 | +} |
| 34 | +``` |
| 35 | +This function installs a new mapping from the given `ActorId` to a boxed `MailboxSender`. If the `ActorId` is already registered, the bind fails (returns `false`), and the existing sender is left unchanged. This ensures that actors cannot be accidentally rebound without first explicitly unbinding them—enforcing a clear handoff protocol. To rebind, the caller must invoke `unbind` first. |
| 36 | + |
| 37 | +It's crucial to recall that `Mailbox` itself implements the `MailboxSender` trait. This is what allows it to be registered directly into a `MailboxMuxer`. The `post` method of a `Mailbox` inspects the incoming `MessageEnvelope` to determine whether it is the intended recipient. If the `ActorId` in the envelope matches the mailbox's own ID, the mailbox delivers the message locally: it looks up the appropriate port by index and invokes `send_serialized` on the matching channel. If the `ActorId` does *not* match, the mailbox delegates the message to its internal forwarder by calling `self.state.forwarder.post(envelope)`. |
| 38 | + |
| 39 | +With this behavior in mind, we can now define a convenience method for registering a full `Mailbox`: |
| 40 | + |
| 41 | +```rust |
| 42 | +impl MailboxMuxer { |
| 43 | + fn bind_mailbox(&self, mailbox: Mailbox) -> bool { |
| 44 | + self.bind(mailbox.actor_id().clone(), mailbox) |
| 45 | + } |
| 46 | +} |
| 47 | +``` |
| 48 | +To support rebinding or teardown, the muxer also provides a symmetric `unbind` function, which removes the sender associated with a given `ActorId`: |
| 49 | +```rust |
| 50 | + pub(crate) fn unbind(&self, actor_id: &ActorId) { |
| 51 | + self.mailboxes.remove(actor_id); |
| 52 | + } |
| 53 | +``` |
| 54 | +And of course, we can implement `MailboxSender` for `MailboxMuxer` itself—allowing it to act as a unified dispatcher for all registered mailboxes: |
| 55 | +```rust |
| 56 | +impl MailboxSender for MailboxMuxer { |
| 57 | + fn post( |
| 58 | + &self, |
| 59 | + envelope: MessageEnvelope, |
| 60 | + return_handle: PortHandle<Undeliverable<MessageEnvelope>>, |
| 61 | + ) { |
| 62 | + let dest_actor_id = envelope.dest().actor_id(); |
| 63 | + match self.mailboxes.get(envelope.dest().actor_id()) { |
| 64 | + None => { |
| 65 | + let err = format!("no mailbox for actor {} registered in muxer", dest_actor_id); |
| 66 | + envelope.undeliverable(DeliveryError::Unroutable(err), return_handle) |
| 67 | + } |
| 68 | + Some(sender) => sender.post(envelope, return_handle), |
| 69 | + } |
| 70 | + } |
| 71 | +} |
| 72 | +``` |
| 73 | +This makes `MailboxMuxer` composable: it can be nested within other routers, shared across components, or substituted for a standalone mailbox in generic code. If the destination `ActorId` is found in the internal map, the message is forwarded to the corresponding sender. Otherwise, it is marked as undeliverable with an appropriate `DeliveryError`. |
0 commit comments