├── .github ├── CODEOWNERS └── workflows │ └── CI.yml ├── .gitignore ├── BENCHES.md ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── bench.rs ├── examples ├── 01_ping.rs ├── 02_notify.rs ├── 03_fibonacci.rs ├── 04_ring.rs ├── 05_timed_stream.rs ├── 06_async_std.rs ├── 07_no_runtime.rs └── 08_websocket.rs ├── src ├── actor.rs ├── address.rs ├── context.rs ├── envelope.rs ├── errors.rs ├── handler.rs ├── lib.rs ├── registry.rs └── runtime │ ├── async_std.rs │ ├── empty.rs │ ├── mod.rs │ └── tokio.rs ├── test.sh └── tests └── tokio ├── mod.rs └── registry.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Default owners for everything in the repository: 2 | * @popzxc 3 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '*' 7 | 8 | jobs: 9 | fmt: 10 | name: Rustfmt 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 10 13 | 14 | steps: 15 | - name: Checkout sources 16 | uses: actions/checkout@v2 17 | 18 | - name: Install rust 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: stable 22 | components: rustfmt 23 | profile: minimal 24 | override: true 25 | 26 | - name: Run rustfmt 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: fmt 30 | args: --all -- --check 31 | 32 | clippy: 33 | name: Clippy 34 | runs-on: ubuntu-latest 35 | timeout-minutes: 10 36 | 37 | steps: 38 | - name: Checkout sources 39 | uses: actions/checkout@v2 40 | 41 | - name: Install rust 42 | uses: actions-rs/toolchain@v1 43 | with: 44 | toolchain: stable 45 | components: clippy 46 | profile: minimal 47 | override: true 48 | 49 | - name: Run clippy 50 | uses: actions-rs/clippy-check@v1 51 | with: 52 | token: ${{ secrets.GITHUB_TOKEN }} 53 | args: -- -D warnings 54 | 55 | audit: 56 | name: Security audit 57 | runs-on: ubuntu-latest 58 | timeout-minutes: 10 59 | 60 | steps: 61 | - uses: actions/checkout@v2 62 | - uses: actions-rs/audit-check@v1 63 | with: 64 | token: ${{ secrets.GITHUB_TOKEN }} 65 | 66 | deadlinks: 67 | name: Deadlinks 68 | runs-on: ubuntu-latest 69 | timeout-minutes: 10 70 | 71 | steps: 72 | - name: Checkout sources 73 | uses: actions/checkout@v2 74 | 75 | - uses: actions-rs/install@v0.1 76 | with: 77 | crate: cargo-deadlinks 78 | use-tool-cache: true 79 | 80 | - name: Run deadlinks 81 | run: cargo deadlinks 82 | 83 | test: 84 | name: Test 85 | runs-on: ubuntu-latest 86 | timeout-minutes: 10 87 | 88 | steps: 89 | - name: Checkout sources 90 | uses: actions/checkout@v2 91 | 92 | - name: Install rust 93 | uses: actions-rs/toolchain@v1 94 | with: 95 | toolchain: stable 96 | profile: minimal 97 | override: true 98 | 99 | - name: Build 100 | uses: actions-rs/cargo@v1 101 | with: 102 | command: build 103 | args: --examples --all 104 | 105 | - name: Test / tokio 106 | uses: actions-rs/cargo@v1 107 | with: 108 | command: test 109 | args: --all-targets --no-default-features --features runtime-tokio 110 | 111 | - name: Test / async-std 112 | uses: actions-rs/cargo@v1 113 | with: 114 | command: test 115 | args: --all-targets --no-default-features --features runtime-async-std 116 | 117 | - name: Test / no features 118 | uses: actions-rs/cargo@v1 119 | with: 120 | command: test 121 | args: --examples --no-default-features 122 | 123 | - name: Doc tests 124 | uses: actions-rs/cargo@v1 125 | with: 126 | command: test 127 | args: --doc 128 | 129 | 130 | examples: 131 | name: Run examples 132 | runs-on: ubuntu-latest 133 | timeout-minutes: 10 134 | 135 | steps: 136 | - name: Checkout sources 137 | uses: actions/checkout@v2 138 | 139 | - name: Install rust 140 | uses: actions-rs/toolchain@v1 141 | with: 142 | toolchain: stable 143 | profile: minimal 144 | override: true 145 | 146 | - name: Build 147 | uses: actions-rs/cargo@v1 148 | with: 149 | command: build 150 | args: --examples 151 | 152 | - run: cargo run --example 01_ping 153 | - run: cargo run --example 02_notify 154 | - run: cargo run --example 03_fibonacci 155 | - run: cargo run --example 04_ring 10 10 156 | - run: cargo run --example 05_timed_stream 157 | - run: cargo run --no-default-features --features runtime-async-std --example 06_async_std 158 | - run: cargo run --example 07_no_runtime 159 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /BENCHES.md: -------------------------------------------------------------------------------- 1 | # Benchmarking 2 | 3 | This document contains the benchmarking results and comparison with other actor libraries. 4 | 5 | ## Used machine 6 | 7 | All the results are presented for 2020 MacBook Pro (M1 / 8gb ram). 8 | 9 | ## Ring benchmark 10 | 11 | Actix provides a [ring benchmark](https://github.com/actix/actix/blob/master/actix/examples/ring.rs) to show 12 | its performance. 13 | 14 | In order to competition to be more or less fair, this section will include not only results 15 | for the benchmark as-is, but also for an async version of `actix` benchmark. 16 | 17 | In order to make `actix` example async, the `Handler` structure was modified in a following way: 18 | 19 | ```rust 20 | impl Handler for Node { 21 | // Result is not a unit type anymore, it's the future. 22 | type Result = ResponseFuture<()>; 23 | 24 | fn handle(&mut self, msg: Payload, _: &mut Context) -> Self::Result { 25 | if msg.0 >= self.limit { 26 | // ..left as-is 27 | return Box::pin(async {}); 28 | } 29 | // ...left as-is 30 | Box::pin(async {}) 31 | } 32 | } 33 | ``` 34 | 35 | ### `actix`, sync version 36 | 37 | `cargo run --release --example ring -- 2000 2000` 38 | Time taken: 0.510156 seconds (7840738 msg/second) 39 | 40 | ### `actix`, async version 41 | 42 | `cargo run --release --example ring -- 2000 2000` 43 | Time taken: 1.108587 seconds (3608196 msg/second) 44 | 45 | ### `messages` 46 | 47 | `cargo run --release --example 04_ring -- 2000 2000` 48 | Time taken: 0.940551 seconds (4252826 msg/second) 49 | 50 | ## Operations benchmark 51 | 52 | Below you can find results for common operations of `messages`. 53 | 54 | ### `message` operations 55 | 56 | **Spawn** 57 | 58 | time: [1.3477 us **1.3814 us** 1.4149 us] 59 | 60 | thrpt: [706.78 Kelem/s **723.92 Kelem/s** 741.99 Kelem/s] 61 | 62 | **Send message** 63 | 64 | time: [19.788 us **19.992 us** 20.255 us] 65 | 66 | thrpt: [49.371 Kelem/s **50.019 Kelem/s** 50.535 Kelem/s] 67 | 68 | **Notify** 69 | 70 | time: [75.169 ns **76.816 ns** 78.467 ns] 71 | 72 | thrpt: [12.744 Melem/s **13.018 Melem/s** 13.303 Melem/s] 73 | 74 | 75 | ### Raw channels 76 | 77 | **Send message (Raw channel)** 78 | 79 | time: [19.540 us **19.632 us** 19.738 us] 80 | 81 | thrpt: [50.663 Kelem/s **50.936 Kelem/s** 51.176 Kelem/s] 82 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # `message` changelog 2 | 3 | ## Unreleased 4 | 5 | ## 0.3.1 (21.01.2022) 6 | 7 | - Improved error message for the send error. [#9] 8 | 9 | [#9]: https://github.com/popzxc/messages-rs/pull/9 10 | 11 | ## 0.3.0 (16.06.2021) 12 | 13 | - Big performance boost: 14 | - Actor spawning performance: +3%. 15 | - Sending message with response: +5% (now on par with `futures` channels). 16 | - Sending notification: **+593%**. 17 | - Ring benchmark report: 4 252 826 msg/second (was 3 121 844 msg/second; `actix`'s 18 | async version of ring benchmark: 3 608 196 msg/second). 19 | 20 | - Some internal improvements. 21 | 22 | ## 0.2.4 (06.06.2021) 23 | 24 | - Actor support was implemented. 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "messages" 3 | version = "0.3.1" 4 | authors = ["Igor Aleksanov "] 5 | edition = "2018" 6 | repository = "https://github.com/popzxc/messages-rs" 7 | documentation = "https://docs.rs/messages" 8 | readme = "README.md" 9 | license = "MIT" 10 | keywords = ["async", "actor", "utils", "actors"] 11 | categories = ["rust-patterns"] 12 | description = "Runtime-agnostic actor library." 13 | 14 | [dependencies] 15 | futures = "0.3.5" 16 | async-trait = "0.1" 17 | async-channel = "1.6" 18 | async-oneshot = "0.5" 19 | 20 | # Runtime section. 21 | tokio = { version = "1.6", features = ["rt"], optional = true } 22 | async-std = { version = "1.9", optional = true } 23 | once_cell = { version = "1.7", optional = true } 24 | 25 | [dev-dependencies] 26 | # Runtimes 27 | tokio = { version = "1.6", features = ["full"] } 28 | async-std = { version = "1.9", features = ["attributes"] } 29 | smol = "1.2" 30 | 31 | # Examples deps 32 | tokio-stream = { version = "0.1", features = ["time"] } # Stream examples 33 | tokio-tungstenite = "0.14" # WebSocket example 34 | num_cpus = "1.13" # Fibonacci example 35 | 36 | # Benches 37 | criterion = { version = "0.3", features = ["async_tokio", "html_reports"] } 38 | 39 | [[bench]] 40 | name = "bench" 41 | required-features = ["runtime-tokio"] 42 | harness = false 43 | 44 | [features] 45 | default = ["runtime-tokio"] 46 | runtime-tokio = ["tokio", "once_cell"] 47 | runtime-async-std = ["async-std", "once_cell"] 48 | 49 | [[example]] 50 | name = "01_ping" 51 | required-features = ["runtime-tokio"] 52 | 53 | [[example]] 54 | name = "02_notify" 55 | required-features = ["runtime-tokio"] 56 | 57 | [[example]] 58 | name = "03_fibonacci" 59 | required-features = ["runtime-tokio"] 60 | 61 | [[example]] 62 | name = "04_ring" 63 | required-features = ["runtime-tokio"] 64 | 65 | [[example]] 66 | name = "05_timed_stream" 67 | required-features = ["runtime-tokio"] 68 | 69 | [[example]] 70 | name = "06_async_std" 71 | required-features = ["runtime-async-std"] 72 | 73 | # `07_no_runtime` doesn't need runtime features. 74 | 75 | [[example]] 76 | name = "08_websocket" 77 | required-features = ["runtime-tokio"] 78 | 79 | [[test]] 80 | name = "tokio" 81 | path = "tests/tokio/mod.rs" 82 | required-features = ["runtime-tokio"] 83 | 84 | [package.metadata.docs.rs] 85 | features = ["runtime-tokio"] 86 | rustdoc-args = ["--cfg", "docsrs"] 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Igor Aleksanov 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Messages. Convenient asynchronous communication 2 | 3 | **Status:** 4 | [![CI](https://github.com/popzxc/messages-rs/workflows/CI/badge.svg)](https://github.com/popzxc/messages-rs/actions) 5 | 6 | **Project info:** 7 | [![Docs.rs](https://docs.rs/messages/badge.svg)](https://docs.rs/messages) 8 | [![Latest Version](https://img.shields.io/crates/v/messages.svg)](https://crates.io/crates/messages) 9 | [![License](https://img.shields.io/github/license/popzxc/messages-rs.svg)](https://github.com/popzxc/messages-rs) 10 | ![Rust 1.50+ required](https://img.shields.io/badge/rust-1.50+-blue.svg?label=Rust) 11 | 12 | ## Description 13 | 14 | `messages` is a runtime-agnostic actor library. 15 | 16 | It is heavily inspired by [`actix`][actix], a great actor framework. 17 | 18 | This crate can be used with any runtime, whether it's popular or not. 19 | However, for the biggest one (`tokio` and `async-std`) there is an optional 20 | built-in support enabling more convenient interface (such as an automatic 21 | actor spawning). 22 | 23 | [actix]: https://crates.io/crates/actix 24 | 25 | ## Key features 26 | 27 | - Full runtime independence. Can be used with any possible runtime that can spawn futures. 28 | - Low dependencies amount. 29 | - Low amount of boilerplate without derive macros. 30 | - Good performance (close to raw channels). 31 | - Relevant (but sufficient) functionality only. 32 | 33 | ## Which library should I choose? 34 | 35 | `actix` is a great, thoughtful, polished, and optimized library. If it is *possible* 36 | for you, you should consider it as the main option. 37 | 38 | However, if any of statements below apply to your use case, `messages` may be better: 39 | 40 | - You can't or don't want to stick to the Actix runtime. 41 | - Your tasks may not have the similar runtime expense (`actix-rt` does not have work stealing 42 | and thus some threads may be underloaded in that case). 43 | - You are seeking for the simpler interface and don't want to implement asynchronous code atop 44 | of the initially sync interface. 45 | 46 | **But what about [`xactor`](https://crates.io/crates/xactor)?** 47 | 48 | `xactor` is another good library inspired by Actix. It initially was built for `async-std` but 49 | then gained `tokio` support. 50 | 51 | Nonetheless, this library is not runtime-agnostic. It supports `async-std` and `tokio` v1, but 52 | is not (yet) compatible with another runtimes. 53 | 54 | That being said, `messages` initially serves different purpose: provide a way to implement 55 | actor workflow without having to think about supported runtimes. 56 | 57 | ## Asyncness 58 | 59 | This library is async-first, meaning that everything is made with respect to asynchronous architecture. 60 | While in *some* cases synchronous interfaces could've been more performant, it'd make the interface much 61 | more bloated. If synchronous actor interface is preferred, consider using `actix`, as it provides one. 62 | 63 | ## Performance 64 | 65 | TL;DR: This library provides performance that is better than in `actix` (for **asynchronous** message handling; 66 | based on the ring benchmark used by `actix` itself) and is tied with `futures` channels. 67 | 68 | More details are presented in the [BENCHES.md](./BENCHES.md). 69 | 70 | *Note:* `messages` treats `async` and multi-threaded context as its main environment, 71 | thus it may be less suitable (or, more precisely, less efficient) for the partially 72 | sync context. For instance, the sync version of the ring benchmark is by 80% faster than this library. 73 | 74 | ## Examples 75 | 76 | ### With runtime features 77 | 78 | ```rust 79 | use messages::prelude::*; 80 | 81 | struct Example; // Most of the types can be an actor. 82 | 83 | // While `Actor` implementation can be customized, it is not required. 84 | #[async_trait] 85 | impl Actor for Example {} 86 | 87 | // Message handler that calculates sum of two numbers. 88 | #[async_trait] 89 | impl Handler<(u8, u8)> for Example { 90 | type Result = u16; 91 | async fn handle(&mut self, (a, b): (u8, u8), context: &Context) -> u16 { 92 | (a as u16) + (b as u16) 93 | } 94 | } 95 | 96 | // Notification handler that just writes received number to stdout. 97 | #[async_trait] 98 | impl Notifiable for Example { 99 | async fn notify(&mut self, input: u8, context: &Context) { 100 | println!("Received number {}", input); 101 | } 102 | } 103 | 104 | #[tokio::main] 105 | async fn main() { 106 | let mut addr = Example.spawn(); 107 | let result = addr.send((22, 20)).await.unwrap(); 108 | assert_eq!(result, 42); 109 | addr.notify(42).await.unwrap(); 110 | addr.stop().await; 111 | addr.wait_for_stop().await; 112 | } 113 | ``` 114 | 115 | ### Without runtime features 116 | 117 | ```rust 118 | use messages::prelude::*; 119 | 120 | struct Ping; 121 | 122 | #[async_trait] 123 | impl Actor for Ping {} 124 | 125 | #[async_trait] 126 | impl Handler for Ping { 127 | type Result = u8; 128 | async fn handle(&mut self, input: u8, context: &Context) -> u8 { 129 | input 130 | } 131 | } 132 | 133 | #[tokio::main] 134 | async fn main() { 135 | let context = Context::new(); 136 | let mut addr = context.address(); 137 | let actor = Ping; 138 | // Could've been any other runtime. 139 | let mut task_handle = tokio::spawn(context.run(actor)); 140 | let result = addr.send(42).await.unwrap(); 141 | assert_eq!(result, 42); 142 | addr.stop().await; 143 | addr.wait_for_stop().await; 144 | task_handle.await.unwrap(); 145 | } 146 | ``` 147 | 148 | ### More 149 | 150 | More examples can be found in the [examples](./examples) directory. 151 | 152 | They are numbered and written in a manner so that the next example is always somewhat superior to the 153 | previous one. You can consider it to be a temporary alternative for a book (which is coming later). 154 | 155 | List of currently provided examples: 156 | 157 | - [Ping](./examples/01_ping.rs): Simple ping actor without much of functionality. 158 | - [Notify](./examples/02_notify.rs): More verbose example showing capabilities of the actor interface. 159 | - [Fibonacci](./examples/03_fibonacci.rs): Example of a coroutine actor, i.e. one that can process messages in parallel. 160 | - [Ring](./examples/04_ring.rs): Ring benchmark, mostly copied from the corresponding `actix` example. 161 | - [Timed stream](./examples/05_timed_stream.rs): Example showing both how to attach stream to an actor and send timed notifications to it. 162 | - [`async-std`](./examples/06_async_std.rs): Version of the `Notify` example adapted for `async-std` runtime. 163 | - [`smol`](./examples/07_no_runtime.rs): Example of using a runtime not supported out of the box. In that case, `smol`. 164 | - [WebSocket](./examples/08_websocket.rs): Simple actor-based echo websocket server (and a client to play with it). 165 | 166 | 167 | ## Contributing 168 | 169 | All kinds of contributions are really appreciated! 170 | 171 | ## License 172 | 173 | `messages` library is licensed under the MIT License. See [LICENSE](LICENSE) for details. 174 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::*; 2 | use futures::{ 3 | channel::{mpsc, oneshot}, 4 | SinkExt, StreamExt, 5 | }; 6 | use messages::prelude::*; 7 | 8 | struct Ping; 9 | 10 | fn runtime() -> tokio::runtime::Runtime { 11 | tokio::runtime::Builder::new_multi_thread() 12 | .enable_all() 13 | .build() 14 | .unwrap() 15 | } 16 | 17 | #[async_trait] 18 | impl Actor for Ping {} 19 | 20 | #[async_trait] 21 | impl Handler for Ping { 22 | type Result = u8; 23 | 24 | async fn handle(&mut self, input: u8, _context: &Context) -> Self::Result { 25 | input 26 | } 27 | } 28 | 29 | #[async_trait] 30 | impl Notifiable for Ping { 31 | async fn notify(&mut self, _input: u8, _context: &Context) { 32 | // Do nothing 33 | } 34 | } 35 | 36 | pub fn criterion_benchmark(c: &mut Criterion) { 37 | // Actors benchmarks. 38 | 39 | let mut g = c.benchmark_group("Actor workflow"); 40 | g.throughput(Throughput::Elements(1)); 41 | 42 | g.bench_function("Actor spawn", move |b| { 43 | b.to_async(runtime()).iter(|| async { 44 | let _ = black_box(Ping.spawn()); 45 | }) 46 | }); 47 | 48 | g.bench_function("Actor send message", move |b| { 49 | b.to_async(runtime()).iter_with_setup( 50 | || Ping.spawn(), 51 | |mut addr| async move { 52 | let _x = black_box(addr.send(20u8).await.unwrap()); 53 | }, 54 | ) 55 | }); 56 | 57 | g.bench_function("Actor notify", move |b| { 58 | b.to_async(runtime()).iter_with_setup( 59 | || Ping.spawn(), 60 | |mut addr| async move { 61 | addr.notify(20u8).await.unwrap(); 62 | }, 63 | ) 64 | }); 65 | 66 | g.finish(); 67 | 68 | // Raw channel benchmarks. 69 | 70 | let mut g = c.benchmark_group("Raw channel throughput"); 71 | g.throughput(Throughput::Elements(1)); 72 | 73 | g.bench_function("Actor send message", move |b| { 74 | b.to_async(runtime()).iter_with_setup( 75 | || { 76 | let (oneshot_send, oneshot_recv) = oneshot::channel::(); 77 | let (send, mut recv) = mpsc::channel::>(16); 78 | tokio::spawn(async move { 79 | let send = recv.next().await.unwrap(); 80 | send.send(42u8).unwrap(); 81 | }); 82 | 83 | (send, oneshot_send, oneshot_recv) 84 | }, 85 | |(mut send, back_send, recv)| async move { 86 | let _x = send.send(back_send).await.unwrap(); 87 | let _y = black_box(recv.await.unwrap()); 88 | }, 89 | ) 90 | }); 91 | 92 | g.finish(); 93 | } 94 | 95 | criterion_group!(benches, criterion_benchmark); 96 | criterion_main!(benches); 97 | -------------------------------------------------------------------------------- /examples/01_ping.rs: -------------------------------------------------------------------------------- 1 | //! Simples possible example of a ping actor that stores 2 | //! sum of received messages and responds with the message 3 | //! being sent. 4 | 5 | // Prelude contains all the required definitions to work 6 | // with the messages crate. 7 | use messages::prelude::*; 8 | 9 | // Define `Ping` message. 10 | // It will be sent to our actor. 11 | // Note that no derives are required to make things work. 12 | // Any type can represent a message as long as it's thread-safe. 13 | struct Ping(usize); 14 | 15 | // Our actor. Despite the fact that it will be used in the multi-threaded 16 | // runtime, we don't have to bother ourselves with `RwLock`s and `Arc`s: 17 | // synchronization guarantees are on the `messages` crate. 18 | struct MyActor { 19 | count: usize, 20 | } 21 | 22 | // In order to turn our structure into an actor, we have to implement the 23 | // corresponding trait for it. 24 | // It is possible to customize actor behavior by implementing additional methods 25 | // for it, but it's not required. 26 | impl Actor for MyActor {} 27 | 28 | // In order to be able to process messages, we must implement either `Handler` or 29 | // `Notifiable` trait. Since we want to return values to the message sender, 30 | // we are going to use `Handler` trait. 31 | #[async_trait] 32 | impl Handler for MyActor { 33 | // Define the type of response that will be sent to the message author. 34 | type Result = usize; 35 | 36 | // Define the logic of the message processing. 37 | async fn handle(&mut self, msg: Ping, _: &Context) -> Self::Result { 38 | self.count += msg.0; 39 | self.count 40 | } 41 | } 42 | 43 | #[tokio::main] 44 | async fn main() { 45 | // Start new actor 46 | let mut address = MyActor { count: 10 }.spawn(); 47 | 48 | // Send message and get the result. 49 | let res = address.send(Ping(10)).await; 50 | 51 | // Check whether actor returned the expected message. 52 | println!("RESULT: {}", res.unwrap() == 20); 53 | } 54 | -------------------------------------------------------------------------------- /examples/02_notify.rs: -------------------------------------------------------------------------------- 1 | //! More comprehensive example that aims to show more of `message` 2 | //! crate functionality. 3 | //! 4 | //! In this example, both `Handler` and `Notifiable` traits are implemented, 5 | //! as well as additional methods of an `Actor` trait. 6 | 7 | use messages::prelude::*; 8 | 9 | #[derive(Debug, Default)] 10 | pub struct Service { 11 | value: u64, 12 | } 13 | 14 | #[async_trait] 15 | impl Actor for Service { 16 | // `started` method will be invoked *before* the first message 17 | // will be received. 18 | async fn started(&mut self) { 19 | println!("Service was started"); 20 | } 21 | 22 | // `stopping` method will be invoked when the actor will be requested 23 | // to stop its execution. 24 | async fn stopping(&mut self) -> ActorAction { 25 | println!("Service is stopping"); 26 | // We could've ignored the stop request by returning `ActorAction::KeepRunning`. 27 | ActorAction::Stop 28 | } 29 | 30 | // `stopped` method will be invoked once actor is actually stopped. 31 | fn stopped(&mut self) { 32 | println!("Service has stopped"); 33 | } 34 | } 35 | 36 | // Type that we will use to send messages. 37 | #[derive(Debug)] 38 | pub struct Request(pub u64); 39 | 40 | #[async_trait] 41 | impl Handler for Service { 42 | type Result = u64; 43 | 44 | async fn handle(&mut self, input: Request, _: &Context) -> u64 { 45 | self.value + input.0 46 | } 47 | } 48 | 49 | // Type that we will use for notifications. 50 | #[derive(Debug)] 51 | pub struct Notification(pub u64); 52 | 53 | // Unlike `Handler`, `Notifiable` trait doesn't have output. 54 | // It only serves one purpose: deliver a message to an actor. 55 | // No response is expected. 56 | #[async_trait] 57 | impl Notifiable for Service { 58 | async fn notify(&mut self, input: Notification, _: &Context) { 59 | self.value = input.0; 60 | } 61 | } 62 | 63 | impl Service { 64 | pub fn new() -> Self { 65 | Self::default() 66 | } 67 | } 68 | 69 | #[tokio::main] 70 | async fn main() { 71 | // Start a service. 72 | let mut address = Service::new().spawn(); 73 | 74 | // Send a notification. 75 | address.notify(Notification(10)).await.unwrap(); 76 | 77 | // Send a request and receive a response. 78 | let response: u64 = address.send(Request(1)).await.unwrap(); 79 | assert_eq!(response, 11); 80 | 81 | // Stop service. 82 | address.stop().await; 83 | // Wait for service to stop. 84 | address.wait_for_stop().await; 85 | // Ensure that actor is not running anymore. 86 | assert!(!address.connected()); 87 | } 88 | -------------------------------------------------------------------------------- /examples/03_fibonacci.rs: -------------------------------------------------------------------------------- 1 | //! Example of actor used to calculate some stuff. 2 | //! In that case, fibonacci numbers. 3 | //! 4 | //! This example uses `Coroutine` trait that allows message handling to be executed 5 | //! in parallel. 6 | 7 | use messages::prelude::*; 8 | 9 | struct FibonacciRequest(pub u32); 10 | 11 | #[derive(Debug, Clone)] 12 | struct FibonacciActor; 13 | 14 | impl Actor for FibonacciActor {} 15 | 16 | #[async_trait] 17 | impl Coroutine for FibonacciActor { 18 | type Result = Result; 19 | 20 | async fn calculate(self, msg: FibonacciRequest) -> Self::Result { 21 | // Artificially added big sleep to easily see whether requests are executed in parallel. 22 | // If requests will be processed sequentially, time to execute it will be `10 * N` seconds. 23 | // Otherwise, it will take `10 * (N / num_cpus)` seconds. 24 | tokio::time::sleep(std::time::Duration::from_secs(10)).await; 25 | if msg.0 == 0 { 26 | Err(()) 27 | } else if msg.0 == 1 { 28 | Ok(1) 29 | } else { 30 | let mut i = 0; 31 | let mut sum = 0; 32 | let mut last = 0; 33 | let mut curr = 1; 34 | while i < msg.0 - 1 { 35 | sum = last + curr; 36 | last = curr; 37 | curr = sum; 38 | i += 1; 39 | } 40 | Ok(sum) 41 | } 42 | } 43 | } 44 | 45 | #[tokio::main] 46 | async fn main() { 47 | // We send one message per CPU. 48 | let n_messages = num_cpus::get() as u32; 49 | 50 | let mut address = FibonacciActor.spawn(); 51 | 52 | let mut futures = Vec::with_capacity(n_messages as usize); 53 | for n in 5..(5 + n_messages) { 54 | let fut = address.calculate(FibonacciRequest(n)); 55 | futures.push(async move { (n, fut.await) }); 56 | } 57 | 58 | let results = futures::future::join_all(futures).await; 59 | for (n, res) in results { 60 | println!("Result for {} is {:?}", n, res); 61 | } 62 | 63 | address.stop().await; 64 | } 65 | -------------------------------------------------------------------------------- /examples/04_ring.rs: -------------------------------------------------------------------------------- 1 | //! Ring benchmark inspired by Programming Erlang: Software for a 2 | //! Concurrent World, by Joe Armstrong, Chapter 8.11.2 3 | //! 4 | //! "Write a ring benchmark. Create N processes in a ring. Send a 5 | //! message round the ring M times so that a total of N * M messages 6 | //! get sent. Time how long this takes for different values of N and M." 7 | 8 | use std::{env, io, time::SystemTime}; 9 | 10 | use futures::{channel::mpsc, SinkExt, StreamExt}; 11 | use messages::prelude::*; 12 | 13 | /// A payload with a counter 14 | #[derive(Debug)] 15 | struct Payload(usize); 16 | 17 | #[derive(Debug)] 18 | struct Node { 19 | id: usize, 20 | limit: usize, 21 | next: Address, 22 | calculated: mpsc::Sender<()>, 23 | } 24 | 25 | impl Actor for Node {} 26 | 27 | #[async_trait] 28 | impl Notifiable for Node { 29 | async fn notify(&mut self, msg: Payload, _ctx: &Context) { 30 | if msg.0 >= self.limit { 31 | println!( 32 | "Actor {} reached limit of {} (payload was {})", 33 | self.id, self.limit, msg.0 34 | ); 35 | 36 | self.calculated.send(()).await.unwrap(); 37 | return; 38 | } 39 | 40 | // Some prime in order for different actors to report progress. 41 | // Large enough to print about once per second in debug mode. 42 | if msg.0 % 498989 == 1 { 43 | println!( 44 | "Actor {} received message {} of {} ({:.2}%)", 45 | self.id, 46 | msg.0, 47 | self.limit, 48 | 100.0 * msg.0 as f32 / self.limit as f32 49 | ); 50 | } 51 | 52 | let _ = self.next.notify(Payload(msg.0 + 1)).await; 53 | } 54 | } 55 | 56 | #[tokio::main] 57 | async fn main() -> io::Result<()> { 58 | let (n_nodes, n_rounds) = parse_args(); 59 | 60 | let now = SystemTime::now(); 61 | 62 | let (calculated_sender, mut calculated_receiver) = mpsc::channel(1); 63 | 64 | println!("Setting up {} nodes", n_nodes); 65 | let limit = n_nodes * n_rounds; 66 | 67 | let mut node = Node::create_and_spawn(move |ctx| { 68 | let first_addr = ctx.address(); 69 | 70 | let mut prev_addr = Node { 71 | id: 1, 72 | limit, 73 | next: first_addr, 74 | calculated: calculated_sender.clone(), 75 | } 76 | .spawn(); 77 | 78 | for id in 2..n_nodes { 79 | prev_addr = Node { 80 | id, 81 | limit, 82 | next: prev_addr, 83 | calculated: calculated_sender.clone(), 84 | } 85 | .spawn(); 86 | } 87 | 88 | Node { 89 | id: n_nodes, 90 | limit, 91 | next: prev_addr, 92 | calculated: calculated_sender, 93 | } 94 | }); 95 | 96 | println!( 97 | "Sending start message and waiting for termination after {} messages...", 98 | limit 99 | ); 100 | 101 | node.notify(Payload(1)).await.unwrap(); 102 | 103 | // We should wait for flow to be completed. 104 | calculated_receiver.next().await; 105 | 106 | match now.elapsed() { 107 | Ok(elapsed) => println!( 108 | "Time taken: {}.{:06} seconds ({} msg/second)", 109 | elapsed.as_secs(), 110 | elapsed.subsec_micros(), 111 | (n_nodes * n_rounds * 1000000) as u128 / elapsed.as_micros() 112 | ), 113 | Err(e) => println!("An error occurred: {:?}", e), 114 | } 115 | 116 | Ok(()) 117 | } 118 | 119 | #[allow(clippy::redundant_closure)] // Generates false positive to `-> !` functions. 120 | fn parse_args() -> (usize, usize) { 121 | let mut args = env::args(); 122 | 123 | // skip first arg 124 | args.next(); 125 | 126 | let n_nodes = args 127 | .next() 128 | .and_then(|val| val.parse::().ok()) 129 | .unwrap_or_else(|| print_usage_and_exit()); 130 | 131 | if n_nodes <= 1 { 132 | eprintln!("Number of nodes must be > 1"); 133 | ::std::process::exit(1); 134 | } 135 | 136 | let n_rounds = args 137 | .next() 138 | .and_then(|val| val.parse::().ok()) 139 | .unwrap_or_else(|| print_usage_and_exit()); 140 | 141 | if args.next().is_some() { 142 | print_usage_and_exit(); 143 | } 144 | 145 | (n_nodes, n_rounds) 146 | } 147 | 148 | fn print_usage_and_exit() -> ! { 149 | eprintln!("Usage:"); 150 | eprintln!("cargo run --example ring -- "); 151 | ::std::process::exit(1); 152 | } 153 | -------------------------------------------------------------------------------- /examples/05_timed_stream.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how to notify actor at a certain interval. 2 | //! It also demonstrates how to attach stream to an actor via its address. 3 | 4 | use std::time::{Duration, Instant}; 5 | 6 | use futures::StreamExt; 7 | use messages::prelude::*; 8 | use tokio_stream::wrappers::IntervalStream; 9 | 10 | #[derive(Debug)] 11 | pub struct Service { 12 | last_notified: Instant, 13 | } 14 | 15 | impl Actor for Service {} 16 | 17 | #[derive(Debug)] 18 | pub struct Notification; 19 | 20 | #[async_trait] 21 | impl Notifiable for Service { 22 | async fn notify(&mut self, _input: Notification, _: &Context) { 23 | println!( 24 | "Notified after {}ms", 25 | self.last_notified.elapsed().as_millis() 26 | ); 27 | self.last_notified = Instant::now(); 28 | } 29 | } 30 | 31 | impl Service { 32 | pub fn create() -> Self { 33 | Self { 34 | last_notified: Instant::now(), 35 | } 36 | } 37 | } 38 | 39 | #[tokio::main] 40 | async fn main() { 41 | // Start a service. 42 | let address = Service::create().spawn(); 43 | 44 | // Attach a stream that will ping the service every 100ms. 45 | // It will emit 10 values only. 46 | let interval_stream = IntervalStream::new(tokio::time::interval(Duration::from_millis(100))) 47 | .take(10) 48 | .map(|_| Notification); 49 | 50 | let join_handle = address.spawn_stream_forwarder(interval_stream); 51 | 52 | // Wait until stream yielded all its values. 53 | join_handle.await.unwrap().unwrap(); 54 | } 55 | -------------------------------------------------------------------------------- /examples/06_async_std.rs: -------------------------------------------------------------------------------- 1 | //! Basically the copy of `notify` example 2 | //! specialized for the `async-std` runtime. 3 | //! More comprehensive example that aims to show more of `message` 4 | //! crate functionality. 5 | //! 6 | //! In this example, both `Handler` and `Notifiable` traits are implemented, 7 | //! as well as additional methods of an `Actor` trait. 8 | 9 | use messages::prelude::*; 10 | 11 | #[derive(Debug, Default)] 12 | pub struct Service { 13 | value: u64, 14 | } 15 | 16 | #[async_trait] 17 | impl Actor for Service { 18 | // `started` method will be invoked *before* the first message 19 | // will be received. 20 | async fn started(&mut self) { 21 | println!("Service was started"); 22 | } 23 | 24 | // `stopping` method will be invoked when the actor will be requested 25 | // to stop its execution. 26 | async fn stopping(&mut self) -> ActorAction { 27 | println!("Service is stopping"); 28 | ActorAction::Stop 29 | } 30 | 31 | // `stopped` method will be invoked once actor is actually stopped. 32 | fn stopped(&mut self) { 33 | println!("Service has stopped"); 34 | } 35 | } 36 | 37 | // Type that we will use to send messages. 38 | #[derive(Debug)] 39 | pub struct Request(pub u64); 40 | 41 | #[async_trait] 42 | impl Handler for Service { 43 | type Result = u64; 44 | 45 | async fn handle(&mut self, input: Request, _: &Context) -> u64 { 46 | self.value + input.0 47 | } 48 | } 49 | 50 | // Type that we will use for notifications. 51 | #[derive(Debug)] 52 | pub struct Notification(pub u64); 53 | 54 | // Unlike `Handler`, `Notifiable` trait doesn't have output. 55 | // It only serves one purpose: deliver a message to an actor. 56 | // No response is expected. 57 | #[async_trait] 58 | impl Notifiable for Service { 59 | async fn notify(&mut self, input: Notification, _: &Context) { 60 | self.value = input.0; 61 | } 62 | } 63 | 64 | impl Service { 65 | pub fn new() -> Self { 66 | Self::default() 67 | } 68 | } 69 | 70 | #[async_std::main] 71 | async fn main() { 72 | // Start a service. 73 | let mut address = Service::new().spawn(); 74 | 75 | // Send a notification. 76 | address.notify(Notification(10)).await.unwrap(); 77 | 78 | // Send a request and receive a response. 79 | let response: u64 = address.send(Request(1)).await.unwrap(); 80 | assert_eq!(response, 11); 81 | 82 | // Stop service. 83 | address.stop().await; 84 | // Wait for service to stop. 85 | address.wait_for_stop().await; 86 | // Ensure that actor is not running anymore. 87 | assert!(!address.connected()); 88 | } 89 | -------------------------------------------------------------------------------- /examples/07_no_runtime.rs: -------------------------------------------------------------------------------- 1 | //! Example of using `messages` without runtime features. 2 | //! In this example we use `smol` to demonstrate some runtime 3 | //! not supported out of the box. 4 | 5 | use messages::prelude::*; 6 | 7 | struct Ping(usize); 8 | 9 | struct MyActor { 10 | count: usize, 11 | } 12 | 13 | impl Actor for MyActor {} 14 | 15 | #[async_trait] 16 | impl Handler for MyActor { 17 | type Result = usize; 18 | 19 | async fn handle(&mut self, msg: Ping, _: &Context) -> Self::Result { 20 | self.count += msg.0; 21 | self.count 22 | } 23 | } 24 | 25 | fn main() { 26 | smol::block_on(async { 27 | // Without runtime, we have to manually create the context in order 28 | // to obtain the actor's address. 29 | let context = Context::new(); 30 | let mut address = context.address(); 31 | let actor_handle = smol::spawn(context.run(MyActor { count: 10 })); 32 | 33 | // Send message and get the result. 34 | let res = address.send(Ping(10)).await; 35 | 36 | // Check whether actor returned the expected message. 37 | println!("RESULT: {}", res.unwrap() == 20); 38 | 39 | // Stop the actor. 40 | address.stop().await; 41 | address.wait_for_stop().await; 42 | actor_handle.await; 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /examples/08_websocket.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | mod server { 4 | use futures::{Sink, SinkExt, StreamExt}; 5 | use messages::prelude::*; 6 | use tokio::net::TcpListener; 7 | use tokio_tungstenite::tungstenite::{Error as WsError, Message}; 8 | 9 | pub struct WebSocketConnection> { 10 | response: T, 11 | } 12 | 13 | impl WebSocketConnection 14 | where 15 | T: Sink, 16 | { 17 | pub fn new(response: T) -> Self { 18 | Self { response } 19 | } 20 | } 21 | 22 | impl Actor for WebSocketConnection where T: Sink + Send + Sync + Unpin + 'static {} 23 | 24 | #[async_trait] 25 | impl Notifiable> for WebSocketConnection 26 | where 27 | T: Sink + Send + Sync + Unpin + 'static, 28 | { 29 | async fn notify(&mut self, input: Result, context: &Context) { 30 | let msg = match input { 31 | Ok(msg) => msg, 32 | Err(_err) => return, 33 | }; 34 | 35 | match msg { 36 | Message::Text(input) => { 37 | let _ = self.response.send(Message::Text(input)).await; 38 | } 39 | Message::Binary(input) => { 40 | let _ = self.response.send(Message::Binary(input)).await; 41 | } 42 | Message::Ping(ping) => { 43 | let _ = self.response.send(Message::Pong(ping)).await; 44 | } 45 | Message::Pong(_) => { 46 | // We don't send ping messages, do nothing. 47 | } 48 | Message::Close(_) => { 49 | context.address().stop().await; 50 | } 51 | } 52 | } 53 | } 54 | 55 | pub(super) async fn run(addr: String) { 56 | // Create the event loop and TCP listener we'll accept connections on. 57 | let try_socket = TcpListener::bind(&addr).await; 58 | let listener = try_socket.expect("Failed to bind"); 59 | println!("Listening on: {}", addr); 60 | 61 | while let Ok((stream, _)) = listener.accept().await { 62 | let (ws_sink, ws_stream) = tokio_tungstenite::accept_async(stream) 63 | .await 64 | .expect("Error during the websocket handshake occurred") 65 | .split(); 66 | let addr = WebSocketConnection::new(ws_sink).spawn(); 67 | addr.spawn_stream_forwarder(ws_stream); 68 | } 69 | } 70 | } 71 | 72 | mod client { 73 | //! Client implementation is respectfull borrowed from [`tokio-tungstenite`] [example]. 74 | //! It does not use actors, and is put here just so you can play with the server. 75 | //! 76 | //! [`tokio-tungstenite`]: https://github.com/snapview/tokio-tungstenite 77 | //! [example]: https://github.com/snapview/tokio-tungstenite/blob/master/examples/client.rs 78 | 79 | use futures::{future, pin_mut, StreamExt}; 80 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 81 | use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; 82 | 83 | pub(super) async fn run(connect_addr: String) { 84 | if !connect_addr.starts_with("ws://") { 85 | eprintln!("Server URL must start with `ws://`"); 86 | return; 87 | } 88 | 89 | let (stdin_tx, stdin_rx) = futures::channel::mpsc::unbounded(); 90 | tokio::spawn(read_stdin(stdin_tx)); 91 | 92 | let (ws_stream, _) = connect_async(connect_addr) 93 | .await 94 | .expect("Failed to connect"); 95 | println!("WebSocket handshake has been successfully completed"); 96 | 97 | let (write, read) = ws_stream.split(); 98 | 99 | let stdin_to_ws = stdin_rx.map(Ok).forward(write); 100 | let ws_to_stdout = { 101 | read.for_each(|message| async { 102 | let data = message.unwrap().into_data(); 103 | tokio::io::stdout().write_all(&data).await.unwrap(); 104 | }) 105 | }; 106 | 107 | pin_mut!(stdin_to_ws, ws_to_stdout); 108 | future::select(stdin_to_ws, ws_to_stdout).await; 109 | } 110 | 111 | // Our helper method which will read data from stdin and send it along the 112 | // sender provided. 113 | async fn read_stdin(tx: futures::channel::mpsc::UnboundedSender) { 114 | let mut stdin = tokio::io::stdin(); 115 | loop { 116 | let mut buf = vec![0; 1024]; 117 | let n = match stdin.read(&mut buf).await { 118 | Err(_) | Ok(0) => break, 119 | Ok(n) => n, 120 | }; 121 | buf.truncate(n); 122 | tx.unbounded_send(Message::binary(buf)).unwrap(); 123 | } 124 | } 125 | } 126 | 127 | #[tokio::main] 128 | async fn main() { 129 | let command = match env::args() 130 | .nth(1) 131 | .map(|s| s.to_ascii_lowercase()) 132 | .filter(|c| c == "server" || c == "client") 133 | { 134 | Some(command) => command, 135 | None => { 136 | println!( 137 | "Usage: `cargo run --example 08_websocket -- [client|server] `, \ 138 | where `arg` is either server address or bind address." 139 | ); 140 | return; 141 | } 142 | }; 143 | 144 | let arg = env::args() 145 | .nth(2) 146 | .unwrap_or_else(|| "127.0.0.1:8080".to_owned()); 147 | 148 | match command.as_ref() { 149 | "server" => server::run(arg).await, 150 | "client" => client::run(arg).await, 151 | _ => unreachable!(), 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/actor.rs: -------------------------------------------------------------------------------- 1 | //! Actor is an entity capable of receiving and processing messages. 2 | //! 3 | //! For details, see the [`Actor`] documentation. 4 | 5 | use async_trait::async_trait; 6 | 7 | use crate::{cfg_runtime, context::Context}; 8 | 9 | /// Action to be performed after `Actor::stopping` is called. 10 | /// 11 | /// In this method, actor can decide whether it will indeed stop 12 | /// or keep running. 13 | #[derive(Debug, Clone, Copy)] 14 | pub enum ActorAction { 15 | /// Ignore stop request and keep running. 16 | KeepRunning, 17 | /// Stop. 18 | Stop, 19 | } 20 | 21 | /// Actor is an entity capable of receiving and processing messages. 22 | /// 23 | /// Each actor runs in an associated [`Context`] object that is capable 24 | /// of managing the message delivery and maintaing the actor state. 25 | /// 26 | /// Minimal implementation of an actor doesn't require any methods on it 27 | /// and can be used on (almost) any type as a marker. 28 | /// 29 | /// However, optional methods for better control of the actor lifespan 30 | /// are provided: 31 | /// 32 | /// - `started` method is called once [`Context`] was launched and is about 33 | /// to start processing incoming messages. 34 | /// - `stopping` method is called after either [`Address::stop`] method was 35 | /// executed, or all the [`Address`] objects connected to an actor were 36 | /// dropped. 37 | /// - `stopped` method is a notification signaling that the [`Context`] future 38 | /// is finishing its execution and after this call `Actor` object will be 39 | /// dropped. 40 | /// 41 | /// ## Stopping 42 | /// 43 | /// Actor can stop in the following scenarios: 44 | /// 45 | /// - [`Address::stop`] method was invoked. 46 | /// - Runtime in which [`Context`] is spawned was shutdown. 47 | /// 48 | /// ## Prerequisites 49 | /// 50 | /// As actors must be suitable for using in the multithreaded runtimes, 51 | /// each type implementing `Actor` must be [`Send`], [`Sync`] and [`'static`][static_lt]. 52 | /// 53 | /// Additionally, it must implement [`Unpin`](std::marker::Unpin) 54 | /// 55 | /// [static_lt]: https://doc.rust-lang.org/reference/types/trait-object.html#trait-object-lifetime-bounds 56 | /// 57 | /// ## Extensions 58 | /// 59 | /// When one of the runtime features is enabled in this crate, there is also an extension trait available: 60 | /// [`RuntimeActorExt`], which provides more convenient interface for spawning actors, e.g. [`RuntimeActorExt::spawn`]. 61 | /// 62 | /// ## Examples 63 | /// 64 | /// This example assumes that `messages` is used with `rt-tokio` feature enabled. 65 | /// 66 | /// ```rust 67 | /// # use messages::prelude::*; 68 | /// 69 | /// struct Ping; 70 | /// 71 | /// #[async_trait] 72 | /// impl Actor for Ping { 73 | /// async fn started(&mut self) { 74 | /// println!("Actor was started"); 75 | /// } 76 | /// 77 | /// async fn stopping(&mut self) -> ActorAction { 78 | /// println!("Actor is stopping"); 79 | /// ActorAction::Stop 80 | /// } 81 | /// 82 | /// fn stopped(&mut self) { 83 | /// println!("Actor is stopped"); 84 | /// } 85 | /// } 86 | /// 87 | /// #[tokio::main] 88 | /// async fn main() { 89 | /// let mut addr = Ping.spawn(); 90 | /// addr.stop().await; 91 | /// addr.wait_for_stop().await; 92 | /// } 93 | /// ``` 94 | #[async_trait] 95 | pub trait Actor: Unpin + Send + Sync + Sized + 'static { 96 | /// Method called after [`Context::run`] method was invoked. 97 | /// 98 | /// It is guaranteed to be called *before* any message will be 99 | /// passed to an actor. 100 | async fn started(&mut self) {} 101 | 102 | /// Method called once actor finished processing messages. It can 103 | /// happen either after [`Address::stop`] was called or 104 | /// all the [`Address`] objects will be dropped. 105 | /// 106 | /// It is guaranteed that after invocation of this method there will 107 | /// be no messages passed to the actor (unless `ActorAction::KeepRunning`). 108 | async fn stopping(&mut self) -> ActorAction { 109 | ActorAction::Stop 110 | } 111 | 112 | /// Final notification about actor life end. Invoking this method 113 | /// will only be followed by the destruction of a [`Context`] object. 114 | fn stopped(&mut self) {} 115 | 116 | /// Creates [`Context`] object and starts the message processing loop. 117 | /// 118 | /// Future returned by this method should not normally be directly `await`ed, 119 | /// but rather is expected to be used in some kind of `spawn` function of 120 | /// the used runtime (e.g. `tokio::spawn` or `async_std::task::spawn`). 121 | async fn run(self) { 122 | Context::new().run(self).await; 123 | } 124 | 125 | /// Alternative to `run` function that should be used if an actor 126 | /// needs access to the [`Context`] object to be created (e.g. to 127 | /// know its own address). 128 | async fn create_and_run(f: F) 129 | where 130 | F: FnOnce(&Context) -> Self + Send, 131 | { 132 | let mut context = Context::new(); 133 | let this = f(&mut context); 134 | context.run(this).await; 135 | } 136 | } 137 | 138 | cfg_runtime! { 139 | use crate::address::Address; 140 | 141 | /// Extension trait for `Actor` providing more convenient interface when 142 | /// one of the runtime features is enabled. 143 | pub trait RuntimeActorExt: Actor { 144 | /// Spawns an actor using supported runtime. 145 | /// 146 | /// Returns an address of this actor. 147 | fn spawn(self) -> Address { 148 | Context::new().spawn(self) 149 | } 150 | 151 | /// Same as [`Actor::create_and_run`], but spawns 152 | /// the future instead of returning it. 153 | /// 154 | /// Returns an address of this actor. 155 | fn create_and_spawn(f: F) -> Address 156 | where 157 | F: FnOnce(&Context) -> Self + Send, 158 | { 159 | let mut context = Context::new(); 160 | let this = f(&mut context); 161 | context.spawn(this) 162 | } 163 | } 164 | 165 | impl RuntimeActorExt for T 166 | where 167 | T: Actor 168 | {} 169 | } 170 | -------------------------------------------------------------------------------- /src/address.rs: -------------------------------------------------------------------------------- 1 | //! An address of an actor. 2 | //! 3 | //! See [`Actor`] documentation for details. 4 | 5 | use std::sync::Arc; 6 | 7 | use crate::{ 8 | actor::Actor, 9 | cfg_runtime, 10 | context::{InputHandle, Signal}, 11 | envelope::{EnvelopeProxy, MessageEnvelope, NotificationEnvelope}, 12 | errors::SendError, 13 | handler::{Handler, Notifiable}, 14 | }; 15 | use futures::{lock::Mutex, Stream, StreamExt}; 16 | 17 | /// `Address` is an object used to communicate with [`Actor`]s. 18 | /// 19 | /// Assuming that [`Actor`] is capable of processing messages of a certain 20 | /// type, the [`Address`] can be used to interact with [`Actor`] by using 21 | /// either [`Address::send`] (for messages) or [`Address::notify`] (for notifications). 22 | pub struct Address { 23 | sender: async_channel::Sender>>, 24 | stop_handle: Arc>, 25 | } 26 | 27 | impl std::fmt::Debug for Address { 28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 | f.debug_struct("Address").finish() 30 | } 31 | } 32 | 33 | impl Clone for Address { 34 | fn clone(&self) -> Self { 35 | Self { 36 | sender: self.sender.clone(), 37 | stop_handle: self.stop_handle.clone(), 38 | } 39 | } 40 | } 41 | 42 | impl Address { 43 | pub(crate) fn new( 44 | sender: async_channel::Sender>>, 45 | stop_handle: Arc>, 46 | ) -> Self { 47 | Self { 48 | sender, 49 | stop_handle, 50 | } 51 | } 52 | 53 | /// Sends a message to the [`Actor`] and receives the response. 54 | /// 55 | /// ## Examples 56 | /// 57 | /// This example assumes that `messages` is used with `rt-tokio` feature enabled. 58 | /// 59 | /// ```rust 60 | /// # use messages::prelude::*; 61 | /// 62 | /// struct Sum; 63 | /// 64 | /// #[async_trait] 65 | /// impl Actor for Sum {} 66 | /// 67 | /// #[async_trait] 68 | /// impl Handler<(u8, u8)> for Sum { 69 | /// type Result = u16; 70 | /// // Implementation omitted. 71 | /// # async fn handle(&mut self, (a, b): (u8, u8), context: &Context) -> u16 { 72 | /// # (a as u16) + (b as u16) 73 | /// # } 74 | /// } 75 | /// 76 | /// #[tokio::main] 77 | /// async fn main() { 78 | /// let mut addr = Sum.spawn(); 79 | /// let result = addr.send((22, 20)).await.unwrap(); 80 | /// assert_eq!(result, 42); 81 | /// # addr.stop().await; 82 | /// # addr.wait_for_stop().await; 83 | /// } 84 | /// ``` 85 | /// 86 | /// ## Errors 87 | /// 88 | /// Will return an error in case associated actor stopped working. 89 | pub async fn send(&mut self, message: IN) -> Result 90 | where 91 | A: Actor + Send + Handler + 'static, 92 | IN: Send + 'static, 93 | A::Result: Send + Sync + 'static, 94 | { 95 | let (sender, receiver) = async_oneshot::oneshot(); 96 | let envelope: MessageEnvelope = MessageEnvelope::new(message, sender); 97 | 98 | let message = Box::new(envelope) as Box + Send + 'static>; 99 | 100 | self.sender 101 | .send(Signal::Message(message)) 102 | .await 103 | .map_err(|_| SendError::ReceiverDisconnected)?; 104 | 105 | receiver.await.map_err(|_| SendError::ReceiverDisconnected) 106 | } 107 | 108 | /// Sends a notification to the [`Actor`] without receiving any kind of response. 109 | /// 110 | /// ## Examples 111 | /// 112 | /// This example assumes that `messages` is used with `rt-tokio` feature enabled. 113 | /// 114 | /// ```rust 115 | /// # use messages::prelude::*; 116 | /// 117 | /// struct Ping; 118 | /// 119 | /// #[async_trait] 120 | /// impl Actor for Ping {} 121 | /// 122 | /// #[async_trait] 123 | /// impl Notifiable for Ping { 124 | /// async fn notify(&mut self, input: u8, context: &Context) { 125 | /// println!("Received number {}", input); 126 | /// } 127 | /// } 128 | /// 129 | /// #[tokio::main] 130 | /// async fn main() { 131 | /// let mut addr = Ping.spawn(); 132 | /// addr.notify(42).await.unwrap(); 133 | /// # addr.stop().await; 134 | /// # addr.wait_for_stop().await; 135 | /// } 136 | /// ``` 137 | /// 138 | /// ## Errors 139 | /// 140 | /// Will return an error in case associated actor stopped working. 141 | pub async fn notify(&mut self, message: IN) -> Result<(), SendError> 142 | where 143 | A: Actor + Send + Notifiable + 'static, 144 | IN: Send + 'static, 145 | { 146 | let envelope: NotificationEnvelope = NotificationEnvelope::new(message); 147 | 148 | let message = Box::new(envelope) as Box + Send + 'static>; 149 | 150 | self.sender 151 | .send(Signal::Message(message)) 152 | .await 153 | .map_err(|_| SendError::ReceiverDisconnected)?; 154 | 155 | Ok(()) 156 | } 157 | 158 | /// Combines provided stream and this `Address` object, returning a future 159 | /// that will run while stream yields messages and send them to the server. 160 | /// 161 | /// [`Actor`] associated with this `Address` must implmenet [`Notifiable`] trait 162 | /// to process messages from the stream. 163 | /// 164 | /// Future returned by this method should not normally be directly `await`ed, 165 | /// but rather is expected to be used in some kind of `spawn` function of 166 | /// the used runtime (e.g. `tokio::spawn` or `async_std::task::spawn`). 167 | /// 168 | /// ## Errors 169 | /// 170 | /// Will return an error in case associated actor stopped working. 171 | pub async fn into_stream_forwarder(mut self, mut stream: S) -> Result<(), SendError> 172 | where 173 | A: Actor + Send + Notifiable + 'static, 174 | S: Send + Stream + Unpin, 175 | IN: Send + 'static, 176 | { 177 | while let Some(message) = stream.next().await { 178 | self.notify(message).await?; 179 | } 180 | Ok(()) 181 | } 182 | 183 | /// Returns `true` if `Address` is still connected to the [`Actor`]. 184 | #[must_use] 185 | pub fn connected(&self) -> bool { 186 | !self.sender.is_closed() 187 | } 188 | 189 | /// Sends a stop request to the corresponding [`Actor`]. 190 | /// 191 | /// Sending this message does not mean that actor will be stopped immediately. 192 | /// In order to make sure that the actor is stopped, [`Address::wait_for_stop`] 193 | /// should be used. 194 | /// 195 | /// Does nothing if address is disconnected from the actor or actor already has 196 | /// been stopped. 197 | pub async fn stop(&mut self) { 198 | // If actor is already stopped, we're fine with it. 199 | drop(self.sender.send(Signal::Stop).await); 200 | } 201 | 202 | /// Creates a future that waits for actor to be fully stopped. 203 | /// 204 | /// Note that this method does not request an actor to stop, it only waits for it 205 | /// in order to stop actor, [`Address::stop`] should be used. 206 | pub async fn wait_for_stop(&self) { 207 | // We will only able to obtain the lock when context will release it. 208 | // However, we don't want to exit early in case this method is called 209 | // before actor is actually started, so we do it in the loop until 210 | // the channel is disconnected. 211 | while self.connected() { 212 | self.stop_handle.lock().await; 213 | } 214 | } 215 | } 216 | 217 | cfg_runtime! { 218 | 219 | use crate::{ 220 | handler::Coroutine, 221 | envelope::CoroutineEnvelope 222 | }; 223 | 224 | impl Address { 225 | /// Version of [`Address::into_stream_forwarder`] that automatically spawns the future. 226 | /// 227 | /// Returned future is the join handle of the spawned task, e.g. it can be awaited 228 | /// if the user is interested in the moment when the stream stopped sending messages. 229 | pub fn spawn_stream_forwarder(self, stream: S) -> crate::runtime::JoinHandle> 230 | where 231 | A: Actor + Send + Notifiable + 'static, 232 | S: Send + Stream + Unpin + 'static, 233 | IN: Send + 'static, 234 | { 235 | crate::runtime::spawn(self.into_stream_forwarder(stream)) 236 | } 237 | 238 | 239 | /// Sends a message to the [`Actor`] and receives the response. 240 | /// Unlike in [`Address::send`], `calculate` supports parallel execution. 241 | /// 242 | /// ## Examples 243 | /// 244 | /// This example assumes that `messages` is used with `rt-tokio` feature enabled. 245 | /// 246 | /// ```rust 247 | /// # use messages::prelude::*; 248 | /// #[derive(Clone)] 249 | /// struct Sum; 250 | /// 251 | /// #[async_trait] 252 | /// impl Actor for Sum {} 253 | /// 254 | /// #[async_trait] 255 | /// impl Coroutine<(u8, u8)> for Sum { 256 | /// type Result = u16; 257 | /// async fn calculate(self, (a, b): (u8, u8)) -> u16 { 258 | /// (a as u16) + (b as u16) 259 | /// } 260 | /// } 261 | /// 262 | /// #[tokio::main] 263 | /// async fn main() { 264 | /// let mut addr = Sum.spawn(); 265 | /// let result = addr.calculate((22, 20)).await.unwrap(); 266 | /// assert_eq!(result, 42); 267 | /// # addr.stop().await; 268 | /// # addr.wait_for_stop().await; 269 | /// } 270 | /// ``` 271 | pub async fn calculate(&self, message: IN) -> Result 272 | where 273 | A: Actor + Send + Coroutine + 'static, 274 | IN: Send + 'static, 275 | A::Result: Send + Sync + 'static, 276 | { 277 | let addr = self.sender.clone(); 278 | let (sender, receiver) = async_oneshot::oneshot(); 279 | let envelope: CoroutineEnvelope = CoroutineEnvelope::new(message, sender); 280 | 281 | let message = Box::new(envelope) as Box + Send + 'static>; 282 | 283 | addr 284 | .send(Signal::Message(message)) 285 | .await 286 | .map_err(|_| SendError::ReceiverDisconnected)?; 287 | 288 | receiver.await.map_err(|_| SendError::ReceiverDisconnected) 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | //! Context represents an environment in which actor is being executed. 2 | //! 3 | //! For details, see the [`Context`] documentation. 4 | 5 | use std::{pin::Pin, sync::Arc}; 6 | 7 | use crate::{ 8 | actor::{Actor, ActorAction}, 9 | address::Address, 10 | cfg_runtime, 11 | envelope::EnvelopeProxy, 12 | }; 13 | use futures::{lock::Mutex, StreamExt}; 14 | 15 | #[derive(Debug)] 16 | pub(crate) enum Signal { 17 | Message(Msg), 18 | Stop, 19 | } 20 | 21 | /// Default capacity for the mailbox. 22 | pub const DEFAULT_CAPACITY: usize = 128; 23 | 24 | pub(crate) type InputHandle = Box + Send + 'static>; 25 | 26 | /// `Context` represents an environment in which actor is being executed. 27 | /// 28 | /// It is capable of transferring incoming messages to the actor, providing 29 | /// actor's address and managing it lifetime (e.g. stopping it). 30 | pub struct Context { 31 | receiver: async_channel::Receiver>>, 32 | address: Address, 33 | stop_handle: Arc>, 34 | } 35 | 36 | impl std::fmt::Debug for Context { 37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 38 | f.debug_struct("Context").finish() 39 | } 40 | } 41 | 42 | impl Default for Context 43 | where 44 | ACTOR: 'static + Send + Actor + Unpin, 45 | { 46 | fn default() -> Self { 47 | Self::new() 48 | } 49 | } 50 | 51 | impl Context 52 | where 53 | ACTOR: 'static + Send + Actor + Unpin, 54 | { 55 | /// Creates a new `Context` object with default capacity (128 elements). 56 | #[must_use] 57 | pub fn new() -> Self { 58 | Self::with_capacity(DEFAULT_CAPACITY) 59 | } 60 | 61 | /// Creates a new `Context` object with custom capacity. 62 | #[must_use] 63 | pub fn with_capacity(capacity: usize) -> Self { 64 | let (sender, receiver) = async_channel::bounded(capacity); 65 | 66 | let stop_handle = Arc::new(Mutex::new(())); 67 | let address = Address::new(sender, stop_handle.clone()); 68 | 69 | Self { 70 | receiver, 71 | address, 72 | stop_handle, 73 | } 74 | } 75 | 76 | /// Returns an address of the actor. 77 | #[must_use] 78 | pub fn address(&self) -> Address { 79 | self.address.clone() 80 | } 81 | 82 | /// Starts the message handling routine. 83 | /// 84 | /// Future returned by this method should not normally be directly `await`ed, 85 | /// but rather is expected to be used in some kind of `spawn` function of 86 | /// the used runtime (e.g. `tokio::spawn` or `async_std::task::spawn`). 87 | pub async fn run(mut self, mut actor: ACTOR) { 88 | #![allow(clippy::mut_mut)] // Warning is spawned because of `futures::select`. 89 | 90 | // Acquire the lock on the mutex so addresses can be used to `await` until 91 | // the actor is stopped. 92 | let stop_handle = self.stop_handle.clone(); 93 | let _mutex_handle = stop_handle.lock().await; 94 | 95 | actor.started().await; 96 | 97 | let mut running = true; 98 | while running { 99 | match self.receiver.next().await { 100 | Some(Signal::Message(mut envelope)) => { 101 | let actor_pin = Pin::new(&mut actor); 102 | let self_pin = Pin::new(&self); 103 | envelope.handle(actor_pin, self_pin).await; 104 | } 105 | Some(Signal::Stop) | None => { 106 | // Notify actor about being stopped. 107 | if let ActorAction::Stop = actor.stopping().await { 108 | // Actor agreed to stop, so actually stop the loop. 109 | running = false; 110 | } 111 | } 112 | } 113 | } 114 | 115 | // Notify actor that it was fully stopped. 116 | actor.stopped(); 117 | } 118 | } 119 | 120 | cfg_runtime! { 121 | use futures::FutureExt; 122 | 123 | impl Context 124 | where 125 | ACTOR: 'static + Send + Actor + Unpin, 126 | { 127 | /// Spawns an actor and returns its address. 128 | pub fn spawn(self, actor: ACTOR) -> Address { 129 | let address = self.address(); 130 | let _handle = crate::runtime::spawn(self.run(actor)).boxed(); 131 | address 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/envelope.rs: -------------------------------------------------------------------------------- 1 | //! Envelope is an entity capable of encapsulating the sent message 2 | //! together with a way to report the result back to the sender (if needed). 3 | //! It consists of two parts: 4 | //! 5 | //! - `EnvelopeProxy` trait that is being used by the `Context` to 6 | //! pass the message to the actor (which is only accessable by 7 | //! the `Context` itself). 8 | //! - `MessageEnvelope` and `NotificationEnvelope` structures that 9 | //! actually have the message inside of them and implement `EnvelopeProxy`. 10 | //! 11 | //! The way it works is as follows: 12 | //! 13 | //! - User calls `Address::send` / `Address::notify` with a message that 14 | //! can be handled by the corresponding `Actor` type. 15 | //! - `Address` creates an `*Envelope` object and converts it to the 16 | //! `Box`. Information about the message type is now 17 | //! elided and we can consider different messages to be of the same type. 18 | //! - This "envelope" is sent to the `Context` through a channel. 19 | //! - Once `Context` processes envelope, it creates `Pin`s to both itself 20 | //! and `Actor` and calls `EnvelopeProxy::handle` to process the message. 21 | 22 | use std::pin::Pin; 23 | 24 | use async_trait::async_trait; 25 | 26 | use crate::{ 27 | cfg_runtime, 28 | prelude::{Actor, Context, Handler, Notifiable}, 29 | }; 30 | 31 | #[async_trait] 32 | pub(crate) trait EnvelopeProxy: Send + 'static { 33 | async fn handle(&mut self, actor: Pin<&mut A>, context: Pin<&Context>); 34 | } 35 | 36 | pub(crate) struct MessageEnvelope, IN> { 37 | data: Option<(IN, async_oneshot::Sender)>, 38 | } 39 | 40 | impl MessageEnvelope 41 | where 42 | A: Handler, 43 | { 44 | pub(crate) fn new(message: IN, response: async_oneshot::Sender) -> Self { 45 | Self { 46 | data: Some((message, response)), 47 | } 48 | } 49 | } 50 | 51 | #[async_trait] 52 | impl EnvelopeProxy for MessageEnvelope 53 | where 54 | A: Handler + Actor + Send + Unpin, 55 | IN: Send + 'static, 56 | A::Result: Send + Sync + 'static, 57 | { 58 | async fn handle(&mut self, actor: Pin<&mut A>, context: Pin<&Context>) { 59 | let (message, mut response) = self.data.take().expect("`Envelope::handle` called twice"); 60 | 61 | let result = actor 62 | .get_mut() 63 | .handle(message, Pin::into_inner(context)) 64 | .await; 65 | let _ = response.send(result); 66 | } 67 | } 68 | 69 | pub(crate) struct NotificationEnvelope, IN> { 70 | message: Option, 71 | _marker: std::marker::PhantomData, 72 | } 73 | 74 | impl NotificationEnvelope 75 | where 76 | A: Notifiable, 77 | { 78 | pub(crate) fn new(message: IN) -> Self { 79 | Self { 80 | message: Some(message), 81 | _marker: std::marker::PhantomData, 82 | } 83 | } 84 | } 85 | 86 | #[async_trait] 87 | impl EnvelopeProxy for NotificationEnvelope 88 | where 89 | A: Notifiable + Actor + Send + Unpin, 90 | IN: Send + 'static, 91 | { 92 | async fn handle(&mut self, actor: Pin<&mut A>, context: Pin<&Context>) { 93 | let message = self 94 | .message 95 | .take() 96 | .expect("`Envelope::handle` called twice"); 97 | 98 | actor 99 | .get_mut() 100 | .notify(message, Pin::into_inner(context)) 101 | .await; 102 | } 103 | } 104 | 105 | cfg_runtime! { 106 | use crate::handler::Coroutine; 107 | 108 | pub(crate) struct CoroutineEnvelope, IN> { 109 | data: Option<(IN, async_oneshot::Sender)>, 110 | } 111 | 112 | impl CoroutineEnvelope 113 | where 114 | A: Coroutine, 115 | { 116 | pub(crate) fn new(message: IN, response: async_oneshot::Sender) -> Self { 117 | Self { 118 | data: Some((message, response)), 119 | } 120 | } 121 | } 122 | 123 | #[async_trait] 124 | impl EnvelopeProxy for CoroutineEnvelope 125 | where 126 | A: Coroutine + Actor + Send + Unpin, 127 | IN: Send + 'static, 128 | A::Result: Send + Sync + 'static, 129 | { 130 | async fn handle(&mut self, actor: Pin<&mut A>, _context: Pin<&Context>) { 131 | let actor = Pin::into_inner(actor).clone(); 132 | let (message, mut response) = self 133 | .data 134 | .take() 135 | .expect("`Envelope::handle` called twice"); 136 | 137 | crate::runtime::spawn(async move { 138 | let result = actor.calculate(message).await; 139 | let _ = response.send(result); 140 | }); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Errors that can occur during the actor interaction workflow. 2 | 3 | /// Errors that can occur while sending the message. 4 | #[derive(Debug)] 5 | pub enum SendError { 6 | /// Error emitted when it was attempted to send a message to the stopped actor. 7 | ReceiverDisconnected, 8 | } 9 | 10 | impl std::fmt::Display for SendError { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | write!(f, "Actor has disconnected and is not accepting messages.") 13 | } 14 | } 15 | 16 | impl std::error::Error for SendError {} 17 | -------------------------------------------------------------------------------- /src/handler.rs: -------------------------------------------------------------------------------- 1 | //! In order for an [`Actor`] to be able to process messages, 2 | //! it should have logic associated with them. 3 | //! 4 | //! For that matter, `messages` provides two traits: 5 | //! 6 | //! - [`Notifiable`]: handler for notifications, e.g. messages that do not require response. 7 | //! - [`Handler`]: handler that produces some data as a response to the sent message. 8 | //! 9 | //! Note that [`Actor`] can implement both [`Notifiable`] and [`Handler`] traits in case 10 | //! the calculated data is important for some modules, but not so much for others. 11 | //! 12 | //! [`Notifiable`] crate is generally more performant than [`Handler`] since it does not 13 | //! include overhead to return result back to the original message sender. 14 | 15 | use async_trait::async_trait; 16 | 17 | use crate::{ 18 | cfg_runtime, 19 | prelude::{Actor, Context}, 20 | }; 21 | 22 | /// `Notifiable` is an extension trait for [`Actor`] that enables it 23 | /// to process notifications. 24 | /// 25 | /// **Note:** Handler workflow guarantees that sent messages will be delivered in 26 | /// order. 27 | /// 28 | /// ## Examples 29 | /// 30 | /// This example assumes that `messages` is used with `rt-tokio` feature enabled. 31 | /// 32 | /// ```rust 33 | /// # use messages::prelude::*; 34 | /// struct Ping; 35 | /// 36 | /// #[async_trait] 37 | /// impl Actor for Ping {} 38 | /// 39 | /// #[async_trait] 40 | /// impl Notifiable for Ping { 41 | /// async fn notify(&mut self, input: u8, context: &Context) { 42 | /// println!("Received number {}", input); 43 | /// } 44 | /// } 45 | /// 46 | /// #[tokio::main] 47 | /// async fn main() { 48 | /// let mut addr = Ping.spawn(); 49 | /// addr.notify(42).await.unwrap(); 50 | /// # addr.stop().await; 51 | /// # addr.wait_for_stop().await; 52 | /// } 53 | /// ``` 54 | #[async_trait] 55 | pub trait Notifiable: Sized + Actor { 56 | /// Processes notification. 57 | async fn notify(&mut self, input: IN, context: &Context); 58 | } 59 | 60 | /// `Handler` is an extension trait for [`Actor`] that enables it 61 | /// to process messages and return results of the message processing. 62 | /// 63 | /// **Note:** Handler workflow guarantees that sent messages will be delivered in 64 | /// order. 65 | /// 66 | /// ## Examples 67 | /// 68 | /// This example assumes that `messages` is used with `rt-tokio` feature enabled. 69 | /// 70 | /// ```rust 71 | /// # use messages::prelude::*; 72 | /// struct Sum; 73 | /// 74 | /// #[async_trait] 75 | /// impl Actor for Sum {} 76 | /// 77 | /// #[async_trait] 78 | /// impl Handler<(u8, u8)> for Sum { 79 | /// type Result = u16; 80 | /// 81 | /// async fn handle(&mut self, (a, b): (u8, u8), context: &Context) -> u16 { 82 | /// (a as u16) + (b as u16) 83 | /// } 84 | /// } 85 | /// 86 | /// #[tokio::main] 87 | /// async fn main() { 88 | /// let mut addr = Sum.spawn(); 89 | /// let result = addr.send((22, 20)).await.unwrap(); 90 | /// assert_eq!(result, 42); 91 | /// # addr.stop().await; 92 | /// # addr.wait_for_stop().await; 93 | /// } 94 | /// ``` 95 | #[async_trait] 96 | pub trait Handler: Sized + Actor { 97 | /// Result of the message processing. 98 | type Result; 99 | 100 | /// Processes a message. 101 | async fn handle(&mut self, input: IN, context: &Context) -> Self::Result; 102 | } 103 | 104 | cfg_runtime! { 105 | 106 | /// Alternative to [`Handler`] that allows parallel message processing. 107 | /// 108 | /// By default, messages for an [`Actor`] are processed sequentially, thus efficiently 109 | /// will use a single core. 110 | /// 111 | /// However, in some cases it makes more sense to allow parallel processing, if it's some 112 | /// kind of caluclation or an access to a shared resource. 113 | /// 114 | /// ## Examples 115 | /// 116 | /// This example assumes that `messages` is used with `rt-tokio` feature enabled. 117 | /// 118 | /// ```rust 119 | /// # use messages::prelude::*; 120 | /// #[derive(Clone)] 121 | /// struct Sum; 122 | /// 123 | /// #[async_trait] 124 | /// impl Actor for Sum {} 125 | /// 126 | /// #[async_trait] 127 | /// impl Coroutine<(u8, u8)> for Sum { 128 | /// type Result = u16; 129 | /// 130 | /// // Note that in this impl the first argument is `self` rather than `&mut self` 131 | /// // and there is no `Context` argument. 132 | /// async fn calculate(self, (a, b): (u8, u8)) -> u16 { 133 | /// (a as u16) + (b as u16) 134 | /// } 135 | /// } 136 | /// 137 | /// #[tokio::main] 138 | /// async fn main() { 139 | /// let mut addr = Sum.spawn(); 140 | /// let result = addr.calculate((22, 20)).await.unwrap(); 141 | /// assert_eq!(result, 42); 142 | /// # addr.stop().await; 143 | /// # addr.wait_for_stop().await; 144 | /// } 145 | /// ``` 146 | #[async_trait] 147 | pub trait Coroutine: Sized + Actor + Clone { 148 | /// Result of the message processing. 149 | type Result; 150 | 151 | /// Processes a message. 152 | async fn calculate(self, input: IN) -> Self::Result; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_cfg))] 2 | #![warn( 3 | missing_debug_implementations, 4 | rust_2018_idioms, 5 | missing_docs, 6 | unreachable_pub 7 | )] 8 | #![warn(clippy::pedantic)] 9 | #![allow(clippy::module_name_repetitions)] 10 | // Seems to be broken right now: https://github.com/rust-lang/rust-clippy/issues/8300 11 | #![allow(clippy::no_effect_underscore_binding)] 12 | 13 | //! `messages` is a runtime-agnostic actor library. 14 | //! 15 | //! It is heavily inspired by [`actix`][actix], a great actor framework. 16 | //! 17 | //! This crate can be used with any runtime, whether it popular or not. 18 | //! However, for the biggest one (`tokio` and `async-std`) there is an optional 19 | //! built-in support enabling more convenient interface (such as an automatic 20 | //! actor spawning). 21 | //! 22 | //! [actix]: https://crates.io/crates/actix 23 | //! 24 | //! ## Asyncness 25 | //! 26 | //! In order to provide convenient interface, this crate uses [`async_trait`](https://docs.rs/async-trait/) 27 | //! to declare traits with `async` methods. 28 | //! To make the experience more convenient, `async_trait::async_trait` macro is publicly re-exported 29 | //! in the [`prelude`] module. 30 | //! 31 | //! ## Examples 32 | //! 33 | //! ### With runtime features 34 | //! 35 | //! ```rust 36 | //! use messages::prelude::*; 37 | //! 38 | //! struct Example; // Most of the types can be an actor. 39 | //! 40 | //! // While `Actor` implementation can be customized, it is not required. 41 | //! #[async_trait] 42 | //! impl Actor for Example {} 43 | //! 44 | //! // Message handler that calculated sum of two numbers. 45 | //! #[async_trait] 46 | //! impl Handler<(u8, u8)> for Example { 47 | //! type Result = u16; 48 | //! async fn handle(&mut self, (a, b): (u8, u8), context: &Context) -> u16 { 49 | //! (a as u16) + (b as u16) 50 | //! } 51 | //! } 52 | //! 53 | //! // Notification handler that calculated just writes received number to stdout. 54 | //! #[async_trait] 55 | //! impl Notifiable for Example { 56 | //! async fn notify(&mut self, input: u8, context: &Context) { 57 | //! println!("Received number {}", input); 58 | //! } 59 | //! } 60 | //! 61 | //! #[tokio::main] 62 | //! async fn main() { 63 | //! let mut addr = Example.spawn(); 64 | //! let result = addr.send((22, 20)).await.unwrap(); 65 | //! assert_eq!(result, 42); 66 | //! addr.notify(42).await.unwrap(); 67 | //! addr.stop().await; 68 | //! addr.wait_for_stop().await; 69 | //! } 70 | //! ``` 71 | //! 72 | //! ### Without runtime features 73 | //! 74 | //! ```rust 75 | //! use messages::prelude::*; 76 | //! 77 | //! struct Ping; 78 | //! 79 | //! #[async_trait] 80 | //! impl Actor for Ping {} 81 | //! 82 | //! #[async_trait] 83 | //! impl Handler for Ping { 84 | //! type Result = u8; 85 | //! async fn handle(&mut self, input: u8, context: &Context) -> u8 { 86 | //! input 87 | //! } 88 | //! } 89 | //! 90 | //! #[tokio::main] 91 | //! async fn main() { 92 | //! let context = Context::new(); 93 | //! let mut addr = context.address(); 94 | //! let actor = Ping; 95 | //! // Could've been any other runtime. 96 | //! let mut task_handle = tokio::spawn(context.run(actor)); 97 | //! let result = addr.send(42).await.unwrap(); 98 | //! assert_eq!(result, 42); 99 | //! addr.stop().await; 100 | //! addr.wait_for_stop().await; 101 | //! task_handle.await.unwrap(); 102 | //! } 103 | //! ``` 104 | //! 105 | //! ## Main entities 106 | //! 107 | //! Main entites of this crate: 108 | //! 109 | //! - [`Actor`](crate::prelude::Actor): definition of an actor. 110 | //! - [`Context`](crate::prelude::Context): execution context for an actor. 111 | //! - [`Address`](crate::prelude::Address): address of an actor that is used to communicate with it. 112 | //! - Handler traits: [`Handler`](crate::prelude::Handler) and [`Notifiable`](crate::prelude::Notifiable). 113 | //! 114 | //! With runtime features enabled, there are also several more points of interest: 115 | //! 116 | //! - [`Registry`](crate::prelude::Registry): Collection of independent, unique and named actors. 117 | //! - [`Service`](crate::prelude::Service): Actor that can be stored in the registry. 118 | //! - [`Coroutine`](crate::prelude::Coroutine): Alternative to the `Handler` trait that allows 119 | //! parallel message processing. 120 | //! 121 | 122 | /// Collection of the main types required to work with `messages` crate. 123 | pub mod prelude { 124 | /// Convenience re-export of [`async_trait`](https://docs.rs/async-trait/) proc-macro. 125 | pub use async_trait::async_trait; 126 | 127 | pub use crate::{ 128 | actor::{Actor, ActorAction}, 129 | address::Address, 130 | context::Context, 131 | errors::SendError, 132 | handler::{Handler, Notifiable}, 133 | }; 134 | 135 | super::cfg_runtime! { 136 | pub use crate::registry::{Service, Registry}; 137 | pub use crate::actor::RuntimeActorExt; 138 | pub use crate::handler::Coroutine; 139 | 140 | /// Re-export of `JoinHandle` of chosen runtime. 141 | #[cfg_attr(not(docsrs), doc(hidden))] 142 | // ^ Kludge: `cargo deadlinks` finds a broken link in the tokio docs, 143 | // and currently it's not possible to ignore that error. 144 | // However, we don't want to completely hide this element. 145 | pub use crate::runtime::JoinHandle; 146 | } 147 | } 148 | 149 | pub mod actor; 150 | pub mod address; 151 | pub mod context; 152 | pub mod errors; 153 | pub mod handler; 154 | 155 | cfg_runtime! { 156 | pub mod registry; 157 | } 158 | 159 | mod envelope; 160 | mod runtime; 161 | -------------------------------------------------------------------------------- /src/registry.rs: -------------------------------------------------------------------------------- 1 | //! [`Registry`] provides a way to get addresses of singleton-like addresses 2 | //! by automatically managing their lifetime under the hood. 3 | //! 4 | //! This module is awailable only when `messages` is build with one of the supported 5 | //! runtime features enabled, as it needs to spawn actors. 6 | 7 | use std::{ 8 | any::{Any, TypeId}, 9 | collections::HashMap, 10 | }; 11 | 12 | use futures::lock::Mutex; 13 | use once_cell::sync::Lazy; 14 | 15 | use crate::prelude::{Actor, Address, RuntimeActorExt}; 16 | 17 | static REGISTRY: Lazy = Lazy::new(Registry::new); 18 | 19 | /// Extension of an [`Actor`] that can be managed by [`Registry`]. 20 | pub trait Service: Actor + Default { 21 | /// Service name. 22 | /// 23 | /// **Note:** All the service names must be unique. Having two services 24 | /// with the same name may result in [`Registry::service`] function panic. 25 | const NAME: &'static str; 26 | } 27 | 28 | /// `Registry` is an manager object providing access to the addresses 29 | /// of [`Actor`]s that implement [`Service`] trait. 30 | /// 31 | /// `Registry` maintains a list of spawned services and when an address 32 | /// of a service is requested, it checks whether the corresponding actor is 33 | /// already running. If so, address of this actor is returned. Otherwise, 34 | /// actor is spawned first. 35 | /// 36 | /// ## Stopping and resuming 37 | /// 38 | /// Services managed by the `Registry` may still be stopped via the [`Address::stop`] method. 39 | /// In that case, actor will not be resumed automatically, but it will be started again 40 | /// if its adress will be requested one more time. 41 | /// 42 | /// ## Examples 43 | /// 44 | /// ```rust 45 | /// # use messages::prelude::*; 46 | /// 47 | /// #[derive(Default)] // Default trait is required for Registry to automatically creaate actor. 48 | /// struct Ping; 49 | /// 50 | /// #[async_trait] 51 | /// impl Actor for Ping {} 52 | /// 53 | /// #[async_trait] 54 | /// impl Service for Ping { 55 | /// const NAME: &'static str = "PingService"; 56 | /// } 57 | /// 58 | /// #[tokio::main] 59 | /// async fn main() { 60 | /// let mut addr: Address = Registry::service().await; 61 | /// # addr.stop().await; 62 | /// # addr.wait_for_stop().await; 63 | /// } 64 | /// ``` 65 | #[derive(Debug, Default)] 66 | pub struct Registry { 67 | services: Mutex>>, 68 | } 69 | 70 | impl Registry { 71 | fn new() -> Self { 72 | Self::default() 73 | } 74 | 75 | /// Returns an address of an actor that implements [`Service`] trait. 76 | /// 77 | /// This function checks whether the corresponding actor is 78 | /// already running. If so, address of this actor is returned. Otherwise, 79 | /// actor is spawned first. 80 | /// 81 | /// ## Panics 82 | /// 83 | /// This method panics if two services having the same name will be attempted 84 | /// to be instantiated. All the names of services are expected to be unique. 85 | pub async fn service() -> Address { 86 | let mut lock = REGISTRY.services.lock().await; 87 | 88 | // Check whether address is already in registry. 89 | if let Some(maybe_addr) = lock.get(S::NAME) { 90 | // Check whether we can downcast the stored address to a desired type. 91 | if let Some(addr) = maybe_addr.downcast_ref::>() { 92 | // Check whether actor is running. It for some reason is was stopped, 93 | // we will have to re-launch it again. 94 | if addr.connected() { 95 | return addr.clone(); 96 | } 97 | } else { 98 | // Two or more services have a not unique name. 99 | panic!( 100 | "Two or more services have a not unique name. \ 101 | Name is {}, attempt to retrieve the type {:?}, but stored type is {:?}", 102 | S::NAME, 103 | TypeId::of::>(), 104 | (&*maybe_addr).type_id() 105 | ); 106 | } 107 | } 108 | 109 | // Address is either not in the registry or has been stopped. 110 | // We now have to spawn and store it in the registry. 111 | let addr = S::default().spawn(); 112 | lock.insert(S::NAME, Box::new(addr.clone())); 113 | 114 | addr 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/runtime/async_std.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | pub use async_std::task::JoinHandle; 4 | 5 | pub(crate) fn spawn(task: T) -> JoinHandle 6 | where 7 | T: Future + Send + 'static, 8 | T::Output: Send + 'static, 9 | { 10 | async_std::task::spawn(task) 11 | } 12 | -------------------------------------------------------------------------------- /src/runtime/empty.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/runtime/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg_attr( 2 | all(feature = "runtime-tokio", not(feature = "runtime-async-std")), 3 | path = "tokio.rs" 4 | )] 5 | #[cfg_attr( 6 | all(feature = "runtime-async-std", not(feature = "runtime-tokio")), 7 | path = "async_std.rs" 8 | )] 9 | #[cfg_attr( 10 | not(any(feature = "runtime-tokio", feature = "runtime-async-std",)), 11 | path = "empty.rs" 12 | )] 13 | mod runtime_impl; 14 | 15 | #[macro_export] 16 | #[doc(hidden)] 17 | macro_rules! cfg_runtime { 18 | ($($item:item)*) => { 19 | $( 20 | #[cfg(any(feature="runtime-tokio", feature="runtime-async-std"))] 21 | #[cfg_attr(docsrs, doc(cfg(any(feature = "runtime-tokio", feature="runtime-async-std"))))] 22 | $item 23 | )* 24 | } 25 | } 26 | 27 | cfg_runtime! { 28 | pub use runtime_impl::JoinHandle; 29 | pub(crate) use runtime_impl::*; 30 | } 31 | -------------------------------------------------------------------------------- /src/runtime/tokio.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | pub use tokio::task::JoinHandle; 4 | 5 | pub(crate) fn spawn(task: T) -> JoinHandle 6 | where 7 | T: Future + Send + 'static, 8 | T::Output: Send + 'static, 9 | { 10 | tokio::spawn(task) 11 | } 12 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Simple script to run all the tests and all the examples 4 | # with all the supported features sequentially. 5 | 6 | # Run tests & examples for tokio 7 | echo "-----------------------" 8 | echo "Running tests for tokio" 9 | echo "-----------------------" 10 | cargo test --all-targets --no-default-features --features runtime-tokio || exit 1 11 | 12 | # Run tests & examples for async-std 13 | echo "---------------------------" 14 | echo "Running tests for async std" 15 | echo "---------------------------" 16 | cargo test --all-targets --no-default-features --features runtime-async-std || exit 1 17 | 18 | # Run examples without any features 19 | echo "--------------------------------------" 20 | echo "Running tests with no runtime features" 21 | echo "--------------------------------------" 22 | cargo test --examples --no-default-features || exit 1 23 | -------------------------------------------------------------------------------- /tests/tokio/mod.rs: -------------------------------------------------------------------------------- 1 | //! Tests using tokio as the main executor. 2 | 3 | use std::sync::{ 4 | atomic::{AtomicBool, Ordering}, 5 | Arc, 6 | }; 7 | 8 | use messages::{ 9 | actor::ActorAction, 10 | prelude::{async_trait, Actor, Context, Handler, RuntimeActorExt}, 11 | }; 12 | 13 | mod registry; 14 | 15 | #[derive(Debug)] 16 | struct PingActor; 17 | 18 | impl Actor for PingActor {} 19 | 20 | #[async_trait] 21 | impl Handler for PingActor { 22 | type Result = u8; 23 | 24 | async fn handle(&mut self, input: u8, _: &Context) -> u8 { 25 | input 26 | } 27 | } 28 | 29 | #[derive(Debug, Default)] 30 | pub struct ActorState { 31 | started: AtomicBool, 32 | stopping: AtomicBool, 33 | stopped: AtomicBool, 34 | } 35 | 36 | #[derive(Debug, Clone)] 37 | struct WorkflowActor { 38 | state: Arc, 39 | } 40 | 41 | impl WorkflowActor { 42 | pub fn new(state: Arc) -> Self { 43 | Self { state } 44 | } 45 | } 46 | 47 | #[async_trait] 48 | impl Actor for WorkflowActor { 49 | async fn started(&mut self) { 50 | self.state.started.store(true, Ordering::SeqCst); 51 | } 52 | 53 | async fn stopping(&mut self) -> ActorAction { 54 | self.state.stopping.store(true, Ordering::SeqCst); 55 | ActorAction::Stop 56 | } 57 | 58 | fn stopped(&mut self) { 59 | self.state.stopped.store(true, Ordering::SeqCst); 60 | } 61 | } 62 | 63 | #[async_trait] 64 | impl Handler<()> for WorkflowActor { 65 | type Result = (); 66 | 67 | async fn handle(&mut self, _input: (), _context: &Context) -> Self::Result {} 68 | } 69 | 70 | #[derive(Debug)] 71 | struct Unstoppable; 72 | 73 | #[async_trait] 74 | impl Actor for Unstoppable { 75 | async fn stopping(&mut self) -> ActorAction { 76 | ActorAction::KeepRunning 77 | } 78 | } 79 | 80 | #[async_trait] 81 | impl Handler<()> for Unstoppable { 82 | type Result = (); 83 | 84 | async fn handle(&mut self, _input: (), _context: &Context) -> Self::Result {} 85 | } 86 | 87 | #[tokio::test] 88 | async fn basic_workflow() { 89 | let actor = PingActor; 90 | let mailbox: Context = Context::new(); 91 | 92 | let mut address = mailbox.address(); 93 | let future = tokio::spawn(mailbox.run(actor)); 94 | 95 | let response = address.send(10).await.unwrap(); 96 | assert_eq!(response, 10); 97 | 98 | address.stop().await; 99 | 100 | assert!(future.await.is_ok()); 101 | } 102 | 103 | #[tokio::test] 104 | async fn runtime_based() { 105 | let mut address = PingActor.spawn(); 106 | let response = address.send(10).await.unwrap(); 107 | assert_eq!(response, 10); 108 | address.stop().await; 109 | } 110 | 111 | #[tokio::test] 112 | async fn lifespan_methods() { 113 | let state = Arc::new(ActorState::default()); 114 | 115 | let mut address = WorkflowActor::new(state.clone()).spawn(); 116 | // Wait for actor to actually start. 117 | address.send(()).await.unwrap(); 118 | 119 | assert!(state.started.load(Ordering::SeqCst)); 120 | assert!(!state.stopping.load(Ordering::SeqCst)); 121 | assert!(!state.stopped.load(Ordering::SeqCst)); 122 | 123 | address.stop().await; 124 | address.wait_for_stop().await; 125 | assert!(state.started.load(Ordering::SeqCst)); 126 | assert!(state.stopping.load(Ordering::SeqCst)); 127 | assert!(state.stopped.load(Ordering::SeqCst)); 128 | } 129 | 130 | #[tokio::test] 131 | async fn unstoppable() { 132 | let mut address = Unstoppable.spawn(); 133 | address.stop().await; 134 | tokio::time::sleep(std::time::Duration::from_millis(50)).await; 135 | assert!(address.connected(), "Actor was shutdown"); 136 | address 137 | .send(()) 138 | .await 139 | .expect("Actor did not process the message"); 140 | } 141 | -------------------------------------------------------------------------------- /tests/tokio/registry.rs: -------------------------------------------------------------------------------- 1 | use messages::prelude::*; 2 | 3 | #[derive(Debug, Default)] 4 | struct PingActor; 5 | 6 | impl Actor for PingActor {} 7 | 8 | #[async_trait] 9 | impl Handler for PingActor { 10 | type Result = u8; 11 | 12 | async fn handle(&mut self, input: u8, _: &Context) -> u8 { 13 | input 14 | } 15 | } 16 | 17 | impl Service for PingActor { 18 | const NAME: &'static str = "Ping"; 19 | } 20 | 21 | #[tokio::test] 22 | async fn get_from_registry() { 23 | let mut address: Address = Registry::service().await; 24 | let response = address.send(10).await.unwrap(); 25 | assert_eq!(response, 10); 26 | address.stop().await; 27 | address.wait_for_stop().await; 28 | 29 | // Service must be restarted after stopping. 30 | let mut address: Address = Registry::service().await; 31 | let response = address.send(10).await.unwrap(); 32 | assert_eq!(response, 10); 33 | address.stop().await; 34 | } 35 | --------------------------------------------------------------------------------