tiny-actor
Tiny-actor is a minimal and unopinionated actor framework for Rust.
The main principle of tiny-actor is merging Inboxes with tasks: It's impossible to create an Inbox without a task. Following this principle allows us to buildi simple pools and supervision-trees with reliable shutdown behaviour.
This library will not be trying out any API's similar to Actix's, Instead I'm planning to build another actor-library that will use tiny-actor under the hood.
Concepts
Channel
A Channel is that which couples Inboxes, Addresses and Children together. Every unique Channel contains the following rust-structs:
- A single
Child(Pool). - One or more
Addresses. - One or more
Inboxes.
The following diagram shows a visual representation of the naming used:
|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
| Channel |
| |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯| |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯| |
| | actor | | Child(Pool) | |
| | |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯| | |________________| |
| | | process(es) | | |
| | | |¯¯¯¯¯¯¯¯¯¯| |¯¯¯¯¯¯¯¯¯| | | |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯| |
| | | | task | | Inbox | | | | Address(es) | |
| | | |__________| |_________| | | |________________| |
| | |_____________________________| | |
| |___________________________________| |
|_____________________________________________________________|
Actor
The term actor is used to describe (one or more) processes sharing a single Channel. The actor appears to be functioning as a single unit, and the processes share an Address.
Process
The term process is used to describe the coupling of an Inbox with a task.
Inbox
An Inbox is a receiver to the Channel, and is primarily used to take messages out of the Channel. Inboxes can be created by spawning new processes and should stay coupled to the task they were spawned with: An Inbox should only be dropped when the task is exiting.
Address
An Address is the clone-able sender of a Channel, and is primarily used to send messages to the actor. When all Addresses are dropped, the Channel is closed automatically. Addresses can be awaited, which will wait until the actor has exits.
Child(Pool)
A Child is a handle to an actor with one process. The Child can be awaited to return the exit-value of the task. The Child is not clone-able, and therefore unique to the Channel. When the Child is dropped, the actor will be halted and subsequently aborted. This can be prevented by detaching the Child.
A ChildPool is similar to a Child, except that the actor can have multiple processes. The ChildPool can be streamed to get the exit-values of all spawned tasks. More processes can be spawned after the actor has been spawned, and it's also possible to halt a portion of the processes of the actor.
Closing
When a Channel is closed, it is not longer possible to send new messages into it. It is still possible to take out any messages that are left. The processes of a closed Channel do not have to exit necessarily, but can continue running. Any senders are notified with a SendError::Closed, while receivers will receive RecvError::ClosedAndEmpty once the Channel has been emptied.
Halting
A process can be halted exactly once, by receiving a RecvError::Halted. Afterwards the process should exit. An actor can be partially halted, meaning that only some of the processeses have been halted.
Aborting
An actor can be aborted through tokio's abort method. This causes the tasks to exit abruptly, and can leave bad state behind. Wherever possible, use halt instead of abort. By default processes are automatically aborted when the Child(Pool) is dropped. This can be prevented by detaching the Child(Pool).
Exiting
An exit can refer to two seperate events which, with good practise, always occur at the same time:
- A
processcan exit by dropping it'sInbox. Once allInboxesof aChannelhave been dropped, theactorhasexited. This type of exit can be retrieved from theChannelat any time usinghas_exited. - A
taskcan exit, which means thetaskis no longer alive. This can only be queried only once, by awaiting theChild(Pool)or by callingis_finished.
Therefore, it is recommended to drop an Inbox only when the task is also exiting, this way an exit always refers to the same event.
Link
An actor can either be attached or detached, which indicates what should happen when the Child(Pool) is dropped. If it is attached, then it will automatically halt all processes, and after the abort-timer, all processes will be aborted. If it is detached, then nothing happens when the Child(Pool) is dropped.
Capacity
A Channel can either be bounded or unbounded. A bounded Channel can receive messages until it's capacity has been reached. After reaching the capacity, senders must wait until space is available. An unbounded Channel does not have this limit, but instead applies a backpressure-algorithm: The more messages in the Channel, the longer the sender must wait before it is allowed to send.
Default Config
Attachedwith an abort-timer of1 sec.Unboundedcapacity with BackPressure timeout starting from5 messagesat25nswith anexponentialgrowth-factor of1.3.
Examples
Basic
use *;
use Duration;
async
Pooled with config
use *;
use Duration;
use StreamExt;
async