3 unstable releases

0.2.1 Mar 13, 2020
0.2.0 Mar 5, 2020
0.1.0 Mar 4, 2020

#181 in #router

MIT license

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:

herbert-was-great

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 from crossbeam_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