diff --git a/README.md b/README.md index 4b7358eb..9fc0a3b3 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ will be supported by `ractor`. There are 4 concurrent message types, which are l 1. Signals: Signals are the highest-priority of all and will interrupt the actor wherever processing currently is (this includes terminating async work). There is only 1 signal today, which is `Signal::Kill`, and it immediately terminates all work. This includes message processing or supervision event processing. 2. Stop: There is also the pre-defined stop signal. You can give a "stop reason" if you want, but it's optional. Stop is a graceful exit, meaning currently executing async work will complete, and on the next message processing iteration Stop will take priority over future supervision events or regular messages. It will **not** terminate currently executing work, regardless of the provided reason. -3. SupervisionEvent: Supervision events are messages from child actors to their supervisors in the event of their startup, death, and/or unhandled panic. Supervision events are how an actor's supervisor(parent) or peer monitors are notified of events of their children/peers and can handle lifetime events for them. +3. SupervisionEvent: Supervision events are messages from child actors to their supervisors in the event of their startup, death, and/or unhandled panic. Supervision events are how an actor's supervisor(parent) or peer monitors are notified of events of their children/peers and can handle lifetime events for them. If you set `panic = 'abort'` in your `Cargo.toml`, panics **will** start cause program termination and not be caught in the supervision flow. 4. Messages: Regular, user-defined, messages are the last channel of communication to actors. They are the lowest priority of the 4 message types and denote general actor work. The first 3 messages types (signals, stop, supervision) are generally quiet unless it's a lifecycle event for the actor, but this channel is the "work" channel doing what your actor wants to do! diff --git a/ractor/Cargo.toml b/ractor/Cargo.toml index 25705ab0..1f3150a8 100644 --- a/ractor/Cargo.toml +++ b/ractor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ractor" -version = "0.9.6" +version = "0.9.7" authors = ["Sean Lawlor", "Evan Au", "Dillon George"] description = "A actor framework for Rust" documentation = "https://docs.rs/ractor" diff --git a/ractor/src/lib.rs b/ractor/src/lib.rs index ea10466b..50663a84 100644 --- a/ractor/src/lib.rs +++ b/ractor/src/lib.rs @@ -113,7 +113,8 @@ //! //! Actors in `ractor` also support supervision. This is done by "linking" actors together in a supervisor-child relationship. //! A supervisor is responsible for the life cycle of the child actor, and as such is notified when the actor starts, -//! stops, and fails (panics). +//! stops, and fails (panics). If you set `panic = 'abort'` in your `Cargo.toml`, panics **will** start cause program termination +//! and not be caught in the supervision flow. //! //! Supervision is presently left to the implementor to outline handling of supervision events, but you can see a suite of //! supervision tests in `crate::actor::tests::supervisor` for examples on the supported functionality. diff --git a/ractor/src/macros.rs b/ractor/src/macros.rs index 5515edfe..47ba0e33 100644 --- a/ractor/src/macros.rs +++ b/ractor/src/macros.rs @@ -6,7 +6,7 @@ //! Macro helpers for remote procedure calls /// `cast!` takes an actor and a message and emits a [crate::RactorErr] error -/// which can be pattern matched on in order to derive the output +/// which can be pattern matched on in order to derive the output. #[macro_export] macro_rules! cast { ($actor:expr, $msg:expr) => { @@ -24,7 +24,7 @@ macro_rules! cast { /// /// Returns [Ok(_)] with the result on successful RPC or [Err(crate::RactorErr)] on failure /// Example usage (without the `cluster` feature) -/// ```no_run +/// ```rust /// use ractor::{call, Actor, RpcReplyPort, ActorRef, ActorProcessingErr}; /// struct TestActor; /// enum MessageFormat { @@ -62,10 +62,13 @@ macro_rules! cast { /// } /// } /// -/// async fn test() { -/// let (actor, _handle) = Actor::spawn(None, TestActor, ()).await.unwrap(); +/// #[tokio::main] +/// async fn main() { +/// let (actor, handle) = Actor::spawn(None, TestActor, ()).await.unwrap(); /// let result = call!(actor, MessageFormat::TestRpc, "Something".to_string()).unwrap(); -/// assert_eq!(result, "Something".to_string()) +/// assert_eq!(result, "Something".to_string()); +/// actor.stop(None); +/// handle.await.unwrap(); /// } /// ``` #[macro_export] @@ -143,10 +146,13 @@ macro_rules! call { /// } /// } /// -/// async fn test() { -/// let (actor, _handle) = Actor::spawn(None, TestActor, ()).await.unwrap(); +/// #[tokio::main] +/// async fn main() { +/// let (actor, handle) = Actor::spawn(None, TestActor, ()).await.unwrap(); /// let result = call_t!(actor, MessageFormat::TestRpc, 50, "Something".to_string()).unwrap(); -/// assert_eq!(result, "Something".to_string()) +/// assert_eq!(result, "Something".to_string()); +/// actor.stop(None); +/// handle.await.unwrap(); /// } /// ``` #[macro_export] diff --git a/ractor/src/pg/mod.rs b/ractor/src/pg/mod.rs index adf7fbd0..b26db79f 100644 --- a/ractor/src/pg/mod.rs +++ b/ractor/src/pg/mod.rs @@ -17,6 +17,50 @@ //! supervision port of the [crate::Actor] //! //! Inspired from [Erlang's `pg` module](https://www.erlang.org/doc/man/pg.html) +//! +//! ## Examples +//! +//! ```rust +//! use ractor::{Actor, ActorRef, ActorProcessingErr}; +//! use ractor::pg; +//! +//! struct ExampleActor; +//! +//! #[cfg_attr(feature = "async-trait", ractor::async_trait)] +//! impl Actor for ExampleActor { +//! type Msg = (); +//! type State = (); +//! type Arguments = (); +//! +//! async fn pre_start(&self, _myself: ActorRef, _args: Self::Arguments) -> Result { +//! println!("Starting"); +//! Ok(()) +//! } +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! let (actor, handle) = Actor::spawn(None, ExampleActor, ()).await.expect("Failed to startup dummy actor"); +//! let group = "the_group".to_string(); +//! +//! // Join the actor to a group. This is also commonly done in `pre_start` or `post_start` +//! // of the actor itself without having to do it externally by some coordinator +//! pg::join(group.clone(), vec![actor.get_cell()]); +//! // Retrieve the pg group membership +//! let members = pg::get_members(&group); +//! // Send a message to the up-casted actor +//! let the_actor: ActorRef<()> = members.get(0).unwrap().clone().into(); +//! ractor::cast!(the_actor, ()).expect("Failed to send message"); +//! +//! // wait for actor exit +//! actor.stop(None); +//! handle.await.unwrap(); +//! +//! // The actor will automatically be removed from the group upon shutdown. +//! let members = pg::get_members(&group); +//! assert_eq!(members.len(), 0); +//! } +//! ``` use std::collections::HashMap; use std::sync::Arc; diff --git a/ractor/src/registry/mod.rs b/ractor/src/registry/mod.rs index 9c11518a..0bc33d1f 100644 --- a/ractor/src/registry/mod.rs +++ b/ractor/src/registry/mod.rs @@ -20,8 +20,9 @@ //! or agents will runtime panic on message reception, and supervision //! processes would need to restart the actors. //! -//! ## Example +//! ## Examples //! +//! **Basic actor retrieval** //! ```rust //! async fn test() { //! let maybe_actor = ractor::registry::where_is("my_actor".to_string()); @@ -31,6 +32,43 @@ //! } //! } //! ``` +//! +//! **Full example** +//! +//! ```rust +//! use ractor::{Actor, ActorRef, ActorProcessingErr}; +//! use ractor::registry; +//! +//! struct ExampleActor; +//! +//! #[cfg_attr(feature = "async-trait", ractor::async_trait)] +//! impl Actor for ExampleActor { +//! type Msg = (); +//! type State = (); +//! type Arguments = (); +//! +//! async fn pre_start(&self, _myself: ActorRef, _args: Self::Arguments) -> Result { +//! println!("Starting"); +//! Ok(()) +//! } +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! let (actor, handle) = Actor::spawn(Some("my_actor".to_string()), ExampleActor, ()).await.expect("Failed to startup dummy actor"); +//! +//! // Retrieve the actor by name from the registry +//! let who: ActorRef<()> = registry::where_is("my_actor".to_string()).expect("Failed to find actor").into(); +//! who.cast(()).expect("Failed to send message"); +//! +//! // wait for actor exit +//! actor.stop(None); +//! handle.await.unwrap(); +//! +//! // Automatically removed from the registry upon shutdown +//! assert!(registry::where_is("my_actor".to_string()).is_none()); +//! } +//! ``` use std::sync::Arc; diff --git a/ractor/src/rpc/mod.rs b/ractor/src/rpc/mod.rs index da166211..b158f200 100644 --- a/ractor/src/rpc/mod.rs +++ b/ractor/src/rpc/mod.rs @@ -9,6 +9,66 @@ //! standard [Erlang `gen_server`](https://www.erlang.org/doc/man/gen_server.html#cast-2). //! The tl;dr is that `cast` is an send without waiting on a reply while `call` is expecting //! a reply from the actor being communicated with. +//! +//! ## Examples +//! +//! ```rust +//! use ractor::{cast, call, call_t}; +//! use ractor::concurrency::Duration; +//! use ractor::{Actor, ActorRef, ActorProcessingErr, RpcReplyPort}; +//! +//! struct ExampleActor; +//! +//! enum ExampleMessage { +//! Cast, +//! Call(RpcReplyPort), +//! } +//! +//! #[cfg(feature = "cluster")] +//! impl ractor::Message for ExampleMessage {} +//! +//! #[cfg_attr(feature = "async-trait", ractor::async_trait)] +//! impl Actor for ExampleActor { +//! type Msg = ExampleMessage; +//! type State = (); +//! type Arguments = (); +//! +//! async fn pre_start(&self, _myself: ActorRef, _args: Self::Arguments) -> Result { +//! println!("Starting"); +//! Ok(()) +//! } +//! +//! async fn handle(&self, _myself: ActorRef, message: Self::Msg, _state: &mut Self::State) -> Result<(), ActorProcessingErr> { +//! match message { +//! ExampleMessage::Cast => println!("Cast message"), +//! ExampleMessage::Call(reply) => { +//! println!("Call message"); +//! let _ = reply.send("a reply".to_string()); +//! } +//! } +//! Ok(()) +//! } +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! let (actor, handle) = Actor::spawn(None, ExampleActor, ()).await.expect("Failed to startup dummy actor"); +//! +//! // send a 1-way message (equivalent patterns) +//! actor.cast(ExampleMessage::Cast).expect("Failed to send message"); +//! cast!(actor, ExampleMessage::Cast).expect("Failed to send message"); +//! +//! // Send a message to the actor, with an associated reply channel, +//! // and wait for the reply from the actor (optionally up to a timeout) +//! let _result = actor.call(ExampleMessage::Call, Some(Duration::from_millis(100))).await.expect("Failed to call actor"); +//! let _result = call!(actor, ExampleMessage::Call).expect("Failed to call actor"); +//! let _result = call_t!(actor, ExampleMessage::Call, 100).expect("Failed to call actor with timeout"); +//! +//! // wait for actor exit +//! actor.stop(None); +//! handle.await.unwrap(); +//! } +//! ``` use crate::concurrency::{self, Duration, JoinHandle}; diff --git a/ractor/src/time/mod.rs b/ractor/src/time/mod.rs index 4ddeaf98..0234d841 100644 --- a/ractor/src/time/mod.rs +++ b/ractor/src/time/mod.rs @@ -13,6 +13,64 @@ //! 2. Send after a delay //! 3. Stop after a delay //! 4. Kill after a delay +//! +//! ## Examples +//! +//! ```rust +//! use ractor::concurrency::Duration; +//! use ractor::{Actor, ActorRef, ActorProcessingErr}; +//! +//! struct ExampleActor; +//! +//! enum ExampleMessage { +//! AfterDelay, +//! OnPeriod, +//! } +//! +//! #[cfg(feature = "cluster")] +//! impl ractor::Message for ExampleMessage {} +//! +//! #[cfg_attr(feature = "async-trait", ractor::async_trait)] +//! impl Actor for ExampleActor { +//! type Msg = ExampleMessage; +//! type State = (); +//! type Arguments = (); +//! +//! async fn pre_start(&self, _myself: ActorRef, _args: Self::Arguments) -> Result { +//! println!("Starting"); +//! Ok(()) +//! } +//! +//! async fn handle(&self, _myself: ActorRef, message: Self::Msg, _state: &mut Self::State) -> Result<(), ActorProcessingErr> { +//! match message { +//! ExampleMessage::AfterDelay => println!("After delay"), +//! ExampleMessage::OnPeriod => println!("On period"), +//! } +//! Ok(()) +//! } +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! let (actor, handle) = Actor::spawn(None, ExampleActor, ()).await.expect("Failed to startup dummy actor"); +//! +//! // send the message after a 100ms delay +//! actor.send_after(Duration::from_millis(100), || ExampleMessage::AfterDelay); +//! +//! // send this message every 10ms +//! actor.send_interval(Duration::from_millis(10), || ExampleMessage::OnPeriod); +//! +//! // Exit the actor after 200ms (equivalent of calling `stop(maybe_reason)`) +//! actor.exit_after(Duration::from_millis(200)); +//! +//! // Kill the actor after 300ms (won't execute since we did stop before, but here +//! // as an example) +//! actor.kill_after(Duration::from_millis(300)); +//! +//! // wait for actor exit +//! handle.await.unwrap(); +//! } +//! ``` use crate::concurrency::{Duration, JoinHandle}; diff --git a/ractor_cluster/Cargo.toml b/ractor_cluster/Cargo.toml index 6605b28c..e05ca597 100644 --- a/ractor_cluster/Cargo.toml +++ b/ractor_cluster/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ractor_cluster" -version = "0.9.6" +version = "0.9.7" authors = ["Sean Lawlor", "Evan Au", "Dillon George"] description = "Distributed cluster environment of Ractor actors" documentation = "https://docs.rs/ractor" diff --git a/ractor_cluster_derive/Cargo.toml b/ractor_cluster_derive/Cargo.toml index 1b4656d9..07301cb4 100644 --- a/ractor_cluster_derive/Cargo.toml +++ b/ractor_cluster_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ractor_cluster_derive" -version = "0.9.6" +version = "0.9.7" authors = ["Sean Lawlor "] description = "Derives for ractor_cluster" license = "MIT"