Mailboxes
Introduction
When you send a message to an actor, the message doesn’t go directly to the actor, but goes to the actor’s mailbox until the actor gets time to process it.
The default mailbox consists of two queues of messages: system messages and user messages. The system messages are used internally by the Actor Context to suspend and resume mailbox processing in case of failure. System messages are also used by internally to manage the Actor, e.g. starting, stopping and restarting it. User messages are sent to the actual Actor.
Messages in the mailbox will always be delivered in FIFO order, with one exception: if there are any system messages they will always be processed before any user messages.
Fundamentally, the following rules apply to the actor mailbox:
- Message posting can be done concurrently by multiple senders , aka. MPSC, Multiple Producers, Single Consumer.
- Message receive is done sequentially by the actor. other rules can apply for special mailboxes, e.g. priority mailboxes.
- Mailboxes are never shared between actors
By default, an unbounded mailbox is used, this means any number of messages can be enqueued into the mailbox.
Changing the mailbox
To use a specific mailbox implementation, you can customize the Props:
var props = Actor.FromProducer(() => new MyActor())
.WithMailbox(() => UnboundedMailbox.Create());
props := actor.FromProducer(MyActorProducer)
.WithMailbox(MyMailboxProducer)
Unbounded Mailbox
The unbounded mailbox is a convenient default but in a scenario where messages are added to the mailbox faster than the actor can process them, this can lead to the application running out of memory. For this reason a bounded mailbox can be specified, the bounded mailbox will pass new messages to dead-letters when the mailbox is full.
Bounded Mailbox
Dropping Tail Mailbox
Dropping Head Mailbox
Mailbox Instrumentation
Dispatchers and Invokers
The mailbox requires two handlers to be registered, a dispatcher and an invoker. When an actor is spawned, the invoker will be the actor context, and the dispatcher is taken from the Props.
Mailbox Invoker
When the mailbox pops a message from the queue, it hands over the message to the registered invoker to handle the message. For an actor, the actor’s context will get the message and invoke the actor’s Receive
method for processing.
If an error occurs while the message is being processed, the mailbox will escalate the error to its registered invoker, so that it can take the appropriate action (e.g. restart the actor) and continue if possible.
You can read more on this topic here: Supervision
Mailbox Dispatchers
When the mailbox gets a message, it will schedule itself to process messages that are in the mailbox queues, using the dispatcher.
The dispatcher is responsible for scheduling the processing to be run. The implementation of this varies by platform, e.g. in Go it is a simple invocation of a goroutine, whereas in C# the processing is handled by registering a Task to be run on the thread pool.
The dispatcher is also responsible for limiting the throughput on each mailbox run. The mailbox will pick messages one by one in a single thread. By limiting the throughput of each run, the thread in use can be released so that other mailboxes can get scheduled to run.
There are some other common reasons to select a different dispatcher. These reasons include (but are not limited to):
- isolating one or more actors to specific threads in order to:
- ensure high-load actors don’t starve the system by consuming too much cpu-time;
- ensure important actors always have a dedicated thread to do their job;
- create bulkheads, ensuring problems created in one part of the system do not leak to others;
- allow actors to execute in a specific SyncrhonizationContext;
.NET Specific information
By default, all actors share a singleton ThreadPoolDispatcher
with a throughput of 300 messages per mailbox run.
ThreadPoolDispatcher
The ThreadPoolDispatcher
uses the built-in TaskFactory
to schedule mailbox runs on the standard .NET thread pool.
CurrentSynchronizationContextDispatcher
The CurrentSynchronizationContextDispatcher
works the same way as the ThreadPoolDispatcher
, but uses the current SynchronizationContext
as the TaskScheduler
. This dispatcher is useful if you are running Proto.Actor in a desktop GUI application.
Mailbox Statistics
Mailbox statistics allows the developer to listen to the following mailbox events:
- User messages
- System messages
- Started event
- Empty event
Mailbox statistics provides an extension point to get notifications of the following mailbox events: MailboxStarted, MailboxEmpty, MessagePosted, MessageReceived.
var props = Actor.FromProducer(() => new MyActor())
.WithMailbox(() => UnboundedMailbox.Create(myMailboxStatistics1, myMailboxStatistics2));