3 unstable releases
0.2.1 | Mar 13, 2020 |
---|---|
0.2.0 | Mar 5, 2020 |
0.1.0 | Mar 4, 2020 |
#184 in #router
64KB
433 lines
herbert
A simple actor framework in Rust built around centralized message routers.
herbert
supports a program architecture pattern consisting of well-defined
and long-lived threads that maintain their own state and interact with one
another through messages sent over channels.
The problem with this approach is keeping track of all of the channels,
propagating them amongst arbitrary threads, and rationalizing about their
lifecycles. herbert
addresses these challenges using a centralized message
router. The router is responsible for spawning actors upon request, monitoring
and responding to changes in their state, and routing messages to and between
them.
The name was inspired by this epic review of Taco Bell Cantina:
Justice for Herbert!
lib.rs
:
Management for actor services and message routing.
An actor is a long-lived thread that performs a specific function or set of functions and consumes a channel of requests from other threads using protocols to describe how and on what data those functions should be performed.
While the actor model has numerous benefits, exherting control over actors (such as during a shutdown event) and keeping track of all of their channels can be challenging. This module provides facilities that support the use of the actor model by addressing those challenges.
To maintain a registry of actors, keep track of their channels, and respond to control events,
a special type of actor called a router is launched. A Router encapsulates the
channels through which clients interact with the router and the actors it maintains. The
router's registry maps an ID for each actor to an Actor type similar to Router
which
encapsulates the channels through which actors receive their input.
Actors are spawned through a control message to the router. They are provided an ActorContext that provides the channels over which the function receives its inputs and control messages, and a channel over which it can communicate its status back to the router.
An actor function must:
- Use the
select!
macro that is re-exported fromcrossbeam_channel
to receive from both the request channel and the control channel. - Convert the
Any + Send
trait objects it receives over the request channel into the correct concrete type. - Respond to ActorCtl::Stop messages it receives over the control channel by promptly stablizing its state, sending the router an ActorStatus::Stopped status message, and exiting.
Simple Example
#[macro_use]
extern crate herbert;
use herbert::prelude::*;
fn main() {
// Create a router. This is manditory.
let router = Router::run("example");
// Spawn an actor, here named "herbert".
spawn_actor!(router, "herbert", |ctx: ActorContext| {
loop {
select! {
// Normal message channel.
recv(ctx.req) -> msg => {
match msg {
Ok(any) => {
// Convert trait object into concrete type.
if let Some(value) = any.downcast_ref::<String>() {
// Do some work.
println!("received order: {}", value);
} else {
// Bad value, here we're just ignoring it.
}
}
Err(e) => {
// Error receiving a normal message, choosing to terminate.
break;
}
}
}
// Control message channel.
recv(ctx.ctl) -> msg => {
match msg {
Ok(ActorCtl::Stop) => {
// We have been requested to stop.
// Stabilize any state and then break the loop to exit.
break;
}
Err(e) => {
// Error receiving a control message, choosing to terminate.
break;
},
}
}
}
}
// Notify the router we are stopped as our last action.
ctx.report_stopped().unwrap();
}).unwrap();
// Send our order to the "herbert" actor thread.
send_actor!(router, "herbert", String::from("2 gorditas")).unwrap();
// ...
// Shut down the router.
router.shutdown();
}
The things you'll need.
Re-exports several items from
crossbeam_channel, including the unbounded
channel type, the Receiver
and Sender
types, and the select!
macro.
Dependencies
~505KB