├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE.md ├── NOTICES.hbs ├── NOTICES.md ├── README.md ├── about.toml ├── deny.toml ├── doc └── dep-graph.png ├── examples ├── hello_world.rs ├── montecarlo.rs └── philosophers.rs ├── modoc.config └── src ├── actors.rs ├── cluster.rs ├── executor.rs ├── executor └── thread_pool.rs ├── lib.rs ├── message.rs ├── prelude.rs ├── system.rs └── system └── system_actor.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything with "gitignore" in the name ( other than the .gitignore file ) 2 | *gitignore* 3 | !.gitignore 4 | 5 | # Cargo 6 | /target 7 | **/*.rs.bk 8 | Cargo.lock 9 | 10 | # IntelliJ Files 11 | /.idea 12 | /*.iml 13 | 14 | # VSCode files 15 | .vscode 16 | 17 | # Misc 18 | **/.DS_Store 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | notifications: 4 | email: 5 | on_success: never 6 | 7 | # Build only master and tagged releases 8 | branches: 9 | only: 10 | - master 11 | - /^v\d+\.\d+\.\d+(-\w*)?$/ 12 | 13 | matrix: 14 | allow_failures: 15 | - rust: nightly 16 | fast_finish: true 17 | include: 18 | - rust: nightly 19 | script: 20 | - cargo test 21 | - rust: beta 22 | script: 23 | - cargo test 24 | - rust: stable 25 | install: 26 | - rustup component add rustfmt --toolchain stable-x86_64-unknown-linux-gnu 27 | env: 28 | - RUSTFLAGS="-D warnings" 29 | script: 30 | - cargo fmt -- --check 31 | - cargo test 32 | - cargo build 33 | - cargo doc --no-deps 34 | - cargo package 35 | deploy: 36 | provider: cargo 37 | on: 38 | branch: master 39 | tags: true 40 | token: 41 | secure: YYk39OlzmFzYUF7A0GpHV6DkC3ji7gqMa21O37hkE8s6pwa9eE8x9oGnzYYy5iPwJtEFNNICXcktc4yaeWHWxWRMORvQtO4hwxTu05lIa3BJBtS8ZyGT8jMFrVzRG07qU7lfFunANPPJwwhio9IhyyERiRct0yPsYtVIwJKNfhyYrjQ287AlSwGt6ZgNtvw353VsKf4brVh3L6QwzTt+G6lT4qlMXyNUz1ocUvwdfbWwFDQXKOKSiJDEpzf0g/bzM1Atpc0JNQat9VTPn22s/82C/+Gap2IiMQQkym2zX7ys/ct0R9PRP+6FF1R0apG897BXVdVZ9Zjtpuz+50KrHHueWueajbx8az2a1qsbJgRW5vQ75T5GShbZPEifZRK3ahiuLknv+8zv9IaqBPfowtCLEtyCBgxf+pVgH4y9ESab21cO9mm/6pEyKSt4lasqtPFHuxGWHxZ8vo6smZHOOyyqLptDXsdMnLjObOwEXoSSoHjpeOis0gDSuvJXV9agKEd1v7RknS+FDS7G2nzqFKly9BcIKUG6Yz7p6+WbiiJO/wg0FlldqcG2OSa9MqpMFGFN2AWCf1m0HHZ4wv6G7spKAIEdRHuVk3BxiH7z7aS4rrZ38guS9CCM12ytBVdyWMG3IqzP+uG5nRy5JSRDWiMVHdybcbHcY/luQVgQPQU= 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.1.0-alpha.0] - 2020-03-26 11 | 12 | The first release after forking from Axiom. 13 | 14 | ### Added 15 | 16 | - Create new `ActorSystem::spawn_pool` function for spawning actor pools and returning an `AidPool` implementation. 17 | - Add traits: `AidPool`, `SyncAidPool` 18 | - Add `AidPool` implementations: `RandomAidPool`, `Aid` ( the existing `Aid` type now implements `AidPool` ) 19 | - Add `actor-pool` Cargo feature that is enabled by default and gates off the `RandomAidPool` implementation to avoid bringin in extra dependencies. 20 | 21 | [unreleased]: https://github.com/katharostech/maxim/compare/v0.1.0-alpha.0...HEAD 22 | [0.1.0-alpha.0]: https://github.com/katharostech/maxim/releases/tag/v0.1.0-alpha.0 -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "maxim" 3 | version = "0.1.0-alpha.0" 4 | edition = "2018" 5 | authors = [ 6 | "Maxim Contributors", 7 | "Zicklag ", 8 | "Robert Simmons Jr. MSc.", 9 | "Khionu Sybiern " 10 | ] 11 | license = "Apache-2.0" 12 | homepage = "https://github.com/katharostech/maxim" 13 | repository = "https://github.com/katharostech/maxim" 14 | documentation = "https://docs.rs/maxim" 15 | readme = "README.md" 16 | description = """ 17 | Implements a highly-scalable and ergonomic actor system for Rust based on the 18 | best of Erlang / Elixir and Akka. A fork of the Axiom actor framework. 19 | """ 20 | 21 | keywords = [ 22 | "Actor", 23 | "Actors", 24 | "Akka", 25 | "Erlang", 26 | "Elixir" 27 | ] 28 | 29 | categories = [ 30 | "asynchronous", 31 | "concurrency" 32 | ] 33 | 34 | exclude = [ 35 | "/.gitignore", 36 | "/.git", 37 | "/.github", 38 | "/.travis.yml", 39 | "/modoc.config", 40 | ] 41 | 42 | [features] 43 | default = ["actor-pool"] 44 | actor-pool = ["rand", "rand_xoshiro"] 45 | 46 | [badges] 47 | # We won't be using Travis in a bit 48 | #travis-ci = { repository = "katharostech/maxim" } 49 | is-it-maintained-issue-resolution = { repository = "katharostech/maxim" } 50 | is-it-maintained-open-issues = { repository = "katharostech/maxim" } 51 | maintenance = { status = "actively-developed" } 52 | 53 | [dev-dependencies] 54 | env_logger = "^0.7.1" 55 | rand = "^0.7" 56 | serde_json = "^1.0.40" 57 | 58 | [dependencies] 59 | bincode = "1.1.4" 60 | dashmap = "1.0.3" 61 | futures = "0.3.1" 62 | num_cpus = "1.10.1" 63 | log = "0.4" 64 | once_cell = "1.0.2" 65 | secc = "0.0.10" 66 | serde = { version = "1.0.97", features = ["derive", "rc"] } 67 | uuid = { version = "0.8.1", features = ["serde", "v4"]} 68 | rand = { version = "0.7.3", optional = true } 69 | rand_xoshiro = { version = "0.4.0", optional = true } 70 | 71 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | ============== 3 | 4 | _Version 2.0, January 2004_ 5 | _<>_ 6 | 7 | ### Terms and Conditions for use, reproduction, and distribution 8 | 9 | #### 1. Definitions 10 | 11 | “License” shall mean the terms and conditions for use, reproduction, and 12 | distribution as defined by Sections 1 through 9 of this document. 13 | 14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright 15 | owner that is granting the License. 16 | 17 | “Legal Entity” shall mean the union of the acting entity and all other entities 18 | that control, are controlled by, or are under common control with that entity. 19 | For the purposes of this definition, “control” means **(i)** the power, direct or 20 | indirect, to cause the direction or management of such entity, whether by 21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the 22 | outstanding shares, or **(iii)** beneficial ownership of such entity. 23 | 24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising 25 | permissions granted by this License. 26 | 27 | “Source” form shall mean the preferred form for making modifications, including 28 | but not limited to software source code, documentation source, and configuration 29 | files. 30 | 31 | “Object” form shall mean any form resulting from mechanical transformation or 32 | translation of a Source form, including but not limited to compiled object code, 33 | generated documentation, and conversions to other media types. 34 | 35 | “Work” shall mean the work of authorship, whether in Source or Object form, made 36 | available under the License, as indicated by a copyright notice that is included 37 | in or attached to the work (an example is provided in the Appendix below). 38 | 39 | “Derivative Works” shall mean any work, whether in Source or Object form, that 40 | is based on (or derived from) the Work and for which the editorial revisions, 41 | annotations, elaborations, or other modifications represent, as a whole, an 42 | original work of authorship. For the purposes of this License, Derivative Works 43 | shall not include works that remain separable from, or merely link (or bind by 44 | name) to the interfaces of, the Work and Derivative Works thereof. 45 | 46 | “Contribution” shall mean any work of authorship, including the original version 47 | of the Work and any modifications or additions to that Work or Derivative Works 48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 49 | by the copyright owner or by an individual or Legal Entity authorized to submit 50 | on behalf of the copyright owner. For the purposes of this definition, 51 | “submitted” means any form of electronic, verbal, or written communication sent 52 | to the Licensor or its representatives, including but not limited to 53 | communication on electronic mailing lists, source code control systems, and 54 | issue tracking systems that are managed by, or on behalf of, the Licensor for 55 | the purpose of discussing and improving the Work, but excluding communication 56 | that is conspicuously marked or otherwise designated in writing by the copyright 57 | owner as “Not a Contribution.” 58 | 59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf 60 | of whom a Contribution has been received by Licensor and subsequently 61 | incorporated within the Work. 62 | 63 | #### 2. Grant of Copyright License 64 | 65 | Subject to the terms and conditions of this License, each Contributor hereby 66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 67 | irrevocable copyright license to reproduce, prepare Derivative Works of, 68 | publicly display, publicly perform, sublicense, and distribute the Work and such 69 | Derivative Works in Source or Object form. 70 | 71 | #### 3. Grant of Patent License 72 | 73 | Subject to the terms and conditions of this License, each Contributor hereby 74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 75 | irrevocable (except as stated in this section) patent license to make, have 76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 77 | such license applies only to those patent claims licensable by such Contributor 78 | that are necessarily infringed by their Contribution(s) alone or by combination 79 | of their Contribution(s) with the Work to which such Contribution(s) was 80 | submitted. If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this License 84 | for that Work shall terminate as of the date such litigation is filed. 85 | 86 | #### 4. Redistribution 87 | 88 | You may reproduce and distribute copies of the Work or Derivative Works thereof 89 | in any medium, with or without modifications, and in Source or Object form, 90 | provided that You meet the following conditions: 91 | 92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of 93 | this License; and 94 | * **(b)** You must cause any modified files to carry prominent notices stating that You 95 | changed the files; and 96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, 97 | all copyright, patent, trademark, and attribution notices from the Source form 98 | of the Work, excluding those notices that do not pertain to any part of the 99 | Derivative Works; and 100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any 101 | Derivative Works that You distribute must include a readable copy of the 102 | attribution notices contained within such NOTICE file, excluding those notices 103 | that do not pertain to any part of the Derivative Works, in at least one of the 104 | following places: within a NOTICE text file distributed as part of the 105 | Derivative Works; within the Source form or documentation, if provided along 106 | with the Derivative Works; or, within a display generated by the Derivative 107 | Works, if and wherever such third-party notices normally appear. The contents of 108 | the NOTICE file are for informational purposes only and do not modify the 109 | License. You may add Your own attribution notices within Derivative Works that 110 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 111 | provided that such additional attribution notices cannot be construed as 112 | modifying the License. 113 | 114 | You may add Your own copyright statement to Your modifications and may provide 115 | additional or different license terms and conditions for use, reproduction, or 116 | distribution of Your modifications, or for any such Derivative Works as a whole, 117 | provided Your use, reproduction, and distribution of the Work otherwise complies 118 | with the conditions stated in this License. 119 | 120 | #### 5. Submission of Contributions 121 | 122 | Unless You explicitly state otherwise, any Contribution intentionally submitted 123 | for inclusion in the Work by You to the Licensor shall be under the terms and 124 | conditions of this License, without any additional terms or conditions. 125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 126 | any separate license agreement you may have executed with Licensor regarding 127 | such Contributions. 128 | 129 | #### 6. Trademarks 130 | 131 | This License does not grant permission to use the trade names, trademarks, 132 | service marks, or product names of the Licensor, except as required for 133 | reasonable and customary use in describing the origin of the Work and 134 | reproducing the content of the NOTICE file. 135 | 136 | #### 7. Disclaimer of Warranty 137 | 138 | Unless required by applicable law or agreed to in writing, Licensor provides the 139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, 140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 141 | including, without limitation, any warranties or conditions of TITLE, 142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 143 | solely responsible for determining the appropriateness of using or 144 | redistributing the Work and assume any risks associated with Your exercise of 145 | permissions under this License. 146 | 147 | #### 8. Limitation of Liability 148 | 149 | In no event and under no legal theory, whether in tort (including negligence), 150 | contract, or otherwise, unless required by applicable law (such as deliberate 151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 152 | liable to You for damages, including any direct, indirect, special, incidental, 153 | or consequential damages of any character arising as a result of this License or 154 | out of the use or inability to use the Work (including but not limited to 155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 156 | any and all other commercial damages or losses), even if such Contributor has 157 | been advised of the possibility of such damages. 158 | 159 | #### 9. Accepting Warranty or Additional Liability 160 | 161 | While redistributing the Work or Derivative Works thereof, You may choose to 162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 163 | other liability obligations and/or rights consistent with this License. However, 164 | in accepting such obligations, You may act only on Your own behalf and on Your 165 | sole responsibility, not on behalf of any other Contributor, and only if You 166 | agree to indemnify, defend, and hold each Contributor harmless for any liability 167 | incurred by, or claims asserted against, such Contributor by reason of your 168 | accepting any such warranty or additional liability. 169 | 170 | _END OF TERMS AND CONDITIONS_ 171 | 172 | ### APPENDIX: How to apply the Apache License to your work 173 | 174 | To apply the Apache License to your work, attach the following boilerplate 175 | notice, with the fields enclosed by brackets `[]` replaced with your own 176 | identifying information. (Don't include the brackets!) The text should be 177 | enclosed in the appropriate comment syntax for the file format. We also 178 | recommend that a file or class name and description of purpose be included on 179 | the same “printed page” as the copyright notice for easier identification within 180 | third-party archives. 181 | 182 | Copyright [yyyy] [name of copyright owner] 183 | 184 | Licensed under the Apache License, Version 2.0 (the "License"); 185 | you may not use this file except in compliance with the License. 186 | You may obtain a copy of the License at 187 | 188 | http://www.apache.org/licenses/LICENSE-2.0 189 | 190 | Unless required by applicable law or agreed to in writing, software 191 | distributed under the License is distributed on an "AS IS" BASIS, 192 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 193 | See the License for the specific language governing permissions and 194 | limitations under the License. 195 | -------------------------------------------------------------------------------- /NOTICES.hbs: -------------------------------------------------------------------------------- 1 | # 3rd Party Licenses 2 | 3 | ## Overview of licenses 4 | 5 | {{#each overview}} - {{name}} ({{count}}) 6 | {{/each}} 7 | 8 | ## All Licenses 9 | 10 | {{#each licenses}} 11 | ### {{name}} 12 | 13 | #### Used by 14 | {{#each used_by}} - [{{crate.name}} {{crate.version}}]({{#if crate.repository}} {{crate.repository}} {{else}} https://crates.io/crates/{{crate.name}} {{/if}}) 15 | {{/each}} 16 | 17 | ``` 18 | {{text}} 19 | ``` 20 | {{/each}} 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Implementation of a highly-scalable and ergonomic actor model for Rust 2 | 3 | [![Latest version](https://img.shields.io/crates/v/maxim.svg)](https://crates.io/crates/maxim) 4 | [![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/katharostech/maxim.svg)](https://isitmaintained.com/project/katharostech/maxim) 5 | [![License](https://img.shields.io/crates/l/maxim.svg)](https://github.com/katharostech/maxim#license) 6 | [![Changelog.md](https://img.shields.io/badge/Keep%20a%20Changelog-Changelog.md-blue)](https://github.com/katharostech/maxim/blob/master/CHANGELOG.md) 7 | 8 | # Maxim 9 | 10 | Maxim is a fork of the [Axiom](https://github.com/rsimmonsjr/axiom) actor framework that was made so we could use the awesome Actor framework while experimenting with our own ideas for Actor framework design. 11 | 12 | Maxim brings a highly-scalable actor model to the Rust language based on the many lessons learned over years of Actor model implementations in Akka and Erlang. Maxim is, however, not a direct re-implementation of either of the two aforementioned actor models but rather a new implementation deriving inspiration from the good parts of those projects. 13 | 14 | Current development on Maxim is focused on learning how the framework works and experimenting with our design ideas. We will be pushing `0.1.0-alpha` releases with our changes until it gets to a point that is relatively usable. The first thing we've added since the fork was a `spawn_pool` feature that allows you to create pools of actors. This and other features we add are likely to change and adapt as we test them in our projects. 15 | 16 | Other things that we are thinking about changing are: 17 | 18 | - Using the [`smol`](https://docs.rs/smol) executor instead of using our own 19 | - This will drastically simplify the implementation as a lot of the work is handled by the executor 20 | - Adding an option to use either bounded or unbounded channels for actor messages ( see "Design Principals of Maxim" below for more info ) 21 | - This would probably involve using [Flume](https://github.com/zesterer/flume) for backing the channels 22 | - Adding an optional macro for matching on message type 23 | 24 | ## Getting Started 25 | 26 | _An actor model is an architectural asynchronous programming paradigm characterized by the use of actors for all processing activities._ 27 | 28 | Actors have the following characteristics: 29 | 30 | 1. An actor can be interacted with only by means of messages. 31 | 2. An actor processes only one message at a time. 32 | 3. An actor will process a message only once. 33 | 4. An actor can send a message to any other actor without knowledge of that actor's internals. 34 | 5. Actors send only immutable data as messages, though they may have mutable internal state. 35 | 6. Actors are location agnostic; they can be sent a message from anywhere in the cluster. 36 | 37 | Note that within the language of Rust, rule five cannot be enforced by Rust but is a best practice which is important for developers creating actors based on Maxim. In Erlang and Elixir rule five cannot be violated because of the structure of the language but this also leads to performance limitations. It's better to allow internal mutable state and encourage the good practice of not sending mutable messages. 38 | 39 | What is important to understand is that these rules combined together makes each actor operate like a micro-service in the memory space of the program using them. Since actor messages are immutable, actors can trade information safely and easily without copying large data structures. 40 | 41 | Although programming in the actor model is quite an involved process you can get started with Maxim in only a few lines of code. 42 | 43 | ```rust 44 | use maxim::prelude::*; 45 | use std::sync::Arc; 46 | use std::time::Duration; 47 | 48 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 49 | 50 | let aid = system 51 | .spawn() 52 | .with( 53 | 0 as usize, 54 | |state: usize, _context: Context, _message: Message| async move { 55 | Ok(Status::done(state)) 56 | } 57 | ) 58 | .unwrap(); 59 | 60 | aid.send(Message::new(11)).unwrap(); 61 | 62 | // It is worth noting that you probably wouldn't just unwrap in real code but deal with 63 | // the result as a panic in Maxim will take down a dispatcher thread and potentially 64 | // hang the system. 65 | 66 | // This will wrap the value `17` in a Message for you! 67 | aid.send_new(17).unwrap(); 68 | 69 | // We can also create and send separately using just `send`, not `send_new`. 70 | let message = Message::new(19); 71 | aid.send(message).unwrap(); 72 | 73 | // Another neat capability is to send a message after some time has elapsed. 74 | aid.send_after(Message::new(7), Duration::from_millis(10)).unwrap(); 75 | aid.send_new_after(7, Duration::from_millis(10)).unwrap(); 76 | ``` 77 | 78 | This code creates an actor system, fetches a builder for an actor via the `spawn()` method, spawns an actor and finally sends the actor a message. Once the actor is done processing a message it returns the new state of the actor and the status after handling this message. In this case we didnt change the state so we just return it. Creating an Maxim actor is literally that easy but there is a lot more functionality available as well. 79 | 80 | Keep in mind that if you are capturing variables from the environment you will have to wrap the `async move {}` block in another block and then move your variables into the first block. Please see the test cases for more examples of this. 81 | 82 | If you want to create an actor with a struct that is simple as well. Let's create one that handles a couple of different message types: 83 | 84 | ```rust 85 | use maxim::prelude::*; 86 | use std::sync::Arc; 87 | 88 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 89 | 90 | struct Data { 91 | value: i32, 92 | } 93 | 94 | impl Data { 95 | fn handle_bool(mut self, message: bool) -> ActorResult { 96 | if message { 97 | self.value += 1; 98 | } else { 99 | self.value -= 1; 100 | } 101 | Ok(Status::done(self)) 102 | } 103 | 104 | fn handle_i32(mut self, message: i32) -> ActorResult { 105 | self.value += message; 106 | Ok(Status::done(self)) 107 | } 108 | 109 | async fn handle(mut self, _context: Context, message: Message) -> ActorResult { 110 | if let Some(msg) = message.content_as::() { 111 | self.handle_bool(*msg) 112 | } else if let Some(msg) = message.content_as::() { 113 | self.handle_i32(*msg) 114 | } else { 115 | panic!("Failed to dispatch properly"); 116 | } 117 | } 118 | } 119 | 120 | let data = Data { value: 0 }; 121 | let aid = system.spawn().name("Fred").with(data, Data::handle).unwrap(); 122 | 123 | aid.send_new(11).unwrap(); 124 | aid.send_new(true).unwrap(); 125 | aid.send_new(false).unwrap(); 126 | ``` 127 | 128 | This code creates a named actor out of an arbitrary struct. Since the only requirement to make an actor is to have a function that is compliant with the [`maxim::actors::Processor`] trait, anything can be an actor. If this struct had been declared somewhere outside of your control you could use it in an actor as state by declaring your own handler function and making the calls to the 3rd party structure. 129 | 130 | _It's important to keep in mind that the starting state is moved into the actor and you will not have external access to it afterwards._ This is by design and although you could conceivably use a [`Arc`] or [`Mutex`] enclosing a structure as state, that would definitely be a bad idea as it would break the rules we laid out for actors. 131 | 132 | ## Detailed Examples 133 | 134 | - [Hello World](https://github.com/katharostech/maxim/blob/master/examples/hello_world.rs): The 135 | obligatory introduction to any computer system. 136 | - [Dining Philosophers](https://github.com/katharostech/maxim/blob/master/examples/philosophers.rs): 137 | An example of using Maxim to solve a classic Finite State Machine problem in computer science. 138 | - [Monte Carlo](https://github.com/katharostech/maxim/blob/master/examples/montecarlo.rs): An 139 | example of how to use Maxim for parallel computation. 140 | 141 | ## Design Principals of Maxim 142 | 143 | These are the core principals of Axiom, the project Maxim was forked from: 144 | 145 | 1. **At its core an actor is just an function that processes messages.** The simplest actor is a 146 | function that takes a message and simply ignores it. The benefit to the functional approach over 147 | the Akka model is that it allows the user to create actors easily and simply. This is the notion 148 | of _micro module programming_; the notion of building a complex system from the smallest 149 | components. Software based on the actor model can get complicated; keeping it simple at the core 150 | is fundamental to solid architecture. 151 | 2. **Actors can be a Finite State Machine (FSM).** Actors receive and process messages nominally 152 | in the order received. However, there are certain circumstances where an actor has to change to 153 | another state and process other messages, skipping certain messages to be processed later. 154 | 3. **When skipping messages, the messages must not move.** Akka allows the skipping of messages 155 | by _stashing_ the message in another data structure and then restoring this stash later. This 156 | process has many inherent flaws. Instead Axiom allows an actor to skip messages in its channel 157 | but leave them where they are, increasing performance and avoiding many problems. 158 | 4. **Actors use a bounded capacity channel.** In Axiom the message capacity for the actor's 159 | channel is bounded, resulting in greater simplicity and an emphasis on good actor design. 160 | 5. **Axiom should be kept as small as possible.** Axiom is the core of the actor model and 161 | should not be expanded to include everything possible for actors. That should be the job of 162 | libraries that extend Axiom. Axiom itself should be an example of _micro module programming_. 163 | 6. **The tests are the best place for examples.** The tests of Axiom will be extensive and well 164 | maintained and should be a resource for those wanting to use Axiom. They should not be a dumping 165 | ground for copy-paste or throwaway code. The best tests will look like architected code. 166 | 7. **A huge emphasis is put on crate user ergonomics.** Axiom should be easy to use. 167 | 168 | The principals that Maxim may **not** preserve are principals 4 and 6. To address those: 169 | 170 | - **Bounded capacity channel:** While it may be best to have a bounded capacity channel, we will need to do some experimentation with the design before we settle our own opinion on it and our initial reaction is that it would be good to have the user be allowed to choose. As far as complexity is concerned we will probably look into out-sourcing our channel implementation to something like [Flume](https://github.com/zesterer/flume). There has not been enough investigation made to make such statements with any certainty, though. 171 | - **The tests are the best place for examples:** While we agree that tests should be examples of how the code will actually be used, we are less along the lines of telling users to go look at the unit tests to find out how to use the library. We want the documentation to be rich and helpful to the users so that they don't _have_ to look at the tests to find out how to use the tool. 172 | 173 | [`maxim::actors::processor`]: https://docs.rs/maxim/latest/maxim/actors/trait.Processor.html 174 | [`arc`]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html 175 | [`mutex`]: https://doc.rust-lang.org/stable/std/sync/struct.Mutex.html 176 | 177 | ## Dependency Graph 178 | 179 | - **Black:** regular dependency 180 | - **Red:** optional dependency 181 | 182 | ![dependency graph](./doc/dep-graph.png) 183 | 184 | ## License 185 | 186 | Copyright 2020 Maxim Contributors 187 | 188 | Licensed under the Apache License, Version 2.0 (the "License"); 189 | you may not use this file except in compliance with the License. 190 | You may obtain a copy of the License at 191 | 192 | http://www.apache.org/licenses/LICENSE-2.0 193 | 194 | Unless required by applicable law or agreed to in writing, software 195 | distributed under the License is distributed on an "AS IS" BASIS, 196 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 197 | See the License for the specific language governing permissions and 198 | limitations under the License. 199 | -------------------------------------------------------------------------------- /about.toml: -------------------------------------------------------------------------------- 1 | accepted = [ 2 | "MIT", 3 | "Apache-2.0", 4 | "ISC", 5 | "BSD-2-Clause", 6 | "BSD-3-Clause" 7 | ] -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # This template contains all of the possible sections and their default values 2 | 3 | # Note that all fields that take a lint level have these possible values: 4 | # * deny - An error will be produced and the check will fail 5 | # * warn - A warning will be produced, but the check will not fail 6 | # * allow - No warning or error will be produced, though in some cases a note 7 | # will be 8 | 9 | # The values provided in this template are the default values that will be used 10 | # when any section or field is not specified in your own configuration 11 | 12 | # If 1 or more target triples (and optionally, target_features) are specified, 13 | # only the specified targets will be checked when running `cargo deny check`. 14 | # This means, if a particular package is only ever used as a target specific 15 | # dependency, such as, for example, the `nix` crate only being used via the 16 | # `target_family = "unix"` configuration, that only having windows targets in 17 | # this list would mean the nix crate, as well as any of its exclusive 18 | # dependencies not shared by any other crates, would be ignored, as the target 19 | # list here is effectively saying which targets you are building for. 20 | targets = [ 21 | # The triple can be any string, but only the target triples built in to 22 | # rustc (as of 1.40) can be checked against actual config expressions 23 | #{ triple = "x86_64-unknown-linux-musl" }, 24 | # You can also specify which target_features you promise are enabled for a 25 | # particular target. target_features are currently not validated against 26 | # the actual valid features supported by the target architecture. 27 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 28 | ] 29 | 30 | # This section is considered when running `cargo deny check advisories` 31 | # More documentation for the advisories section can be found here: 32 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 33 | [advisories] 34 | # The path where the advisory database is cloned/fetched into 35 | db-path = "~/.cargo/advisory-db" 36 | # The url of the advisory database to use 37 | db-url = "https://github.com/rustsec/advisory-db" 38 | # The lint level for security vulnerabilities 39 | vulnerability = "deny" 40 | # The lint level for unmaintained crates 41 | unmaintained = "warn" 42 | # The lint level for crates that have been yanked from their source registry 43 | yanked = "warn" 44 | # The lint level for crates with security notices. Note that as of 45 | # 2019-12-17 there are no security notice advisories in 46 | # https://github.com/rustsec/advisory-db 47 | notice = "warn" 48 | # A list of advisory IDs to ignore. Note that ignored advisories will still 49 | # output a note when they are encountered. 50 | ignore = [ 51 | #"RUSTSEC-0000-0000", 52 | ] 53 | # Threshold for security vulnerabilities, any vulnerability with a CVSS score 54 | # lower than the range specified will be ignored. Note that ignored advisories 55 | # will still output a note when they are encountered. 56 | # * None - CVSS Score 0.0 57 | # * Low - CVSS Score 0.1 - 3.9 58 | # * Medium - CVSS Score 4.0 - 6.9 59 | # * High - CVSS Score 7.0 - 8.9 60 | # * Critical - CVSS Score 9.0 - 10.0 61 | #severity-threshold = 62 | 63 | # This section is considered when running `cargo deny check licenses` 64 | # More documentation for the licenses section can be found here: 65 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 66 | [licenses] 67 | # The lint level for crates which do not have a detectable license 68 | unlicensed = "deny" 69 | # List of explictly allowed licenses 70 | # See https://spdx.org/licenses/ for list of possible licenses 71 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 72 | allow = [ 73 | "MIT", 74 | "Apache-2.0", 75 | "BSD-2-Clause", 76 | ] 77 | # List of explictly disallowed licenses 78 | # See https://spdx.org/licenses/ for list of possible licenses 79 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 80 | deny = [ 81 | #"Nokia", 82 | ] 83 | # Lint level for licenses considered copyleft 84 | copyleft = "warn" 85 | # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses 86 | # * both - The license will be approved if it is both OSI-approved *AND* FSF 87 | # * either - The license will be approved if it is either OSI-approved *OR* FSF 88 | # * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF 89 | # * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved 90 | # * neither - This predicate is ignored and the default lint level is used 91 | allow-osi-fsf-free = "neither" 92 | # Lint level used when no other predicates are matched 93 | # 1. License isn't in the allow or deny lists 94 | # 2. License isn't copyleft 95 | # 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" 96 | default = "deny" 97 | # The confidence threshold for detecting a license from license text. 98 | # The higher the value, the more closely the license text must be to the 99 | # canonical license text of a valid SPDX license file. 100 | # [possible values: any between 0.0 and 1.0]. 101 | confidence-threshold = 0.8 102 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 103 | # aren't accepted for every possible crate as with the normal allow list 104 | exceptions = [ 105 | # Each entry is the crate and version constraint, and its specific allow 106 | # list 107 | #{ allow = ["Zlib"], name = "adler32", version = "*" }, 108 | ] 109 | 110 | # Some crates don't have (easily) machine readable licensing information, 111 | # adding a clarification entry for it allows you to manually specify the 112 | # licensing information 113 | #[[licenses.clarify]] 114 | # The name of the crate the clarification applies to 115 | #name = "ring" 116 | # THe optional version constraint for the crate 117 | #version = "*" 118 | # The SPDX expression for the license requirements of the crate 119 | #expression = "MIT AND ISC AND OpenSSL" 120 | # One or more files in the crate's source used as the "source of truth" for 121 | # the license expression. If the contents match, the clarification will be used 122 | # when running the license check, otherwise the clarification will be ignored 123 | # and the crate will be checked normally, which may produce warnings or errors 124 | # depending on the rest of your configuration 125 | #license-files = [ 126 | # Each entry is a crate relative path, and the (opaque) hash of its contents 127 | #{ path = "LICENSE", hash = 0xbd0eed23 } 128 | #] 129 | 130 | [licenses.private] 131 | # If true, ignores workspace crates that aren't published, or are only 132 | # published to private registries 133 | ignore = false 134 | # One or more private registries that you might publish crates to, if a crate 135 | # is only published to private registries, and ignore is true, the crate will 136 | # not have its license(s) checked 137 | registries = [ 138 | #"https://sekretz.com/registry 139 | ] 140 | 141 | # This section is considered when running `cargo deny check bans`. 142 | # More documentation about the 'bans' section can be found here: 143 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 144 | [bans] 145 | # Lint level for when multiple versions of the same crate are detected 146 | multiple-versions = "warn" 147 | # The graph highlighting used when creating dotgraphs for crates 148 | # with multiple versions 149 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 150 | # * simplest-path - The path to the version with the fewest edges is highlighted 151 | # * all - Both lowest-version and simplest-path are used 152 | highlight = "all" 153 | # List of crates that are allowed. Use with care! 154 | allow = [ 155 | #{ name = "ansi_term", version = "=0.11.0" }, 156 | ] 157 | # List of crates to deny 158 | deny = [ 159 | # Each entry the name of a crate and a version range. If version is 160 | # not specified, all versions will be matched. 161 | #{ name = "ansi_term", version = "=0.11.0" }, 162 | ] 163 | # Certain crates/versions that will be skipped when doing duplicate detection. 164 | skip = [ 165 | #{ name = "ansi_term", version = "=0.11.0" }, 166 | ] 167 | # Similarly to `skip` allows you to skip certain crates during duplicate 168 | # detection. Unlike skip, it also includes the entire tree of transitive 169 | # dependencies starting at the specified crate, up to a certain depth, which is 170 | # by default infinite 171 | skip-tree = [ 172 | #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, 173 | ] 174 | 175 | # This section is considered when running `cargo deny check sources`. 176 | # More documentation about the 'sources' section can be found here: 177 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 178 | [sources] 179 | # Lint level for what to happen when a crate from a crate registry that is not 180 | # in the allow list is encountered 181 | unknown-registry = "warn" 182 | # Lint level for what to happen when a crate from a git repository that is not 183 | # in the allow list is encountered 184 | unknown-git = "warn" 185 | # List of URLs for allowed crate registries. Defaults to the crates.io index 186 | # if not specified. If it is specified but empty, no registries are allowed. 187 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 188 | # List of URLs for allowed Git repositories 189 | allow-git = [] 190 | -------------------------------------------------------------------------------- /doc/dep-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/katharostech/maxim/5829b3b740e430f571f9696ba3b99638633c7d1f/doc/dep-graph.png -------------------------------------------------------------------------------- /examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | //! This is an example of implementing the classic "Hello World" in the Maxim actor system. 2 | //! 3 | //! Demonstrates 4 | //! * Creating an actor system. 5 | //! * Spawning an actor with a static function handler. 6 | //! * Setting up the current thread to talk to the actor system. 7 | //! * Sending a message to an actor. 8 | //! * Determining the content of a message and acting on the message. 9 | //! * Triggering an actor system shutdown within the actor. 10 | //! * Awaiting the actor system to shut down. 11 | 12 | use maxim::prelude::*; 13 | use serde::{Deserialize, Serialize}; 14 | 15 | /// The messages we will be sending to our actor. All messages must be serializable and 16 | /// deserializable with serde. 17 | #[derive(Serialize, Deserialize)] 18 | enum HelloMessages { 19 | Greet, 20 | } 21 | 22 | /// This is the handler that will be used by the actor. 23 | async fn hello(_: (), context: Context, message: Message) -> ActorResult<()> { 24 | if let Some(_msg) = message.content_as::() { 25 | println!("Hello World from Actor: {:?}", context.aid); 26 | context.system.trigger_shutdown(); 27 | } 28 | Ok(Status::done(())) 29 | } 30 | 31 | pub fn main() { 32 | // First we initialize the actor system using the default config. 33 | let config = ActorSystemConfig::default(); 34 | let system = ActorSystem::create(config); 35 | 36 | // Spawn the actor and send the message. 37 | let aid = system.spawn().with((), hello).unwrap(); 38 | aid.send(Message::new(HelloMessages::Greet)).unwrap(); 39 | 40 | // The actor will trigger shutdown, we just wait for it. 41 | system.await_shutdown(None); 42 | } 43 | -------------------------------------------------------------------------------- /examples/montecarlo.rs: -------------------------------------------------------------------------------- 1 | //! This is an example of a parallel processing implementation of a Monte-Carlo simulation 2 | //! The simulation is of a basic gambling game adapted from this page: 3 | //! https://towardsdatascience.com/the-house-always-wins-monte-carlo-simulation-eb82787da2a3 4 | //! 5 | //! This example demonstrates: 6 | //! * Using many instances of the same actor to run a simulation multiple times in parallel. 7 | //! * Spawning a manager actor that itself spawns other actors. 8 | //! * Using monitors to allow a manager actor to know when each of its child actors have completed 9 | //! their work. 10 | 11 | use maxim::prelude::*; 12 | use rand::{thread_rng, Rng}; 13 | use serde::{Deserialize, Serialize}; 14 | use std::collections::HashMap; 15 | 16 | /// Represents the state of a simplified gambling game as described on the website linked above. 17 | #[derive(Debug, Copy, Clone)] 18 | struct Game { 19 | /// The player's current funds. Funds are allowed to be negative since the player 20 | /// can potentially lose more money than they started with. 21 | funds: i64, 22 | /// How much money the player wagers per turn. 23 | wager: u32, 24 | /// The total number of game rounds that will be played. 25 | total_rounds: u32, 26 | } 27 | 28 | impl Game { 29 | /// This function performs a dice roll according to the rules of the simple gambling game. 30 | /// On average, the player will win their roll 49 out of 100 times, resulting in a house edge 31 | /// of 2% 32 | fn roll_dice() -> bool { 33 | let mut rng = thread_rng(); 34 | match rng.gen_range(0, 101) { 35 | x if x > 51 => true, 36 | _ => false, 37 | } 38 | } 39 | 40 | /// This is the Processor function for the game actors. 41 | async fn play(mut self, ctx: Context, msg: Message) -> ActorResult { 42 | // A game instance starts when the `GameManager` actor sends a message containing its `Aid`. 43 | // This allows this actor to send its results back to the manager once the game is complete. 44 | if let Some(results_aid) = msg.content_as::() { 45 | // Set up some extra starting state for the game. 46 | let mut current_round = 1; 47 | let mut results_vec = Vec::new(); 48 | 49 | // Play the game and record the amount of funds that the player has after each roll of the dice 50 | // in the results_vec. 51 | while current_round <= self.total_rounds { 52 | current_round += 1; 53 | match Game::roll_dice() { 54 | true => self.funds += self.wager as i64, 55 | false => self.funds -= self.wager as i64, 56 | } 57 | results_vec.push(self.funds); 58 | } 59 | 60 | // Now that the game is finished, the results of the game need to be reported 61 | // to the `GameManager`. 62 | results_aid 63 | .send_new(GameMsg::new(ctx.aid.clone(), results_vec)) 64 | .unwrap(); 65 | // Because the `GameManager` is monitoring this actor, sending the `Stop` status 66 | // will inform the manager that this game is now completed. 67 | return Ok(Status::stop(self)); 68 | } 69 | Ok(Status::done(self)) 70 | } 71 | } 72 | 73 | impl Default for Game { 74 | fn default() -> Self { 75 | Self { 76 | funds: 10_000, 77 | wager: 100, 78 | total_rounds: 100, 79 | } 80 | } 81 | } 82 | 83 | /// The message type that `Game` instances send to the `GameManager` when they are finished 84 | /// with their work. 85 | #[derive(Debug, Serialize, Deserialize)] 86 | struct GameMsg { 87 | /// The ID of the actor that sent this message. This is used by the `GameManager` to 88 | /// associate game results with the actor that created them. 89 | aid: Aid, 90 | /// This vec contains a history of the player's current funds after each dice roll during 91 | /// the game. 92 | results_vec: Vec, 93 | } 94 | 95 | impl GameMsg { 96 | fn new(aid: Aid, vec: Vec) -> Self { 97 | Self { 98 | aid, 99 | results_vec: vec, 100 | } 101 | } 102 | } 103 | 104 | /// This actor's job is to spawn a number of `Game` actors and then aggregate the 105 | /// results of each game that is played. 106 | #[derive(Debug)] 107 | struct GameManager { 108 | /// The number of games that have been played so far. 109 | games_finished: u32, 110 | /// The total number of games that are to be played. 111 | total_games: u32, 112 | /// The results of each finished game, keyed by `Game` actor ID 113 | results: HashMap>, 114 | } 115 | 116 | impl GameManager { 117 | fn new(total_games: u32) -> Self { 118 | Self { 119 | games_finished: 0, 120 | total_games, 121 | results: HashMap::new(), 122 | } 123 | } 124 | } 125 | 126 | impl GameManager { 127 | // This is the Processor function for the manager actor. 128 | async fn gather_results(mut self, ctx: Context, msg: Message) -> ActorResult { 129 | // Receive messages from the Game actors and aggregate their results in a `HashMap`. 130 | if let Some(game_msg) = msg.content_as::() { 131 | self.results 132 | .insert(game_msg.aid.clone(), game_msg.results_vec.clone()); 133 | } 134 | 135 | if let Some(sys_msg) = msg.content_as::() { 136 | match &*sys_msg { 137 | // This is the first code that will run in the actor. It spawns the Game actors, 138 | // registers them to its monitoring list, then sends them a message indicating 139 | // that they should start their games. 140 | 141 | // The message contains the `Aid` of this actor, which the `Game` actors will use 142 | // to report their results back to this actor when they are finished. 143 | SystemMsg::Start => { 144 | let game_conditions = Game::default(); 145 | println!("Starting funds: ${}", game_conditions.funds); 146 | println!("Wager per round: ${}", game_conditions.wager); 147 | println!("Rounds per game: {}", game_conditions.total_rounds); 148 | println!("Running simulations..."); 149 | for i in 0..self.total_games { 150 | let name = format!("Game{}", i); 151 | let aid = ctx 152 | .system 153 | .spawn() 154 | .name(&name) 155 | .with(game_conditions, Game::play) 156 | .unwrap(); 157 | ctx.system.monitor(&ctx.aid, &aid); 158 | aid.send_new(ctx.aid.clone()).unwrap(); 159 | } 160 | } 161 | // This code runs each time a monitored `Game` actor stops. Once all the actors are 162 | // finished, the average final results of each game will be printed and then the 163 | // actor system will be shut down. 164 | SystemMsg::Stopped { .. } => { 165 | self.games_finished += 1; 166 | if self.games_finished == self.total_games { 167 | // Each vec of results contains the entire history of a game for every time that 168 | // the dice was rolled. Instead of printing out all of that data, we will simply 169 | // print the average of the funds that the player had at the end of each game. 170 | let average_funds = self 171 | .results 172 | .values() 173 | .map(|v| v.last().unwrap()) 174 | .sum::() 175 | / self.total_games as i64; 176 | println!("Simulations ran: {}", self.results.len()); 177 | println!("Final average funds: ${}", average_funds); 178 | 179 | // We're all done here, time to shut things down. 180 | ctx.system.trigger_shutdown(); 181 | } 182 | } 183 | _ => {} 184 | } 185 | } 186 | Ok(Status::done(self)) 187 | } 188 | } 189 | 190 | const NUM_GAMES: u32 = 100; 191 | 192 | fn main() { 193 | // Initialize the actor system. 194 | let config = ActorSystemConfig::default().message_channel_size(210); 195 | 196 | let system = ActorSystem::create(config); 197 | 198 | // Spawn the results aggregator, which will in turn spawn the Game actors. 199 | system 200 | .spawn() 201 | .name("Manager") 202 | .with(GameManager::new(NUM_GAMES), GameManager::gather_results) 203 | .unwrap(); 204 | 205 | // Wait for the actor system to shut down. 206 | system.await_shutdown(None); 207 | } 208 | -------------------------------------------------------------------------------- /examples/philosophers.rs: -------------------------------------------------------------------------------- 1 | //! An implementation of the Chandy & Misra solution to the classic finite state machine (FSM) 2 | //! concurrency problem known as [Dining Philosophers] 3 | //! (https://en.wikipedia.org/wiki/Dining_philosophers_problem) problem using Maxim. 4 | //! 5 | //! # Demonstrated in this Example: 6 | //! * Basic usage of Actors to solve a classic problem in concurrency. 7 | //! * Communication with Enumeration based messages. 8 | //! * Skipping messages in the channel to defer processing. 9 | //! * Implementation of finite state machine semantics with differential processing. 10 | //! * Ability to send messages to self; i.e. `EtopEating and `BecomeHungry`. 11 | //! * Ability to send messages after a specified time frame. 12 | //! * Ability to create an actor from a closure (the actor collecting metrics and shutting down). 13 | //! * Ability to inject data into a state of an actor (merics map). 14 | //! * Ability to send the same message to several targets without copying (requesting metrics). 15 | //! * Ability to use an enum, `ForkCommand` and `Command` as a message. 16 | //! * Ability to use a struct, `MetricsReply` and `EndSimulation` as a message. 17 | //! * Use of `enum` as well as `struct` values for messages. 18 | //! 19 | //! This example is extremely strict. If the FSM at any time gets out of sync with expectations 20 | //! panics ensue. Some FSM implementations might be quite a bit more lose, preferring to ignore 21 | //! badly timed messages. This is largely up to the user. 22 | 23 | use maxim::prelude::*; 24 | use log::LevelFilter; 25 | use log::{error, info}; 26 | use serde::{Deserialize, Serialize}; 27 | use std::collections::HashMap; 28 | use std::env; 29 | use std::time::{Duration, Instant}; 30 | 31 | /// A command sent to a fork actor. 32 | #[derive(Debug, Serialize, Deserialize)] 33 | pub enum ForkCommand { 34 | /// A command sent when a fork is requested. 35 | RequestFork(Aid), 36 | /// Mark the fork as being used which will mark it dirty. 37 | UsingFork(Aid), 38 | /// Sent to a fork to indicate that it was put down and no longer is in use. This will 39 | /// allow the fork to be sent to the next user. The `Aid` is the id of the current 40 | /// holder of the fork. 41 | ForkPutDown(Aid), 42 | } 43 | 44 | /// A fork for use in the problem. 45 | #[derive(Eq, PartialEq)] 46 | struct Fork { 47 | /// A flag to indicate if the fork is clean or not. 48 | clean: bool, 49 | /// The actor that owns the fork. 50 | owned_by: Option, 51 | } 52 | 53 | impl Fork { 54 | /// Creates a new fork structure, defaulting `clean` to false as per the Chandy problem 55 | /// solution to 'Dining Philosophers'. 56 | fn new() -> Fork { 57 | Fork { 58 | clean: false, 59 | owned_by: None, 60 | } 61 | } 62 | 63 | /// Request that a fork be sent to a philosopher. 64 | fn fork_requested(mut self, context: Context, requester: Aid) -> ActorResult { 65 | match &self.owned_by { 66 | Some(owner) => { 67 | if self.clean { 68 | Ok(Status::skip(self)) 69 | } else { 70 | owner.send_new(Command::GiveUpFork(context.aid.clone()))?; 71 | Ok(Status::skip(self)) 72 | } 73 | } 74 | None => { 75 | self.owned_by = Some(requester.clone()); 76 | requester.send_new(Command::ReceiveFork(context.aid.clone()))?; 77 | Ok(Status::done(self)) 78 | } 79 | } 80 | } 81 | 82 | /// The philosopher that is the current owner of the fork has put it down, making it available 83 | /// for other philosophers to pick up. 84 | fn fork_put_down(mut self, context: Context, sender: Aid) -> ActorResult { 85 | match &self.owned_by { 86 | Some(owner) => { 87 | if owner == &sender { 88 | self.owned_by = None; 89 | self.clean = true; 90 | // Resetting the skip allows fork requests to be processed. 91 | Ok(Status::reset(self)) 92 | } else { 93 | error!( 94 | "[{}] fork_put_down() from non-owner: {} real owner is: {}", 95 | context.aid, sender, owner 96 | ); 97 | Ok(Status::done(self)) 98 | } 99 | } 100 | None => { 101 | error!( 102 | "[{}] fork_put_down() from non-owner: {} real owner is: None:", 103 | context.aid, sender 104 | ); 105 | Ok(Status::done(self)) 106 | } 107 | } 108 | } 109 | 110 | /// The owner of the fork is notifying the fork that they are going to use the fork. This 111 | /// will mark the fork as dirty and make it available to be sent to another philosopher if 112 | /// they request the fork. 113 | fn using_fork(mut self, context: Context, sender: Aid) -> ActorResult { 114 | match &self.owned_by { 115 | Some(owner) => { 116 | if owner == &sender { 117 | self.clean = false; 118 | // Resetting the skip allows fork requests to be processed now that the fork 119 | // has been marked as being dirty. 120 | Ok(Status::reset(self)) 121 | } else { 122 | error!("[{}] Got UsingFork from non-owner: {}", context.aid, sender); 123 | Ok(Status::done(self)) 124 | } 125 | } 126 | _ => { 127 | error!("[{}] Got UsingFork from non-owner: {}", context.aid, sender); 128 | Ok(Status::done(self)) 129 | } 130 | } 131 | } 132 | 133 | /// Handles actor messages, downcasting them to the proper types and then sends the messages 134 | /// to the other functions to handle the details. 135 | pub async fn handle(self, context: Context, message: Message) -> ActorResult { 136 | if let Some(msg) = message.content_as::() { 137 | match &*msg { 138 | ForkCommand::RequestFork(requester) => { 139 | self.fork_requested(context, requester.clone()) 140 | } 141 | ForkCommand::UsingFork(owner) => self.using_fork(context, owner.clone()), 142 | ForkCommand::ForkPutDown(owner) => self.fork_put_down(context, owner.clone()), 143 | } 144 | } else { 145 | Ok(Status::done(self)) 146 | } 147 | } 148 | } 149 | 150 | /// The state of a philosopher at the table. 151 | #[derive(Debug)] 152 | enum PhilosopherState { 153 | /// Philosopher is thinking. 154 | Thinking, 155 | /// Philosopher is hungry and waiting for one or more forks. 156 | Hungry, 157 | /// Has both forks and is eating. 158 | Eating, 159 | } 160 | 161 | /// A command sent to a Philosopher actor. 162 | #[derive(Debug, Serialize, Deserialize)] 163 | pub enum Command { 164 | /// Command to start eating. The u16 is the current state change number when sent this will 165 | /// be used to track whether this message is old or if it should be handled. 166 | StopEating(u16), 167 | /// Command to stop eating. The u16 is the current state change number when sent this will 168 | /// be used to track whether this message is old or if it should be handled. 169 | BecomeHungry(u16), 170 | /// Instructs an actor to receive give up a fork with the given `aid`. 171 | GiveUpFork(Aid), 172 | /// Instructs an actor to receive a fork. 173 | ReceiveFork(Aid), 174 | /// Instructs a philosopher to send the given actor its metrics. 175 | SendMetrics(Aid), 176 | } 177 | 178 | // This struct is a message that carries metrics from a philosopher upon request. 179 | #[derive(Clone, Debug, Serialize, Deserialize)] 180 | struct MetricsReply { 181 | aid: Aid, 182 | metrics: Metrics, 183 | } 184 | 185 | /// A struct that holds metrics for a philosopher. 186 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 187 | struct Metrics { 188 | /// The number of state changes that have occurred. 189 | state_change_count: u16, 190 | /// The number of times a Philosopher failed to eat because he didnt have both forks. 191 | failed_to_eat: u16, 192 | /// The time that the Philosopher spent thinking. 193 | time_thinking: Duration, 194 | /// The time that the Philosopher spent hungry. 195 | time_hungry: Duration, 196 | /// The time that the Philosopher spent eating. 197 | time_eating: Duration, 198 | } 199 | 200 | /// The structure holding the state of the philosopher actor. 201 | struct Philosopher { 202 | /// The size of the time slice to use. This is used for scheduled state changes. 203 | time_slice: Duration, 204 | /// The current state that the philosopher is in. 205 | state: PhilosopherState, 206 | /// The `Aid` of the philosopher's left fork. 207 | left_fork_aid: Aid, 208 | /// Whether the philosopher has the left fork. 209 | has_left_fork: bool, 210 | /// Whether or not the philosopher has requested the left fork. 211 | left_fork_requested: bool, 212 | /// The `Aid` of the philosopher's right fork. 213 | right_fork_aid: Aid, 214 | /// Whether the philosopher has the right fork. 215 | has_right_fork: bool, 216 | /// Whether or not the philosopher has requested the right fork. 217 | right_fork_requested: bool, 218 | /// The last time the philosopher's state changed. This is used for tracking time eating, etc. 219 | last_state_change: Instant, 220 | /// The metrics for this actor. 221 | metrics: Metrics, 222 | } 223 | 224 | impl Philosopher { 225 | /// Creates a new dining philosopher that starts hungry by default. The passed fork aids 226 | /// are used to request forks for eating. 227 | pub fn new(time_slice: Duration, left_fork_aid: Aid, right_fork_aid: Aid) -> Philosopher { 228 | Philosopher { 229 | time_slice, 230 | state: PhilosopherState::Thinking, 231 | left_fork_aid, 232 | has_left_fork: false, 233 | left_fork_requested: false, 234 | right_fork_aid, 235 | has_right_fork: false, 236 | right_fork_requested: false, 237 | last_state_change: Instant::now(), 238 | metrics: Metrics { 239 | state_change_count: 0, 240 | failed_to_eat: 0, 241 | time_thinking: Duration::from_micros(0), 242 | time_hungry: Duration::from_micros(0), 243 | time_eating: Duration::from_micros(0), 244 | }, 245 | } 246 | } 247 | 248 | /// Changes the philosopher to a state of eating. 249 | fn begin_eating(&mut self, context: Context) -> Result<(), StdError> { 250 | self.metrics.time_hungry += Instant::elapsed(&self.last_state_change); 251 | self.last_state_change = Instant::now(); 252 | self.state = PhilosopherState::Eating; 253 | self.metrics.state_change_count += 1; 254 | // Now that we are eating we will tell the fork that we are using it, 255 | // thus marking the fork as dirty. 256 | self.left_fork_aid 257 | .send_new(ForkCommand::UsingFork(context.aid.clone()))?; 258 | self.right_fork_aid 259 | .send_new(ForkCommand::UsingFork(context.aid.clone()))?; 260 | 261 | // Schedule to stop eating after an eating time slice elapsed. 262 | let msg = Message::new(Command::StopEating(self.metrics.state_change_count)); 263 | context.aid.send_after(msg, self.time_slice)?; 264 | Ok(()) 265 | } 266 | 267 | /// The philosopher received a fork. Once they have both forks they can start eating. 268 | /// Otherwise they have to wait for the other fork to begin eating. 269 | fn fork_received(mut self, context: Context, fork_aid: Aid) -> ActorResult { 270 | if self.left_fork_aid == fork_aid { 271 | self.has_left_fork = true; 272 | self.left_fork_requested = false; 273 | } else if self.right_fork_aid == fork_aid { 274 | self.has_right_fork = true; 275 | self.right_fork_requested = false; 276 | } else { 277 | panic!("[{}] Unknown Fork Received: {}", context.aid, fork_aid); 278 | } 279 | 280 | // If we have both forks then we can start eating. 281 | if self.has_left_fork && self.has_right_fork { 282 | self.begin_eating(context)?; 283 | } 284 | Ok(Status::done(self)) 285 | } 286 | 287 | /// Helper to request forks that the philosopher doesnt have. 288 | fn request_missing_forks(&mut self, context: Context) -> Result<(), StdError> { 289 | if !self.has_left_fork && !self.left_fork_requested { 290 | self.left_fork_requested = true; 291 | self.left_fork_aid 292 | .send_new(ForkCommand::RequestFork(context.aid.clone()))?; 293 | } 294 | if !self.has_right_fork && !self.right_fork_requested { 295 | self.right_fork_requested = true; 296 | self.right_fork_aid 297 | .send_new(ForkCommand::RequestFork(context.aid.clone()))?; 298 | } 299 | Ok(()) 300 | } 301 | 302 | /// The philosopher is being instructed to get hungry which will cause them to ask for the 303 | /// forks to eat. Note that since the `BecomeHungry` message is sent as a scheduled message 304 | /// it may arrive after the philosopher has already changed state. For this reason we track 305 | /// the state change count and compare it with the number in the message. 306 | fn become_hungry(mut self, context: Context, state_num: u16) -> ActorResult { 307 | if self.metrics.state_change_count == state_num { 308 | if self.has_left_fork && self.has_right_fork { 309 | self.begin_eating(context)?; 310 | } else { 311 | match &self.state { 312 | PhilosopherState::Thinking => { 313 | self.metrics.time_thinking += Instant::elapsed(&self.last_state_change); 314 | self.last_state_change = Instant::now(); 315 | self.state = PhilosopherState::Hungry; 316 | self.metrics.state_change_count += 1; 317 | self.request_missing_forks(context)?; 318 | } 319 | PhilosopherState::Hungry => { 320 | error!("[{}] Got BecomeHungry while eating!", context.aid); 321 | } 322 | PhilosopherState::Eating => { 323 | error!("[{}] Got BecomeHungry while eating!", context.aid); 324 | } 325 | }; 326 | } 327 | } 328 | Ok(Status::done(self)) 329 | } 330 | 331 | /// Changes the philosopher to the state of thinking. Note that this doesn't mean that the 332 | /// philosopher will put down his forks. He will only do that if requested to. 333 | fn begin_thinking(&mut self, context: Context) -> Result<(), StdError> { 334 | self.state = PhilosopherState::Thinking; 335 | self.metrics.state_change_count += 1; 336 | self.metrics.time_eating += Instant::elapsed(&self.last_state_change); 337 | self.last_state_change = Instant::now(); 338 | 339 | let msg = Message::new(Command::BecomeHungry(self.metrics.state_change_count)); 340 | context.aid.send_after(msg, self.time_slice)?; 341 | Ok(()) 342 | } 343 | 344 | /// Processes a command to stop eating. Note that this can be received in any state because 345 | /// it is a delayed message send and thus it was enqueued when the philosopher was in the 346 | /// eating state but the philosopher might be in another state when received. That is why 347 | /// we track the state change count and compare it with the number in the message. 348 | fn stop_eating(mut self, context: Context, state_num: u16) -> ActorResult { 349 | if self.metrics.state_change_count == state_num { 350 | if let PhilosopherState::Eating = &self.state { 351 | self.begin_thinking(context)?; 352 | } 353 | } 354 | Ok(Status::done(self)) 355 | } 356 | 357 | /// Processes a command to a philosopher to give up a fork. Note that this can be received 358 | /// when the philosopher is in any state since the philosopher will not put down a fork 359 | /// unless he is asked to. A philosopher can be eating, stop eating and start thinking 360 | /// and then start eating again if no one asked for his forks. The fork actor is the only 361 | /// actor sending this message and it will only do so if the fork is dirty. 362 | fn give_up_fork(mut self, context: Context, fork_aid: Aid) -> ActorResult { 363 | if self.left_fork_aid == fork_aid { 364 | if self.has_left_fork { 365 | self.has_left_fork = false; 366 | fork_aid.send_new(ForkCommand::ForkPutDown(context.aid.clone()))?; 367 | } 368 | } else if self.right_fork_aid == fork_aid { 369 | if self.has_right_fork { 370 | self.has_right_fork = false; 371 | fork_aid.send_new(ForkCommand::ForkPutDown(context.aid.clone()))?; 372 | } 373 | } else { 374 | error!( 375 | "[{}] Unknown fork asked for: {}:\n left ==> {}\n right ==> {}", 376 | context.aid, fork_aid, self.left_fork_aid, self.right_fork_aid 377 | ); 378 | } 379 | 380 | match &self.state { 381 | PhilosopherState::Hungry => { 382 | self.metrics.failed_to_eat += 1; 383 | self.begin_thinking(context)?; 384 | } 385 | PhilosopherState::Eating => { 386 | self.begin_thinking(context)?; 387 | } 388 | _ => (), 389 | } 390 | Ok(Status::done(self)) 391 | } 392 | 393 | /// A function that handles sending metrics to an actor that requests the metrics. 394 | fn send_metrics(self, context: Context, reply_to: Aid) -> ActorResult { 395 | // We copy the metrics because we want to send immutable data. This call 396 | // cant move the metrics out of self so it must copy them. 397 | reply_to.send_new(MetricsReply { 398 | aid: context.aid.clone(), 399 | metrics: self.metrics, 400 | })?; 401 | Ok(Status::done(self)) 402 | } 403 | 404 | /// Handle a message for a dining philosopher, mostly dispatching to another method to 405 | /// manage the details of handling the message. The only exception being the `Start` 406 | /// system message which is handled inline. 407 | pub async fn handle(self, context: Context, message: Message) -> ActorResult { 408 | if let Some(msg) = message.content_as::() { 409 | match &*msg { 410 | Command::StopEating(state_num) => self.stop_eating(context, *state_num), 411 | Command::BecomeHungry(state_num) => self.become_hungry(context, *state_num), 412 | Command::ReceiveFork(fork_aid) => self.fork_received(context, fork_aid.clone()), 413 | Command::GiveUpFork(fork_aid) => self.give_up_fork(context, fork_aid.clone()), 414 | Command::SendMetrics(reply_to) => self.send_metrics(context, reply_to.clone()), 415 | } 416 | } else if let Some(msg) = message.content_as::() { 417 | match &*msg { 418 | // Note that we generally want to make handling this message last as we know that 419 | // this message will be received only once so we want to put the most used 420 | // message types first. 421 | SystemMsg::Start => { 422 | context.aid.send_new(Command::BecomeHungry(0))?; 423 | Ok(Status::done(self)) 424 | } 425 | _ => Ok(Status::done(self)), 426 | } 427 | } else { 428 | Ok(Status::done(self)) 429 | } 430 | } 431 | } 432 | 433 | // This will serve as a signal to shutdown the simulation. 434 | #[derive(Clone, Debug, Serialize, Deserialize)] 435 | struct EndSimulation {} 436 | 437 | /// Main method of the dining philosophers problem. This sets up the solution and starts the 438 | /// actors. 439 | pub fn main() { 440 | let args: Vec = env::args().collect(); 441 | let level = if args.contains(&"-v".to_string()) { 442 | LevelFilter::Debug 443 | } else { 444 | LevelFilter::Info 445 | }; 446 | 447 | env_logger::builder() 448 | .filter_level(level) 449 | .is_test(true) 450 | .try_init() 451 | .unwrap(); 452 | 453 | // FIXME Let the user pass in the number of philosophers at the table, time slice 454 | // and runtime as command line parameters. 455 | let count = 5 as usize; 456 | let time_slice = Duration::from_millis(10); 457 | let run_time = Duration::from_millis(5000); 458 | let mut forks: Vec = Vec::with_capacity(count); 459 | let mut results: HashMap> = HashMap::with_capacity(count); 460 | 461 | // Initialize the actor system. 462 | let config = ActorSystemConfig::default().thread_pool_size(4); 463 | let system = ActorSystem::create(config); 464 | 465 | // Spawn the fork actors clockwise from top of table. 466 | for i in 0..count { 467 | let name = format!("Fork-{}", i); 468 | 469 | let fork = system 470 | .spawn() 471 | .name(&name) 472 | .with(Fork::new(), Fork::handle) 473 | .unwrap(); 474 | forks.push(fork); 475 | } 476 | 477 | // FIXME Make this list support many more philosophers. 478 | let names = vec![ 479 | "Confucius", 480 | "Laozi", 481 | "Descartes", 482 | "Ben Franklin", 483 | "Thomas Jefferson", 484 | ]; 485 | 486 | // Spawn the philosopher actors clockwise from top of table. 487 | for left in 0..count { 488 | let right = if left == 0 { count - 1 } else { left - 1 }; 489 | let state = Philosopher::new(time_slice, forks[left].clone(), forks[right].clone()); 490 | 491 | let philosopher = system 492 | .spawn() 493 | .name(names[left]) 494 | .with(state, Philosopher::handle) 495 | .unwrap(); 496 | results.insert(philosopher, None); 497 | } 498 | 499 | // This actor is created with a closure and when it gets the timed message it will 500 | // request metrics of all of the actors and then print the metrics when all collected 501 | // and shut down the actor system. 502 | let _shutdown = system 503 | .spawn() 504 | .name("Manager") 505 | .with( 506 | results, 507 | move |mut state: HashMap>, context: Context, message: Message| { 508 | async move { 509 | if let Some(msg) = message.content_as::() { 510 | state.insert(msg.aid.clone(), Some(msg.metrics)); 511 | 512 | // Check to see if we have all of the metrics collected and if so then 513 | // output the results of the simulation and end the program by shutting 514 | // down the actor system. 515 | if !state.iter().any(|(_, metrics)| metrics.is_none()) { 516 | info!("Final Metrics:"); 517 | for (aid, metrics) in state.iter() { 518 | info!("{}: {:?}", aid, metrics); 519 | } 520 | context.system.trigger_shutdown(); 521 | } 522 | } else if let Some(_) = message.content_as::() { 523 | // We create a message that will be sent to all actors in our list. Note that 524 | // we can send the message with an extremely lightweight clone. 525 | let request = Message::new(Command::SendMetrics(context.aid.clone())); 526 | for (aid, _) in state.iter() { 527 | aid.send(request.clone())?; 528 | } 529 | } else if let Some(msg) = message.content_as::() { 530 | // FIXME SERIOUSLY consider making SystemMsg variants into structs to simplify 531 | // code. 532 | if let SystemMsg::Start = &*msg { 533 | let msg = Message::new(EndSimulation {}); 534 | context.aid.send_after(msg, run_time)?; 535 | } 536 | } 537 | Ok(Status::done(state)) 538 | } 539 | }, 540 | ) 541 | .expect("failed to create shutdown actor"); 542 | 543 | system.await_shutdown(None); 544 | } 545 | -------------------------------------------------------------------------------- /modoc.config: -------------------------------------------------------------------------------- 1 | # Uses the `cargo-modoc` plugin to add the README contents to the lib.rs doc string. 2 | # See: https://github.com/kurtlawrence/cargo-modoc 3 | "README.md" = [ "src/lib.rs" ] 4 | -------------------------------------------------------------------------------- /src/cluster.rs: -------------------------------------------------------------------------------- 1 | //! Implements a cluster manager for Maxim that manages connections to remote actor systems 2 | //! over TCP. 3 | //! 4 | //! This is a reference implmentation for creating a cluster manager for Maxim. The developer can 5 | //! use any technology they want for managing an Maxim cluster so long as it supports bridging two 6 | //! actor systems with channels. This implementation achieves that bridge through generic 7 | //! run-of-the-mill TCP connections. This is not to say that this code is simple, or usable only 8 | //! for a reference. It is designed to be the default way Maxim is clustered and thus it will be 9 | //! robust and well tested like the rest of Maxim. 10 | 11 | use crate::prelude::*; 12 | use log::{error, info}; 13 | use secc::*; 14 | use std::collections::HashMap; 15 | use std::io::prelude::*; 16 | use std::io::{BufReader, BufWriter}; 17 | use std::net::{SocketAddr, TcpListener, TcpStream}; 18 | use std::sync::atomic::{AtomicBool, Ordering}; 19 | use std::sync::{Arc, RwLock}; 20 | use std::sync::{Condvar, Mutex}; 21 | use std::thread; 22 | use std::thread::JoinHandle; 23 | use std::time::Duration; 24 | use uuid::Uuid; 25 | 26 | /// Encapsulates information on a connection to another actor system. 27 | struct ConnectionData { 28 | /// Uuid of the system that this manager is connected to. 29 | pub system_uuid: Uuid, 30 | /// The uuid of the system that this connection data references. 31 | pub address: SocketAddr, 32 | /// The sender used to send wire messages to the connected actor system. 33 | pub sender: SeccSender, 34 | /// The receiver used to send wire messages to the connected actor system. 35 | pub receiver: SeccReceiver, 36 | /// A join handle for the thread managing the transmitting. 37 | pub tx_handle: JoinHandle<()>, 38 | /// A join handle for the thread managing the receiving. 39 | pub rx_handle: JoinHandle<()>, 40 | } 41 | 42 | /// Data for the [`TcpClusterMgr`]. 43 | struct TcpClusterMgrData { 44 | /// Address that this manager is listening for connections on. 45 | listen_address: SocketAddr, 46 | /// Actor System that this manager is attached to. 47 | system: ActorSystem, 48 | /// Handle to the thread that is listening for connections. 49 | listener: RwLock>>, 50 | /// A map containing the data for all of the connections to this server. 51 | connections: RwLock>, 52 | /// A flag to exit the loops. 53 | running: AtomicBool, 54 | } 55 | 56 | #[derive(Clone)] 57 | pub struct TcpClusterMgr { 58 | data: Arc, 59 | } 60 | 61 | impl TcpClusterMgr { 62 | /// Creates a new manager attached to the given actor system that manages connections to other 63 | /// [`TcpClusterMgr`]s. 64 | pub fn create(system: &ActorSystem, address: SocketAddr) -> TcpClusterMgr { 65 | let result = TcpClusterMgr { 66 | data: Arc::new(TcpClusterMgrData { 67 | listen_address: address, 68 | system: system.clone(), 69 | listener: RwLock::new(None), 70 | connections: RwLock::new(HashMap::new()), 71 | running: AtomicBool::new(true), 72 | }), 73 | }; 74 | 75 | { 76 | // We will create a condvar so we wait for the listener to be up before the function 77 | // returning to the caller of this function. Note that we lock before starting the 78 | // listener so that we dont miss a notify and loop endlessly. 79 | let pair = Arc::new((Mutex::new(false), Condvar::new())); 80 | let (mutex, condvar) = &*pair; 81 | let mut started = mutex.lock().unwrap(); 82 | let join_handle = result.start_tcp_listener(pair.clone()); 83 | while !*started { 84 | started = condvar.wait(started).unwrap(); 85 | } 86 | 87 | // We store the handle for the thread for later shutdown reasons. 88 | let mut handle = result.data.listener.write().unwrap(); 89 | *handle = Some(join_handle); 90 | } 91 | 92 | result 93 | } 94 | 95 | // Starts a TCP listener that listens for incomming connections from other [`TcpClusterMgr`]s 96 | // and then creates a remote channel thread with the other actor system. 97 | fn start_tcp_listener(&self, pair: Arc<(Mutex, Condvar)>) -> JoinHandle<()> { 98 | let system = self.data.system.clone(); 99 | let address = self.data.listen_address; 100 | let manager = self.clone(); 101 | thread::spawn(move || { 102 | system.init_current(); 103 | let sys_uuid = system.uuid(); 104 | let listener = TcpListener::bind(address).unwrap(); 105 | info!("{}: Listening for connections on {}.", sys_uuid, address); 106 | 107 | // Notify the cluster manager that the listener is ready. 108 | let (mutex, condvar) = &*pair; 109 | let mut started = mutex.lock().unwrap(); 110 | *started = true; 111 | condvar.notify_all(); 112 | drop(started); 113 | 114 | // Starts a loop waiting for connections from other managers. 115 | // FIXME Create a Shutdown Mechanism 116 | while manager.data.running.load(Ordering::Relaxed) { 117 | match listener.accept() { 118 | Ok((stream, socket_address)) => { 119 | info!( 120 | "{}: Accepting connection from: {}.", 121 | sys_uuid, socket_address 122 | ); 123 | manager.start_tcp_threads(stream, socket_address); 124 | } 125 | Err(e) => { 126 | error!("couldn't get client: {:?}", e); 127 | } 128 | } 129 | } 130 | }) 131 | } 132 | 133 | /// Connects to another [`TcpClusterMgr`] with TCP at the given socket address. 134 | pub fn connect(&self, address: SocketAddr, timeout: Duration) -> std::io::Result<()> { 135 | // FIXME Error handling needs to be improved. 136 | let stream = TcpStream::connect_timeout(&address, timeout)?; 137 | self.start_tcp_threads(stream, address); 138 | Ok(()) 139 | } 140 | 141 | /// Connects this actor system to a remote actor system using the given string which contains 142 | /// `host:port` for the other actor sytem. 143 | fn start_tcp_threads(&self, stream: TcpStream, address: SocketAddr) { 144 | let arc_stream = Arc::new(stream); 145 | 146 | // FIXME: Allow channel size and poll to be configurable. 147 | let (sender, receiver) = secc::create::(32, Duration::from_millis(10)); 148 | let system_uuid = self.data.system.connect(&sender, &receiver); 149 | 150 | // Create the threads that manage the connections between the two systems. 151 | let tx_handle = self.start_tx_thread(arc_stream.clone(), receiver.clone()); 152 | let rx_handle = self.start_rx_thread(arc_stream, sender.clone()); 153 | 154 | let data = ConnectionData { 155 | system_uuid, 156 | address, 157 | receiver, 158 | sender, 159 | tx_handle, 160 | rx_handle, 161 | }; 162 | 163 | info!( 164 | "{:?}: Connected to {:?}@{:?}", 165 | self.data.system.uuid(), 166 | system_uuid, 167 | address 168 | ); 169 | 170 | let mut connections = self.data.connections.write().unwrap(); 171 | connections.insert(data.system_uuid, data); 172 | } 173 | 174 | /// Starts the thread that takes messages off the receiver from the actor system channel 175 | /// and sends them to the remote system. 176 | fn start_tx_thread( 177 | &self, 178 | stream: Arc, 179 | receiver: SeccReceiver, 180 | ) -> JoinHandle<()> { 181 | // This thread manages transmitting messages to the stream. 182 | let system = self.data.system.clone(); 183 | let manager = self.clone(); 184 | thread::spawn(move || { 185 | system.init_current(); 186 | let mut writer = BufWriter::new(&*stream); 187 | 188 | // FIXME Put in a mechanism for soft shutdown. 189 | // FIXME Allow configurable timeout. 190 | // FIXME Errors are not currently handled! 191 | while manager.data.running.load(Ordering::Relaxed) { 192 | if let Ok(message) = receiver.receive_await_timeout(Duration::from_millis(10)) { 193 | bincode::serialize_into(&mut writer, &message).unwrap(); 194 | writer.flush().unwrap(); 195 | } 196 | } 197 | }) 198 | } 199 | 200 | /// Starts the thread that receives messages from the wire and puts them on the sender 201 | /// to send them to the actor system for processing. 202 | fn start_rx_thread( 203 | &self, 204 | stream: Arc, 205 | sender: SeccSender, 206 | ) -> JoinHandle<()> { 207 | let system = self.data.system.clone(); 208 | let manager = self.clone(); 209 | 210 | // This thread manages receiving messages from the stream. 211 | // FIXME Errors are not currently handled! 212 | // FIXME No mechanism to exit softly now. 213 | thread::spawn(move || { 214 | system.init_current(); 215 | let mut reader = BufReader::new(&*stream); 216 | while manager.data.running.load(Ordering::Relaxed) { 217 | let msg: WireMessage = bincode::deserialize_from(&mut reader).unwrap(); 218 | sender.send(msg).unwrap(); 219 | } 220 | }) 221 | } 222 | } 223 | 224 | #[cfg(test)] 225 | mod tests { 226 | use crate::tests::*; 227 | 228 | use super::*; 229 | 230 | #[test] 231 | fn test_tcp_remote_connect() { 232 | init_test_log(); 233 | 234 | let socket_addr1 = SocketAddr::from(([127, 0, 0, 1], 7717)); 235 | let system1 = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 236 | let cluster_mgr1 = TcpClusterMgr::create(&system1, socket_addr1); 237 | 238 | let socket_addr2 = SocketAddr::from(([127, 0, 0, 1], 7727)); 239 | let system2 = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 240 | let _cluster_mgr2 = TcpClusterMgr::create(&system2, socket_addr2); 241 | 242 | // thread::sleep(Duration::from_millis(5000)); 243 | cluster_mgr1 244 | .connect(socket_addr2, Duration::from_millis(2000)) 245 | .unwrap(); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/executor.rs: -------------------------------------------------------------------------------- 1 | //! The Executor is responsible for the high-level scheduling of Actors. 2 | 3 | use crate::actors::ActorStream; 4 | use crate::executor::thread_pool::MaximThreadPool; 5 | use crate::prelude::*; 6 | use dashmap::DashMap; 7 | use futures::task::ArcWake; 8 | use futures::Stream; 9 | use log::{debug, info, trace, warn}; 10 | use std::collections::{BTreeMap, VecDeque}; 11 | use std::pin::Pin; 12 | use std::sync::{Arc, Condvar, Mutex, RwLock}; 13 | use std::task::{Context, Poll, Waker}; 14 | use std::time::{Duration, Instant}; 15 | 16 | mod thread_pool; 17 | 18 | /// The Executor is responsible for the high-level scheduling of Actors. When an Actor is 19 | /// registered, it is wrapped in a Task and added to the sleep queue. When the Actor is woken by a 20 | /// sent message, the Executor will check load balancing data and queue it in the Reactor with the 21 | /// least load. 22 | #[derive(Clone)] 23 | pub(crate) struct MaximExecutor { 24 | /// The system's "is shutting down" flag. 25 | shutdown_triggered: Arc<(Mutex, Condvar)>, 26 | /// Barrier to await shutdown on. 27 | thread_pool: Arc, 28 | /// Actors that have no messages available. 29 | sleeping: Arc>, 30 | /// All Reactors owned by this Executor. 31 | reactors: Arc>, 32 | /// Counting actors per reactor for even distribution of Actors. 33 | actors_per_reactor: Arc>, 34 | } 35 | 36 | impl MaximExecutor { 37 | /// Creates a new Executor with the given actor system configuration. This will govern the 38 | /// configuration of the executor. 39 | pub(crate) fn new(shutdown_triggered: Arc<(Mutex, Condvar)>) -> Self { 40 | Self { 41 | shutdown_triggered, 42 | thread_pool: Default::default(), 43 | sleeping: Default::default(), 44 | reactors: Default::default(), 45 | actors_per_reactor: Default::default(), 46 | } 47 | } 48 | 49 | /// Initializes the executor and starts the MaximReactor instances based on the count of the 50 | /// number of threads configured in the actor system. This must be called before any work can 51 | /// be performed with the actor system. 52 | pub(crate) fn init(&self, system: &ActorSystem) { 53 | for i in 0..system.data.config.thread_pool_size { 54 | let reactor = MaximReactor::new(self.clone(), system, i); 55 | self.reactors.insert(i, reactor.clone()); 56 | self.actors_per_reactor.insert(i, 0); 57 | let sys = system.clone(); 58 | info!("Spawning Reactors"); 59 | self.thread_pool 60 | .spawn(format!("Reactor-{}", reactor.name), move || { 61 | sys.init_current(); 62 | futures::executor::enter().expect("Executor nested in other executor"); 63 | loop { 64 | // `MaximReactor::thread` returns true if it's set to be ran again. 65 | if !reactor.thread() { 66 | break; 67 | } 68 | } 69 | }); 70 | } 71 | } 72 | 73 | /// This gives the ActorStream to the Executor to manage. This must be ran before any messages 74 | /// are sent to the Actor, else it will fail to be woken until after its registered. 75 | pub(crate) fn register_actor(&self, actor: ActorStream) { 76 | let id = actor.context.aid.clone(); 77 | let actor = Mutex::new(Box::pin(actor)); 78 | 79 | self.sleeping.insert(id.clone(), Task { id, actor }); 80 | } 81 | 82 | /// This wakes an ActorStream in the Executor which will cause its future to be polled. The Aid, 83 | /// through the ActorSystem, will call this on Message Send. 84 | pub(crate) fn wake(&self, id: Aid) { 85 | trace!("Waking Actor `{}`", id.name_or_uuid()); 86 | // Pull the Task 87 | let task = match self.sleeping.remove(&id) { 88 | Some((_, task)) => task, 89 | None => { 90 | debug!( 91 | "Actor `{}` not in Executor - already woken or stopped", 92 | id.name_or_uuid() 93 | ); 94 | return; 95 | } 96 | }; 97 | // Get the optimal Reactor 98 | let destination = self.get_reactor_with_least_actors(); 99 | // Increment the Reactor's Actor count 100 | *self.actors_per_reactor.get_mut(&destination).unwrap() += 1; 101 | // Insert in the Reactor 102 | self.reactors.get(&destination).unwrap().insert(task); 103 | } 104 | 105 | /// Iterates over the actors-per-reactor collection, and finds the Reactor with the least number 106 | /// of Actors. 107 | /// 108 | /// Note: while this is a little race-y, it's acceptable. It will produce adequate distribution. 109 | fn get_reactor_with_least_actors(&self) -> u16 { 110 | let mut iter_state = (0u16, u32::max_value()); 111 | for i in self.actors_per_reactor.iter() { 112 | if i.value() < &iter_state.1 { 113 | iter_state = (*i.key(), *i.value()); 114 | } 115 | } 116 | iter_state.0 117 | } 118 | 119 | /// When a Reactor is done with an task, it will be sent here, and the Executor will decrement 120 | /// the Actor count for that Reactor. 121 | fn return_task(&self, task: Task, reactor: &MaximReactor) { 122 | trace!( 123 | "Actor {} returned from Reactor {}", 124 | task.id.name_or_uuid(), 125 | reactor.name 126 | ); 127 | // Put the Task back. 128 | self.sleeping.insert(task.id.clone(), task); 129 | // Decrement the Reactor's Actor count. 130 | *self.actors_per_reactor.get_mut(&reactor.id).unwrap() -= 1; 131 | } 132 | 133 | /// Block until the threads have finished shutting down. This MUST be called AFTER shutdown is 134 | /// triggered. 135 | pub(crate) fn await_shutdown(&self, timeout: impl Into>) -> ShutdownResult { 136 | let start = Instant::now(); 137 | info!("Notifying Reactor threads, so they can end gracefully"); 138 | for r in self.reactors.iter() { 139 | match r.thread_condvar.read() { 140 | Ok(g) => g.1.notify_one(), 141 | Err(_) => return ShutdownResult::Panicked, 142 | } 143 | } 144 | let timeout = timeout.into().map(|t| t - (Instant::now() - start)); 145 | info!("Awaiting the threadpool's shutdown"); 146 | self.thread_pool.await_shutdown(timeout) 147 | } 148 | } 149 | 150 | /// Result of awaiting shutdown. 151 | #[derive(Debug, Eq, PartialEq)] 152 | pub enum ShutdownResult { 153 | Ok, 154 | TimedOut, 155 | Panicked, 156 | } 157 | 158 | /// The Reactor is a wrapper for a worker thread. It contains the queues, locks, and other state 159 | /// information necessary to manage the work load and worker thread. 160 | /// 161 | /// Actors are added to the Reactor on waking, queued for polling. If they can be polled again, they 162 | /// are retained till they are depleted of messages or are stopped. 163 | #[derive(Clone)] 164 | pub(crate) struct MaximReactor { 165 | /// The ID of the Reactor 166 | id: u16, 167 | /// The diagnostic ID of this Reactor. 168 | name: String, 169 | /// The Executor that owns this Reactor. 170 | executor: MaximExecutor, 171 | /// The queue of Actors that are ready to be polled. 172 | run_queue: Arc>>, 173 | /// The queue of Actors this Reactor is responsible for. 174 | wait_queue: Arc>>, 175 | /// This is used to pause/resume threads that run out of work. 176 | thread_condvar: Arc, Condvar)>>, 177 | /// How long the thread waits on the thread_condvar before timing out and looping anyways. 178 | thread_wait_time: Duration, 179 | /// How long to work on an Actor before moving on to the next Wakeup. 180 | time_slice: Duration, 181 | /// If an `ActorStream::poll_next` takes longer than this, it will log a warning. 182 | warn_threshold: Duration, 183 | } 184 | 185 | // A little hack to dictate a loop from inside a function call. 186 | enum LoopResult { 187 | Ok(T), 188 | Continue, 189 | } 190 | 191 | impl MaximReactor { 192 | /// Creates a new Reactor 193 | fn new(executor: MaximExecutor, system: &ActorSystem, id: u16) -> MaximReactor { 194 | let name = format!("{:08x?}-{}", system.data.uuid.as_fields().0, id); 195 | debug!("Creating Reactor {}", name); 196 | 197 | MaximReactor { 198 | id, 199 | name, 200 | executor, 201 | run_queue: Arc::new(RwLock::new(Default::default())), 202 | wait_queue: Arc::new(RwLock::new(BTreeMap::new())), 203 | thread_condvar: Arc::new(RwLock::new((Mutex::new(()), Condvar::new()))), 204 | thread_wait_time: system.config().thread_wait_time, 205 | time_slice: system.config().time_slice, 206 | warn_threshold: system.config().warn_threshold, 207 | } 208 | } 209 | 210 | /// Moves an Actor from the executor into a reactor. 211 | fn insert(&self, task: Task) { 212 | let token = Token { 213 | id: task.id.clone(), 214 | reactor: self.clone(), 215 | }; 216 | let waker = futures::task::waker(Arc::new(token)); 217 | let wakeup = Wakeup { 218 | id: task.id.clone(), 219 | waker, 220 | }; 221 | self.wait(task); 222 | self.wake(wakeup); 223 | } 224 | 225 | /// This is the core unit of work that drives the Reactor. The Executor should run this on an 226 | /// endless loop. Returns `false` when it should no longer be ran. 227 | pub(crate) fn thread(&self) -> bool { 228 | // If we're shutting down, quit. 229 | { 230 | if *self 231 | .executor 232 | .shutdown_triggered 233 | .0 234 | .lock() 235 | .expect("Poisoned shutdown_triggered condvar") 236 | { 237 | debug!("Reactor-{} acknowledging shutdown", self.name); 238 | return false; 239 | } 240 | } 241 | 242 | let (w, mut task) = match self.get_work() { 243 | LoopResult::Ok(v) => v, 244 | LoopResult::Continue => return true, 245 | }; 246 | let aid = w.id.clone(); 247 | 248 | let end = Instant::now() + self.time_slice; 249 | loop { 250 | let start = Instant::now(); 251 | // This polls the Actor as a Stream. 252 | match task.poll(&w.waker) { 253 | Poll::Ready(result) => { 254 | // Ready(None) indicates an empty message queue. Time to sleep. 255 | if result.is_none() { 256 | self.executor.return_task(task, self); 257 | break; 258 | } 259 | // The Actor should handle its own internal modifications in response to the 260 | // result. 261 | let is_stopping = { 262 | task.actor 263 | .lock() 264 | .expect("Poisoned Actor") 265 | .handle_result(result.unwrap()) 266 | }; 267 | // It's dead, Jim. 268 | if is_stopping { 269 | break; 270 | } 271 | // If we're past this timeslice, add back into the queues and move 272 | // to the next woken Actor. Else, poll it again. 273 | if Instant::now() >= end { 274 | self.wait(task); 275 | self.wake(w); 276 | break; 277 | } 278 | } 279 | // Still pending, return to wait_queue. Drop the wakeup, because the futures 280 | // will re-add it later through their wakers. 281 | Poll::Pending => { 282 | trace!("Reactor-{} waiting on pending Actor", self.name); 283 | self.wait(task); 284 | break; 285 | } 286 | } 287 | if Instant::now().duration_since(start) >= self.warn_threshold { 288 | warn!( 289 | "Actor {} took longer than configured warning threshold", 290 | aid.name_or_uuid() 291 | ); 292 | } 293 | } 294 | true 295 | } 296 | 297 | // If there's no Actors woken, the Reactor thread will block on the condvar. If there's a Wakeup 298 | // without an Actor (which might happen due to an acceptable race condition), we can continue to 299 | // the next woken Actor, and drop this Wakeup. Otherwise, we have the Wakeup and Task we need, 300 | // and can continue. 301 | // 302 | // While this arrangement is a little dense, it saves a level of indentation. 303 | #[inline] 304 | fn get_work(&self) -> LoopResult<(Wakeup, Task)> { 305 | if let Some(w) = self.get_woken() { 306 | if let Some(task) = self.remove_waiting(&w.id) { 307 | trace!( 308 | "Reactor-{} received Wakeup for Actor `{}`", 309 | self.name, 310 | task.id.name_or_uuid() 311 | ); 312 | LoopResult::Ok((w, task)) 313 | } else { 314 | trace!("Reactor-{} dropping spurious WakeUp", self.name); 315 | LoopResult::Continue 316 | } 317 | } else { 318 | let (mutex, condvar) = &*self 319 | .thread_condvar 320 | .read() 321 | .expect("Poisoned Reactor condvar"); 322 | 323 | trace!("Reactor-{} waiting on condvar", self.name); 324 | let g = mutex.lock().expect("Poisoned Reactor condvar"); 325 | let _ = condvar 326 | .wait_timeout(g, self.thread_wait_time) 327 | .expect("Poisoned Reactor condvar"); 328 | trace!("Reactor-{} resuming", self.name); 329 | LoopResult::Continue 330 | } 331 | } 332 | 333 | /// Add an Actor's Wakeup to the run_queue. 334 | fn wake(&self, wakeup: Wakeup) { 335 | self.run_queue 336 | .write() 337 | .expect("Poisoned run_queue") 338 | .push_back(wakeup); 339 | self.thread_condvar 340 | .read() 341 | .expect("Poisoned Reactor condvar") 342 | .1 343 | .notify_one(); 344 | } 345 | 346 | /// Pop the next Wakeup. 347 | fn get_woken(&self) -> Option { 348 | self.run_queue 349 | .write() 350 | .expect("Poisoned run_queue") 351 | .pop_front() 352 | } 353 | 354 | /// Add a Task to the Reactor's wait_queue. 355 | fn wait(&self, task: Task) { 356 | self.wait_queue 357 | .write() 358 | .expect("Poisoned wait_queue") 359 | .insert(task.id.clone(), task); 360 | } 361 | 362 | /// Remove a Task from the Reactor's wait_queue. 363 | fn remove_waiting(&self, id: &Aid) -> Option { 364 | self.wait_queue 365 | .write() 366 | .expect("Poisoned wait_queue") 367 | .remove(id) 368 | } 369 | } 370 | 371 | /// Tasks represent the unit of work that an Executor-Reactor system is responsible for. 372 | struct Task { 373 | id: Aid, 374 | actor: Mutex>>, 375 | } 376 | 377 | impl Task { 378 | /// Proxy poll into the ActorStream 379 | fn poll(&mut self, waker: &Waker) -> Poll>> { 380 | let mut ctx = Context::from_waker(waker); 381 | 382 | self.actor 383 | .lock() 384 | .expect("Poisoned ActorStream") 385 | .as_mut() 386 | .poll_next(&mut ctx) 387 | } 388 | } 389 | 390 | /// Object used for generating our wakers. 391 | struct Token { 392 | id: Aid, 393 | reactor: MaximReactor, 394 | } 395 | 396 | impl ArcWake for Token { 397 | fn wake_by_ref(arc_self: &Arc) { 398 | let id = arc_self.id.clone(); 399 | 400 | let wakeup = Wakeup { 401 | id, 402 | waker: futures::task::waker(arc_self.clone()), 403 | }; 404 | 405 | (arc_self.reactor).wake(wakeup); 406 | } 407 | } 408 | 409 | /// Object representing the need to wake an Actor, to be enqueued for waking. 410 | struct Wakeup { 411 | id: Aid, 412 | waker: Waker, 413 | } 414 | 415 | #[cfg(test)] 416 | mod tests { 417 | use crate::executor::ShutdownResult; 418 | use crate::prelude::*; 419 | use crate::tests::*; 420 | use log::*; 421 | use std::future::Future; 422 | use std::pin::Pin; 423 | use std::task::Poll; 424 | use std::thread; 425 | use std::time::Duration; 426 | 427 | struct PendingNTimes { 428 | pending_count: u8, 429 | sleep_for: u64, 430 | } 431 | 432 | impl PendingNTimes { 433 | fn new(n: u8, sleep_for: u64) -> Self { 434 | Self { 435 | pending_count: n, 436 | sleep_for, 437 | } 438 | } 439 | } 440 | 441 | impl Future for PendingNTimes { 442 | type Output = ActorResult<()>; 443 | 444 | fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { 445 | match &mut self.pending_count { 446 | 0 => Poll::Ready(Ok(Status::done(()))), 447 | count => { 448 | *count -= 1; 449 | debug!("Pending, {} times left", count); 450 | let waker = cx.waker().clone(); 451 | let sleep_for = self.sleep_for; 452 | thread::spawn(move || { 453 | sleep(sleep_for); 454 | waker.wake(); 455 | }); 456 | Poll::Pending 457 | } 458 | } 459 | } 460 | } 461 | 462 | #[test] 463 | fn test_nested_futures_wakeup() { 464 | init_test_log(); 465 | 466 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 467 | let _aid = system 468 | .spawn() 469 | .with((), |_: (), c: Context, _: Message| async move { 470 | let r = PendingNTimes::new(1, 50).await; 471 | c.system.trigger_shutdown(); 472 | r 473 | }) 474 | .unwrap(); 475 | assert_ne!( 476 | system.await_shutdown(Duration::from_millis(100)), 477 | ShutdownResult::TimedOut, 478 | "Failed to trigger shutdown, actor was never woken" 479 | ); 480 | } 481 | 482 | #[test] 483 | fn test_thread_wakes_after_no_work() { 484 | init_test_log(); 485 | 486 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(1)); 487 | let aid = system.spawn().with((), simple_handler).unwrap(); 488 | // Sleep for a little longer than the condvar's default timeout 489 | sleep(125); 490 | let _ = aid.send_new(11); 491 | await_received(&aid, 2, 1000).unwrap(); 492 | system.trigger_and_await_shutdown(None); 493 | } 494 | 495 | #[test] 496 | fn test_actor_awake_phases() { 497 | init_test_log(); 498 | 499 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(1)); 500 | let aid = system 501 | .spawn() 502 | .with((), |_: (), _: Context, msg: Message| async move { 503 | if let Some(_) = msg.content_as::() { 504 | return Ok(Status::done(())); 505 | } 506 | 507 | PendingNTimes::new(1, 25).await 508 | }) 509 | .unwrap(); 510 | await_received(&aid, 1, 5).expect("Actor took too long to process Start"); 511 | let _ = aid.send_new(()).unwrap(); 512 | sleep(5); 513 | { 514 | let pending = system 515 | .executor() 516 | .reactors 517 | .iter() 518 | .nth(0) 519 | .unwrap() 520 | .wait_queue 521 | .read() 522 | .unwrap() 523 | .len(); 524 | assert_eq!(pending, 1, "Actor should be pending"); 525 | } 526 | await_received(&aid, 2, 30).expect("Actor failed to process message"); 527 | sleep(20); 528 | { 529 | let pending = system 530 | .executor() 531 | .reactors 532 | .iter() 533 | .nth(0) 534 | .unwrap() 535 | .wait_queue 536 | .read() 537 | .unwrap() 538 | .len(); 539 | assert_eq!( 540 | pending, 0, 541 | "Actor should be returned to the Executor by now" 542 | ); 543 | } 544 | { 545 | let running = system 546 | .executor() 547 | .reactors 548 | .iter() 549 | .nth(0) 550 | .unwrap() 551 | .run_queue 552 | .read() 553 | .unwrap() 554 | .len(); 555 | assert_eq!(running, 0, "Actor should not be running again"); 556 | } 557 | assert_eq!( 558 | system.executor().sleeping.len(), 559 | 2, 560 | "Actor was not returned to Executor" 561 | ); 562 | } 563 | } 564 | -------------------------------------------------------------------------------- /src/executor/thread_pool.rs: -------------------------------------------------------------------------------- 1 | use crate::executor::ShutdownResult; 2 | use log::{debug, error, trace}; 3 | use std::sync::{Arc, Condvar, Mutex}; 4 | use std::thread; 5 | use std::time::Duration; 6 | 7 | /// Maxim's core ThreadPool, custom so we can await the shutdown of the threads. 8 | #[derive(Default)] 9 | pub(crate) struct MaximThreadPool { 10 | drain: Arc, 11 | } 12 | 13 | impl MaximThreadPool { 14 | /// Primary functionality of the ThreadPool. Spawns a thread, adds a little tracking before and 15 | /// after execution of the given function. 16 | pub fn spawn(&self, name: String, f: F) -> Arc { 17 | let deed = Arc::new(ThreadDeed { 18 | name, 19 | state: Mutex::new(ThreadState::Stopped), 20 | drain: self.drain.clone(), 21 | }); 22 | self.thread(f, deed.clone()); 23 | deed 24 | } 25 | 26 | /// The function that actually spawns the thread with tracking. 27 | fn thread(&self, mut f: F, deed: Arc) 28 | where 29 | F: FnMut() + Send + 'static, 30 | { 31 | thread::Builder::new() 32 | .name(deed.name.clone()) 33 | .spawn(move || { 34 | let lease = ThreadLease::new(deed); 35 | lease.deed.drain.increment(); 36 | debug!("Thread {} has started", lease.deed.name); 37 | lease.deed.set_running(); 38 | f(); 39 | lease.deed.set_stopped(); 40 | }) 41 | .expect("Failed to spawn thread"); 42 | } 43 | 44 | /// Blocks until all threads have stopped, or the timeout has been reached. 45 | pub fn await_shutdown(&self, timeout: impl Into>) -> ShutdownResult { 46 | match timeout.into() { 47 | Some(t) => self.drain.wait_timeout(t), 48 | None => self.drain.wait(), 49 | } 50 | } 51 | } 52 | 53 | /// The record that accompanies the thread. This should have all the data associated with the 54 | /// thread. 55 | pub(crate) struct ThreadDeed { 56 | pub name: String, 57 | pub state: Mutex, 58 | drain: Arc, 59 | } 60 | 61 | impl ThreadDeed { 62 | /// Helper to set the ThreadState as Running. 63 | fn set_running(&self) { 64 | *self.state.lock().unwrap() = ThreadState::Running; 65 | } 66 | 67 | /// Helper to set the ThreadState as Stopped. 68 | fn set_stopped(&self) { 69 | *self.state.lock().unwrap() = ThreadState::Stopped; 70 | } 71 | } 72 | 73 | /// A wrapper for the ThreadDeed that checks for premature thread termination. The lifetime of the 74 | /// Lease defines the thread lifetime. As the only thing that ends a thread prematurely at this 75 | /// level is a panic, the thread dying before we can set it as Stopped indicates a panic without the 76 | /// need to join on the JoinHandle. 77 | struct ThreadLease { 78 | deed: Arc, 79 | } 80 | 81 | impl ThreadLease { 82 | pub fn new(deed: Arc) -> Self { 83 | Self { deed } 84 | } 85 | } 86 | 87 | impl Drop for ThreadLease { 88 | fn drop(&mut self) { 89 | // Don't you tell me it's poisoned!! I want to find out for myself!! 90 | let mut g = match self.deed.state.lock() { 91 | Ok(g) => g, 92 | Err(psn) => psn.into_inner(), 93 | }; 94 | // If the Lease dropped while Running, it Panicked. 95 | if let ThreadState::Running = *g { 96 | *g = ThreadState::Panicked; 97 | error!("Thread {} panicked!", self.deed.name) 98 | } else { 99 | debug!("Thread {} has stopped", self.deed.name) 100 | } 101 | // Either way, it's dead, let's decrement the thread counter. 102 | self.deed.drain.decrement(); 103 | } 104 | } 105 | 106 | /// As it's named, the state of the thread. Should be self-explanatory. 107 | pub enum ThreadState { 108 | Running, 109 | Stopped, 110 | Panicked, 111 | } 112 | 113 | /// A semaphore of sorts that unblocks when its internal counter hits 0 114 | #[derive(Default)] 115 | struct DrainAwait { 116 | /// Mutex for blocking on 117 | mutex: Mutex, 118 | /// Condvar for waiting on 119 | condvar: Condvar, 120 | } 121 | 122 | impl DrainAwait { 123 | /// Increment the drain counter 124 | pub fn increment(&self) { 125 | let mut g = self.mutex.lock().expect("DrainAwait poisoned"); 126 | let new = *g + 1; 127 | trace!("Incrementing DrainAwait to {}", new); 128 | *g += 1; 129 | } 130 | 131 | /// Decrement the drain counter, and notify condvar if it hits 0 132 | pub fn decrement(&self) { 133 | let mut guard = self.mutex.lock().expect("DrainAwait poisoned"); 134 | *guard -= 1; 135 | trace!("Decrementing DrainAwait to {}", *guard); 136 | if *guard == 0 { 137 | debug!("DrainAwait is depleted, notifying blocked threads"); 138 | self.condvar.notify_all(); 139 | } 140 | } 141 | 142 | /// Block on the condvar 143 | pub fn wait(&self) -> ShutdownResult { 144 | let mut guard = match self.mutex.lock() { 145 | Ok(g) => g, 146 | Err(_) => return ShutdownResult::Panicked, 147 | }; 148 | 149 | while *guard != 0 { 150 | guard = match self.condvar.wait(guard) { 151 | Ok(g) => g, 152 | Err(_) => return ShutdownResult::Panicked, 153 | }; 154 | } 155 | ShutdownResult::Ok 156 | } 157 | 158 | /// Block on the condvar until it times out 159 | pub fn wait_timeout(&self, timeout: Duration) -> ShutdownResult { 160 | let mut guard = match self.mutex.lock() { 161 | Ok(g) => g, 162 | Err(_) => return ShutdownResult::Panicked, 163 | }; 164 | 165 | while *guard != 0 { 166 | let (new_guard, timeout) = match self.condvar.wait_timeout(guard, timeout) { 167 | Ok(ret) => (ret.0, ret.1), 168 | Err(_) => return ShutdownResult::Panicked, 169 | }; 170 | 171 | if timeout.timed_out() { 172 | return ShutdownResult::TimedOut; 173 | } 174 | guard = new_guard; 175 | } 176 | ShutdownResult::Ok 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of a highly-scalable and ergonomic actor model for Rust 2 | //! 3 | //! [![Latest version](https://img.shields.io/crates/v/maxim.svg)](https://crates.io/crates/maxim) 4 | //! [![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/katharostech/maxim.svg)](https://isitmaintained.com/project/katharostech/maxim) 5 | //! [![License](https://img.shields.io/crates/l/maxim.svg)](https://github.com/katharostech/maxim#license) 6 | //! [![Changelog.md](https://img.shields.io/badge/Keep%20a%20Changelog-Changelog.md-blue)](https://github.com/katharostech/maxim/blob/master/CHANGELOG.md) 7 | //! 8 | //! # Maxim 9 | //! 10 | //! Maxim is a fork of the [Axiom](https://github.com/rsimmonsjr/axiom) actor framework that was made so we could use the awesome Actor framework while experimenting with our own ideas for Actor framework design. 11 | //! 12 | //! Maxim brings a highly-scalable actor model to the Rust language based on the many lessons learned over years of Actor model implementations in Akka and Erlang. Maxim is, however, not a direct re-implementation of either of the two aforementioned actor models but rather a new implementation deriving inspiration from the good parts of those projects. 13 | //! 14 | //! Current development on Maxim is focused on learning how the framework works and experimenting with our design ideas. We will be pushing `0.1.0-alpha` releases with our changes util it gets to a point that is relatively usable. The first thing we've added since the fork was a `spawn_pool` feature that allows you to create pools of actors. This and other features we add are likely to change and adapt as we test them in our projects. 15 | //! 16 | //! Other things that we are thinking about changing are: 17 | //! 18 | //! - Using [Agnostik](https://github.com/bastion-rs/agnostik) as an executor to allow Maxim to run on any executor 19 | //! - If Agnostik will not suffice for some reason we will probably switch to Tokio for the executor to avoid maintaining our own 20 | //! - Adding an optional macro for matching on message type 21 | //! - Adding an option to use either bounded or unbounded channels for actor messages ( see "Design Principals of Maxim" below for more info ) 22 | //! - This woud probably involve using [Flume](https://github.com/zesterer/flume) for backing the channels 23 | //! 24 | //! ## Getting Started 25 | //! 26 | //! _An actor model is an architectural asynchronous programming paradigm characterized by the use of actors for all processing activities._ 27 | //! 28 | //! Actors have the following characteristics: 29 | //! 30 | //! 1. An actor can be interacted with only by means of messages. 31 | //! 2. An actor processes only one message at a time. 32 | //! 3. An actor will process a message only once. 33 | //! 4. An actor can send a message to any other actor without knowledge of that actor's internals. 34 | //! 5. Actors send only immutable data as messages, though they may have mutable internal state. 35 | //! 6. Actors are location agnostic; they can be sent a message from anywhere in the cluster. 36 | //! 37 | //! Note that within the language of Rust, rule five cannot be enforced by Rust but is a best practice which is important for developers creating actors based on Maxim. In Erlang and Elixir rule five cannot be violated because of the structure of the language but this also leads to performance limitations. It's better to allow internal mutable state and encourage the good practice of not sending mutable messages. 38 | //! 39 | //! What is important to understand is that these rules combined together makes each actor operate like a micro-service in the memory space of the program using them. Since actor messages are immutable, actors can trade information safely and easily without copying large data structures. 40 | //! 41 | //! Although programming in the actor model is quite an involved process you can get started with Maxim in only a few lines of code. 42 | //! 43 | //! ```rust 44 | //! use maxim::prelude::*; 45 | //! use std::sync::Arc; 46 | //! use std::time::Duration; 47 | //! 48 | //! let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 49 | //! 50 | //! let aid = system 51 | //! .spawn() 52 | //! .with( 53 | //! 0 as usize, 54 | //! |state: usize, _context: Context, _message: Message| async move { 55 | //! Ok(Status::done(state)) 56 | //! } 57 | //! ) 58 | //! .unwrap(); 59 | //! 60 | //! aid.send(Message::new(11)).unwrap(); 61 | //! 62 | //! // It is worth noting that you probably wouldn't just unwrap in real code but deal with 63 | //! // the result as a panic in Maxim will take down a dispatcher thread and potentially 64 | //! // hang the system. 65 | //! 66 | //! // This will wrap the value `17` in a Message for you! 67 | //! aid.send_new(17).unwrap(); 68 | //! 69 | //! // We can also create and send separately using just `send`, not `send_new`. 70 | //! let message = Message::new(19); 71 | //! aid.send(message).unwrap(); 72 | //! 73 | //! // Another neat capability is to send a message after some time has elapsed. 74 | //! aid.send_after(Message::new(7), Duration::from_millis(10)).unwrap(); 75 | //! aid.send_new_after(7, Duration::from_millis(10)).unwrap(); 76 | //! ``` 77 | //! 78 | //! This code creates an actor system, fetches a builder for an actor via the `spawn()` method, spawns an actor and finally sends the actor a message. Once the actor is done processing a message it returns the new state of the actor and the status after handling this message. In this case we didnt change the state so we just return it. Creating an Maxim actor is literally that easy but there is a lot more functionality available as well. 79 | //! 80 | //! Keep in mind that if you are capturing variables from the environment you will have to wrap the `async move {}` block in another block and then move your variables into the first block. Please see the test cases for more examples of this. 81 | //! 82 | //! If you want to create an actor with a struct that is simple as well. Let's create one that handles a couple of different message types: 83 | //! 84 | //! ```rust 85 | //! use maxim::prelude::*; 86 | //! use std::sync::Arc; 87 | //! 88 | //! let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 89 | //! 90 | //! struct Data { 91 | //! value: i32, 92 | //! } 93 | //! 94 | //! impl Data { 95 | //! fn handle_bool(mut self, message: bool) -> ActorResult { 96 | //! if message { 97 | //! self.value += 1; 98 | //! } else { 99 | //! self.value -= 1; 100 | //! } 101 | //! Ok(Status::done(self)) 102 | //! } 103 | //! 104 | //! fn handle_i32(mut self, message: i32) -> ActorResult { 105 | //! self.value += message; 106 | //! Ok(Status::done(self)) 107 | //! } 108 | //! 109 | //! async fn handle(mut self, _context: Context, message: Message) -> ActorResult { 110 | //! if let Some(msg) = message.content_as::() { 111 | //! self.handle_bool(*msg) 112 | //! } else if let Some(msg) = message.content_as::() { 113 | //! self.handle_i32(*msg) 114 | //! } else { 115 | //! panic!("Failed to dispatch properly"); 116 | //! } 117 | //! } 118 | //! } 119 | //! 120 | //! let data = Data { value: 0 }; 121 | //! let aid = system.spawn().name("Fred").with(data, Data::handle).unwrap(); 122 | //! 123 | //! aid.send_new(11).unwrap(); 124 | //! aid.send_new(true).unwrap(); 125 | //! aid.send_new(false).unwrap(); 126 | //! ``` 127 | //! 128 | //! This code creates a named actor out of an arbitrary struct. Since the only requirement to make an actor is to have a function that is compliant with the [`maxim::actors::Processor`] trait, anything can be an actor. If this struct had been declared somewhere outside of your control you could use it in an actor as state by declaring your own handler function and making the calls to the 3rd party structure. 129 | //! 130 | //! _It's important to keep in mind that the starting state is moved into the actor and you will not have external access to it afterwards._ This is by design and although you could conceivably use a [`Arc`] or [`Mutex`] enclosing a structure as state, that would definitely be a bad idea as it would break the rules we laid out for actors. 131 | //! 132 | //! ## Detailed Examples 133 | //! 134 | //! - [Hello World](https://github.com/katharostech/maxim/blob/master/examples/hello_world.rs): The 135 | //! obligatory introduction to any computer system. 136 | //! - [Dining Philosophers](https://github.com/katharostech/maxim/blob/master/examples/philosophers.rs): 137 | //! An example of using Maxim to solve a classic Finite State Machine problem in computer science. 138 | //! - [Monte Carlo](https://github.com/katharostech/maxim/blob/master/examples/montecarlo.rs): An 139 | //! example of how to use Maxim for parallel computation. 140 | //! 141 | //! ## Design Principals of Maxim 142 | //! 143 | //! These are the core principals of Axiom, the project Maxim was forked from: 144 | //! 145 | //! 1. **At its core an actor is just an function that processes messages.** The simplest actor is a 146 | //! function that takes a message and simply ignores it. The benefit to the functional approach over 147 | //! the Akka model is that it allows the user to create actors easily and simply. This is the notion 148 | //! of _micro module programming_; the notion of building a complex system from the smallest 149 | //! components. Software based on the actor model can get complicated; keeping it simple at the core 150 | //! is fundamental to solid architecture. 151 | //! 2. **Actors can be a Finite State Machine (FSM).** Actors receive and process messages nominally 152 | //! in the order received. However, there are certain circumstances where an actor has to change to 153 | //! another state and process other messages, skipping certain messages to be processed later. 154 | //! 3. **When skipping messages, the messages must not move.** Akka allows the skipping of messages 155 | //! by _stashing_ the message in another data structure and then restoring this stash later. This 156 | //! process has many inherent flaws. Instead Axiom allows an actor to skip messages in its channel 157 | //! but leave them where they are, increasing performance and avoiding many problems. 158 | //! 4. **Actors use a bounded capacity channel.** In Axiom the message capacity for the actor's 159 | //! channel is bounded, resulting in greater simplicity and an emphasis on good actor design. 160 | //! 5. **Axiom should be kept as small as possible.** Axiom is the core of the actor model and 161 | //! should not be expanded to include everything possible for actors. That should be the job of 162 | //! libraries that extend Axiom. Axiom itself should be an example of _micro module programming_. 163 | //! 6. **The tests are the best place for examples.** The tests of Axiom will be extensive and well 164 | //! maintained and should be a resource for those wanting to use Axiom. They should not be a dumping 165 | //! ground for copy-paste or throwaway code. The best tests will look like architected code. 166 | //! 7. **A huge emphasis is put on crate user ergonomics.** Axiom should be easy to use. 167 | //! 168 | //! The principals that Maxim may **not** preserve are principals 4 and 6. To address those: 169 | //! 170 | //! - **Bounded capacity channel:** While it may be best to have a bounded capacity channel, we will need to do some experimentation with the design before we settle our own opinion on it and our initial reaction is that it would be good to have the user be allowed to choose. As far as complexity is concerned we will probably look into out-sourcing our channel implementation to something like [Flume](https://github.com/zesterer/flume). There has not been enough investigation made to make such statements with any certainty, though. 171 | //! - **The tests are the best place for examples:** While we agree that tests should be examples of how the code will actually be used, we are less along the lines of telling users to go look at the unit tests to find out how to use the library. We want the documentation to be rich and helpful to the users so that they don't _have_ to look at the tests to find out how to use the tool. 172 | //! 173 | //! [`maxim::actors::processor`]: https://docs.rs/maxim/latest/maxim/actors/trait.Processor.html 174 | //! [`arc`]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html 175 | //! [`mutex`]: https://doc.rust-lang.org/stable/std/sync/struct.Mutex.html 176 | //! 177 | //! ## License 178 | //! 179 | //! Copyright 2020 Maxim Contributors 180 | //! 181 | //! Licensed under the Apache License, Version 2.0 (the "License"); 182 | //! you may not use this file except in compliance with the License. 183 | //! You may obtain a copy of the License at 184 | //! 185 | //! http://www.apache.org/licenses/LICENSE-2.0 186 | //! 187 | //! Unless required by applicable law or agreed to in writing, software 188 | //! distributed under the License is distributed on an "AS IS" BASIS, 189 | //! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | //! See the License for the specific language governing permissions and 191 | //! limitations under the License. 192 | 193 | use std::any::Any; 194 | use std::error::Error; 195 | use std::fmt::{Display, Formatter}; 196 | 197 | // Re-export futures so the user doesn't need to import it. 198 | pub use futures; 199 | use prelude::*; 200 | 201 | pub mod actors; 202 | pub mod cluster; 203 | mod executor; 204 | pub mod message; 205 | pub mod system; 206 | 207 | pub mod prelude; 208 | 209 | /// A helper alias to ensure returned errors conform as needed. 210 | pub type StdError = Box; 211 | 212 | /// A type for a result from an actor's message processor. 213 | /// A Result::Err is treated as a fatal error, and the Actor will be stopped. 214 | pub type ActorResult = Result<(State, Status), StdError>; 215 | 216 | #[derive(Debug)] 217 | pub struct Panic { 218 | panic_payload: String, 219 | } 220 | 221 | impl Display for Panic { 222 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 223 | write!(f, "{}", self.panic_payload) 224 | } 225 | } 226 | 227 | impl Error for Panic {} 228 | 229 | impl From> for Panic { 230 | fn from(val: Box) -> Self { 231 | let panic_payload = match val.downcast::<&'static str>() { 232 | Ok(s) => String::from(*s), 233 | Err(val) => match val.downcast::() { 234 | Ok(s) => *s, 235 | Err(_) => String::from("Panic payload unserializable"), 236 | }, 237 | }; 238 | Self { panic_payload } 239 | } 240 | } 241 | 242 | #[cfg(test)] 243 | mod tests { 244 | use std::thread; 245 | use std::time::Duration; 246 | 247 | use log::LevelFilter; 248 | use secc::{SeccReceiver, SeccSender}; 249 | use serde::{Deserialize, Serialize}; 250 | 251 | use super::*; 252 | 253 | #[derive(Clone)] 254 | pub struct AssertCollect { 255 | tx: SeccSender<(bool, String)>, 256 | rx: SeccReceiver<(bool, String)>, 257 | } 258 | 259 | impl AssertCollect { 260 | pub fn new() -> Self { 261 | let (tx, rx) = secc::create(256, Duration::from_millis(10)); 262 | Self { tx, rx } 263 | } 264 | 265 | pub fn assert(&self, cond: bool, msg: impl Into) { 266 | let m = msg.into(); 267 | self.tx.send((cond, m.clone())).unwrap(); 268 | 269 | if !cond { 270 | panic!("{}", m) 271 | } 272 | } 273 | 274 | pub fn panic(&self, msg: impl Into) -> ! { 275 | let m = msg.into(); 276 | self.tx.send((false, m.clone())).unwrap(); 277 | panic!("{}", m) 278 | } 279 | 280 | pub fn collect(&self) { 281 | while let Ok((cond, s)) = self.rx.receive() { 282 | assert!(cond, "{}", s); 283 | } 284 | } 285 | } 286 | 287 | pub fn init_test_log() { 288 | let _ = env_logger::builder() 289 | .filter_level(LevelFilter::Warn) 290 | .is_test(true) 291 | .try_init(); 292 | } 293 | 294 | pub fn sleep(millis: u64) { 295 | thread::sleep(Duration::from_millis(millis)) 296 | } 297 | 298 | /// A function that just returns `Ok(Status::Done)` which can be used as a handler for 299 | /// a simple dummy actor. 300 | pub async fn simple_handler(_: (), _: Context, _: Message) -> ActorResult<()> { 301 | Ok(Status::done(())) 302 | } 303 | 304 | /// A utility that waits for a certain number of messages to arrive in a certain time and 305 | /// returns an `Ok<()>` when they do or an `Err` when not. 306 | pub fn await_received(aid: &Aid, count: u8, timeout_ms: u64) -> Result<(), String> { 307 | use std::time::Instant; 308 | let start = Instant::now(); 309 | let duration = Duration::from_millis(timeout_ms); 310 | while aid.received().unwrap() < count as usize { 311 | if Instant::elapsed(&start) > duration { 312 | return Err(format!( 313 | "Timed out after {}ms! Messages received: {}; Messages expected: {}", 314 | timeout_ms, 315 | aid.received().unwrap(), 316 | count 317 | )); 318 | } 319 | } 320 | Ok(()) 321 | } 322 | 323 | #[test] 324 | #[should_panic] 325 | fn test_assert_receive() { 326 | let tracker = AssertCollect::new(); 327 | let t2 = tracker.clone(); 328 | 329 | let join = thread::spawn(move || t2.panic("This is a panic")); 330 | let _ = join.join(); 331 | 332 | tracker.collect(); 333 | } 334 | 335 | /// This test shows how the simplest actor can be built and used. This actor uses a closure 336 | /// that simply returns that the message is processed without doing anything with it. 337 | #[test] 338 | fn test_simplest_actor() { 339 | init_test_log(); 340 | 341 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 342 | 343 | // We spawn the actor using a closure. Note that because of a bug in the Rust compiler 344 | // as of 2019-07-12 regarding type inference we have to specify all of the types manually 345 | // but when that bug goes away this will be even simpler. 346 | let aid = system 347 | .spawn() 348 | .with((), |_: (), _: Context, _: Message| async { 349 | Ok(Status::done(())) 350 | }) 351 | .unwrap(); 352 | 353 | // Send a message to the actor. 354 | aid.send_new(11).unwrap(); 355 | 356 | // The actor will get two messages including the Start message. 357 | await_received(&aid, 2, 1000).unwrap(); 358 | system.trigger_and_await_shutdown(None); 359 | } 360 | 361 | /// This test shows how the simplest struct-based actor can be built and used. This actor 362 | /// merely returns that the message was processed. 363 | #[test] 364 | fn test_simplest_struct_actor() { 365 | init_test_log(); 366 | 367 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 368 | 369 | // We declare a basic struct that has a handle method that does basically nothing. 370 | // Subsequently we will create that struct as a starting state when we spawn the actor 371 | // and then send the actor a message. 372 | struct Data {} 373 | 374 | impl Data { 375 | async fn handle(self, _: Context, _: Message) -> ActorResult { 376 | Ok(Status::done(self)) 377 | } 378 | } 379 | 380 | let aid = system.spawn().with(Data {}, Data::handle).unwrap(); 381 | 382 | // Send a message to the actor. 383 | aid.send_new(11).unwrap(); 384 | 385 | await_received(&aid, 2, 1000).unwrap(); 386 | system.trigger_and_await_shutdown(None); 387 | } 388 | 389 | /// This test shows how a closure based actor can be created to process different kinds of 390 | /// messages and mutate the actor's state based upon the messages passed. Note that the 391 | /// state of the actor is not available outside the actor itself. 392 | #[test] 393 | fn test_dispatching_with_closure() { 394 | init_test_log(); 395 | 396 | let tracker = AssertCollect::new(); 397 | let t = tracker.clone(); 398 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 399 | 400 | let starting_state: usize = 0 as usize; 401 | let closure = move |mut state: usize, context: Context, message: Message| { 402 | let t = t.clone(); 403 | async move { 404 | // Expected messages in the expected order. 405 | let expected: Vec = vec![11, 13, 17]; 406 | // Attempt to downcast to expected message. 407 | if let Some(_msg) = message.content_as::() { 408 | state += 1; 409 | Ok(Status::done(state)) 410 | } else if let Some(msg) = message.content_as::() { 411 | t.assert(expected[state - 1] == *msg, "Unexpected message content"); 412 | t.assert(state == context.aid.received().unwrap(), "Unexpected state"); 413 | state += 1; 414 | Ok(Status::done(state)) 415 | } else if let Some(_msg) = message.content_as::() { 416 | // Note that we put this last because it only is ever received once, we 417 | // want the most frequently received messages first. 418 | Ok(Status::done(state)) 419 | } else { 420 | t.panic("Failed to dispatch properly") 421 | } 422 | } 423 | }; 424 | 425 | let aid = system.spawn().with(starting_state, closure).unwrap(); 426 | 427 | // First message will always be the SystemMsg::Start. 428 | assert_eq!(1, aid.sent().unwrap()); 429 | 430 | // Send some messages to the actor in the order required in the test. In a real actor 431 | // its unlikely any order restriction would be needed. However this test makes sure that 432 | // the messages are processed correctly. 433 | aid.send_new(11 as i32).unwrap(); 434 | assert_eq!(2, aid.sent().unwrap()); 435 | aid.send_new(13 as i32).unwrap(); 436 | assert_eq!(3, aid.sent().unwrap()); 437 | aid.send_new(17 as i32).unwrap(); 438 | assert_eq!(4, aid.sent().unwrap()); 439 | 440 | await_received(&aid, 4, 1000).unwrap(); 441 | system.trigger_and_await_shutdown(None); 442 | tracker.collect(); 443 | } 444 | 445 | /// This test shows how a struct-based actor can be used and process different kinds of 446 | /// messages and mutate the actor's state based upon the messages passed. Note that the 447 | /// state of the actor is not available outside the actor itself. 448 | #[test] 449 | fn test_dispatching_with_struct() { 450 | init_test_log(); 451 | 452 | let tracker = AssertCollect::new(); 453 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 454 | 455 | // We create a basic struct with a handler and use that handler to dispatch to other 456 | // inherent methods in the struct. Note that we don't have to implement any traits here 457 | // and there is nothing forcing the handler to be an inherent method. 458 | struct Data { 459 | value: i32, 460 | tracker: AssertCollect, 461 | } 462 | 463 | impl Data { 464 | fn handle_bool(mut self, message: bool) -> ActorResult { 465 | if message { 466 | self.value += 1; 467 | } else { 468 | self.value -= 1; 469 | } 470 | Ok(Status::done(self)) // This assertion will fail but we still have to return. 471 | } 472 | 473 | fn handle_i32(mut self, message: i32) -> ActorResult { 474 | self.value += message; 475 | Ok(Status::done(self)) // This assertion will fail but we still have to return. 476 | } 477 | 478 | async fn handle(self, _context: Context, message: Message) -> ActorResult { 479 | if let Some(msg) = message.content_as::() { 480 | self.handle_bool(*msg) 481 | } else if let Some(msg) = message.content_as::() { 482 | self.handle_i32(*msg) 483 | } else if let Some(_msg) = message.content_as::() { 484 | // Note that we put this last because it only is ever received once, we 485 | // want the most frequently received messages first. 486 | Ok(Status::done(self)) 487 | } else { 488 | self.tracker.panic("Failed to dispatch properly") 489 | } 490 | } 491 | } 492 | 493 | let data = Data { 494 | value: 0, 495 | tracker: tracker.clone(), 496 | }; 497 | 498 | let aid = system.spawn().with(data, Data::handle).unwrap(); 499 | 500 | // Send some messages to the actor. 501 | aid.send_new(11).unwrap(); 502 | aid.send_new(true).unwrap(); 503 | aid.send_new(true).unwrap(); 504 | aid.send_new(false).unwrap(); 505 | 506 | await_received(&aid, 4, 1000).unwrap(); 507 | system.trigger_and_await_shutdown(None); 508 | tracker.collect(); 509 | } 510 | 511 | /// Tests and demonstrates the process to create a closure that captures the environment 512 | /// outside the closure in a manner sufficient to be used in a future. 513 | #[test] 514 | fn test_closure_with_move() { 515 | init_test_log(); 516 | 517 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 518 | let target_aid = system.spawn().with((), simple_handler).unwrap(); 519 | 520 | let aid_moved = target_aid.clone(); // clone for the closure 521 | let aid = system 522 | .spawn() 523 | .with((), move |_: (), _: Context, _: Message| { 524 | // Each future needs its own copy of the target aid. 525 | let tgt = aid_moved.clone(); 526 | async move { 527 | tgt.send_new(11)?; 528 | Ok(Status::done(())) 529 | } 530 | }) 531 | .unwrap(); 532 | 533 | aid.send_new(11).unwrap(); 534 | await_received(&target_aid, 2, 1000).unwrap(); 535 | system.trigger_and_await_shutdown(None); 536 | } 537 | 538 | /// Tests an example where one actor starts another actor, the actors exchange a simple 539 | /// ping-pong message and then the first actor triggers a shutdown when the pong message is 540 | /// received. Note that these actors just use simple functions to accomplish the task though 541 | /// they could have used functions on structures, closures, and even had a multiple methods 542 | /// to handle the messages. 543 | #[test] 544 | fn test_ping_pong() { 545 | /// A simple enum used as test messages. 546 | #[derive(Serialize, Deserialize)] 547 | pub enum PingPong { 548 | Ping(Aid), 549 | Pong, 550 | } 551 | 552 | async fn ping(_: (), context: Context, message: Message) -> ActorResult<()> { 553 | if let Some(msg) = message.content_as::() { 554 | match &*msg { 555 | PingPong::Pong => { 556 | context.system.trigger_shutdown(); 557 | Ok(Status::done(())) 558 | } 559 | _ => Err("Unexpected message".to_string().into()), 560 | } 561 | } else if let Some(msg) = message.content_as::() { 562 | // Start messages happen only once so we keep them last. 563 | match &*msg { 564 | SystemMsg::Start => { 565 | // Now we will spawn a new actor to handle our pong and send to it. 566 | let pong_aid = context.system.spawn().with((), pong)?; 567 | pong_aid.send_new(PingPong::Ping(context.aid.clone()))?; 568 | Ok(Status::done(())) 569 | } 570 | _ => Ok(Status::done(())), 571 | } 572 | } else { 573 | Ok(Status::done(())) 574 | } 575 | } 576 | 577 | async fn pong(_: (), _: Context, message: Message) -> ActorResult<()> { 578 | if let Some(msg) = message.content_as::() { 579 | match &*msg { 580 | PingPong::Ping(from) => { 581 | from.send_new(PingPong::Pong)?; 582 | Ok(Status::done(())) 583 | } 584 | _ => Err("Unexpected message".into()), 585 | } 586 | } else { 587 | Ok(Status::done(())) 588 | } 589 | } 590 | 591 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 592 | system.spawn().with((), ping).unwrap(); 593 | system.await_shutdown(None); 594 | } 595 | } 596 | -------------------------------------------------------------------------------- /src/message.rs: -------------------------------------------------------------------------------- 1 | //! Defines the types associated with messages sent to actors. 2 | 3 | use crate::AidError; 4 | use serde::de::DeserializeOwned; 5 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 6 | use std::any::{Any, TypeId}; 7 | use std::collections::hash_map::DefaultHasher; 8 | use std::error::Error; 9 | use std::hash::Hash; 10 | use std::hash::Hasher; 11 | use std::sync::{Arc, RwLock}; 12 | 13 | /// This defines any value safe to send across threads as an ActorMessage. 14 | pub trait ActorMessage: Send + Sync + Any { 15 | /// Gets a bincode serialized version of the message and returns it in a result or an error 16 | /// indicating what went wrong. 17 | fn to_bincode(&self) -> Result, Box> { 18 | Err(Box::new(AidError::CantConvertToBincode)) 19 | } 20 | 21 | fn from_bincode(_data: &Vec) -> Result> 22 | where 23 | Self: Sized, 24 | { 25 | Err(Box::new(AidError::CantConvertFromBincode)) 26 | } 27 | } 28 | 29 | impl dyn ActorMessage { 30 | fn downcast(self: Arc) -> Option> { 31 | if TypeId::of::() == (*self).type_id() { 32 | unsafe { 33 | let ptr = Arc::into_raw(self) as *const T; 34 | Some(Arc::from_raw(ptr)) 35 | } 36 | } else { 37 | None 38 | } 39 | } 40 | } 41 | 42 | impl ActorMessage for T 43 | where 44 | T: Serialize + DeserializeOwned + Sync + Send + Any + ?Sized, 45 | { 46 | fn to_bincode(&self) -> Result, Box> { 47 | let data = bincode::serialize(self)?; 48 | Ok(data) 49 | } 50 | 51 | fn from_bincode(data: &Vec) -> Result> { 52 | let decoded: Self = bincode::deserialize(data)?; 53 | Ok(decoded) 54 | } 55 | } 56 | 57 | /// The message content in a message. 58 | enum MessageContent { 59 | /// The message is a local message. 60 | Local(Arc), 61 | /// The message is from remote and has the given hash of a [`std::any::TypeId`] and the 62 | /// serialized content. 63 | Remote(Vec), 64 | } 65 | 66 | impl Serialize for MessageContent { 67 | fn serialize(&self, serializer: S) -> Result 68 | where 69 | S: Serializer, 70 | { 71 | match self { 72 | MessageContent::Local(v) => { 73 | let data = v 74 | .to_bincode() 75 | .map_err(|e| serde::ser::Error::custom(format!("{}", e)))?; 76 | MessageContent::Remote(data).serialize(serializer) 77 | } 78 | MessageContent::Remote(content) => serializer.serialize_bytes(content), 79 | } 80 | } 81 | } 82 | 83 | impl<'de> Deserialize<'de> for MessageContent { 84 | fn deserialize(deserializer: D) -> Result 85 | where 86 | D: Deserializer<'de>, 87 | { 88 | Ok(MessageContent::Remote(Vec::::deserialize( 89 | deserializer, 90 | )?)) 91 | } 92 | } 93 | 94 | /// Holds the data used in a message. 95 | #[derive(Serialize, Deserialize)] 96 | struct MessageData { 97 | /// The hash of the [`TypeId`] for the type used to construct the message. 98 | type_id_hash: u64, 99 | /// The content of the message in a RwLock. The lock is needed because if the message 100 | /// came from remote, it will need to be converted to a local message variant. 101 | content: RwLock, 102 | } 103 | 104 | /// A type for a message sent to an actor channel. 105 | /// 106 | /// Note that this type uses an internal [`Arc`] so there is no reason to surround it with 107 | /// another [`Arc`] to make it thread safe. 108 | #[derive(Clone, Serialize, Deserialize)] 109 | pub struct Message { 110 | data: Arc, 111 | } 112 | 113 | impl Message { 114 | /// Creates a new message from a value, transferring ownership to the message. 115 | /// 116 | /// # Examples 117 | /// ```rust 118 | /// use maxim::message::Message; 119 | /// 120 | /// let msg = Message::new(11); 121 | /// ``` 122 | pub fn new(value: T) -> Message 123 | where 124 | T: 'static + ActorMessage, 125 | { 126 | Message { 127 | data: Arc::new(MessageData { 128 | type_id_hash: Message::hash_type_id::(), 129 | content: RwLock::new(MessageContent::Local(Arc::new(value))), 130 | }), 131 | } 132 | } 133 | 134 | /// Creates a new message from an [`Arc`], transferring ownership of the Arc to the message. 135 | /// Note that this is more efficient if a user wants to send a message that is already an 136 | /// [`Arc`] so they dont create an arc inside an [`Arc`]. 137 | /// 138 | /// # Examples 139 | /// ```rust 140 | /// use maxim::message::Message; 141 | /// use std::sync::Arc; 142 | /// 143 | /// let arc = Arc::new(11); 144 | /// let msg = Message::from_arc(arc); 145 | /// ``` 146 | pub fn from_arc(value: Arc) -> Message 147 | where 148 | T: 'static + ActorMessage, 149 | { 150 | Message { 151 | data: Arc::new(MessageData { 152 | type_id_hash: Message::hash_type_id::(), 153 | content: RwLock::new(MessageContent::Local(value)), 154 | }), 155 | } 156 | } 157 | 158 | /// A helper that will return the hash of the type id for `T`. 159 | #[inline] 160 | fn hash_type_id() -> u64 { 161 | let mut hasher = DefaultHasher::new(); 162 | TypeId::of::().hash(&mut hasher); 163 | hasher.finish() 164 | } 165 | 166 | /// Get the content as an [`Arc`]. If this fails a `None` will be returned. Note that 167 | /// the user need not worry whether the message came from a local or remote source as the 168 | /// heavy lifting for that is done internally. The first successful attempt to downcast a 169 | /// remote message will result in the value being converted to a local message. 170 | /// 171 | /// # Examples 172 | /// ```rust 173 | /// use maxim::message::Message; 174 | /// use std::sync::Arc; 175 | /// 176 | /// let value = 11 as i32; 177 | /// let msg = Message::new(value); 178 | /// assert_eq!(value, *msg.content_as::().unwrap()); 179 | /// assert_eq!(None, msg.content_as::()); 180 | /// ``` 181 | pub fn content_as(&self) -> Option> 182 | where 183 | T: 'static + ActorMessage, 184 | { 185 | // To make this fail fast we will first check against the hash of the type_id that the 186 | // user wants to convert the message content to. 187 | if self.data.type_id_hash != Message::hash_type_id::() { 188 | None 189 | } else { 190 | // We first have to figure out if the content is Local or Remote because they have 191 | // vastly different implications. 192 | let read_guard = self.data.content.read().unwrap(); 193 | match &*read_guard { 194 | // If the content is Local then we just downcast the arc type. 195 | // type. This should fail fast if the type ids don't match. 196 | MessageContent::Local(content) => content.clone().downcast::(), 197 | // If the content is Remote then we will turn it into a Local. 198 | MessageContent::Remote(_) => { 199 | // To convert the message we have to drop the read lock and re-acquire a 200 | // write lock on the content. 201 | drop(read_guard); 202 | let mut write_guard = self.data.content.write().unwrap(); 203 | // Because of a potential race we will try again. 204 | match &*write_guard { 205 | // Another thread beat us to the write so we just downcast normally. 206 | MessageContent::Local(content) => content.clone().downcast::(), 207 | // This thread got the write lock and the content is still remote. 208 | MessageContent::Remote(content) => { 209 | // We deserialize the content and replace it in the message with a 210 | // new local variant. 211 | match T::from_bincode(&content) { 212 | Ok(concrete) => { 213 | let new_value: Arc = Arc::new(concrete); 214 | *write_guard = MessageContent::Local(new_value.clone()); 215 | drop(write_guard); 216 | Some(new_value) 217 | } 218 | Err(err) => { 219 | // The only reason this should happen is if the type id hash 220 | // check is somehow broken so we will want to fix it. 221 | panic!("Deserialization shouldn't have failed: {:?}", err) 222 | } 223 | } 224 | } 225 | } 226 | } 227 | } 228 | } 229 | } 230 | } 231 | 232 | #[cfg(test)] 233 | mod tests { 234 | use super::*; 235 | 236 | /// Testing helper to create an actor message from a value. 237 | fn new_actor_msg(value: T) -> Arc 238 | where 239 | T: 'static + ActorMessage, 240 | { 241 | Arc::new(value) 242 | } 243 | 244 | /// Tests the basic downcast functionality for an `ActorMessage` type which is owned by 245 | /// the `Message`. 246 | #[test] 247 | fn test_actor_message_downcast() { 248 | let value = 11 as i32; 249 | let msg = new_actor_msg(value); 250 | assert_eq!(value, *msg.clone().downcast::().unwrap()); 251 | assert_eq!(None, msg.downcast::()); 252 | } 253 | 254 | /// Tests that messages can be created with the `new` method and that they use `Local` 255 | /// content for the message. 256 | #[test] 257 | fn test_message_new() { 258 | let value = 11 as i32; 259 | let msg = Message::new(value); 260 | let read_guard = msg.data.content.read().unwrap(); 261 | match &*read_guard { 262 | MessageContent::Remote(_) => panic!("Expected a Local variant."), 263 | MessageContent::Local(content) => { 264 | assert_eq!(value, *content.clone().downcast::().unwrap()); 265 | } 266 | } 267 | } 268 | 269 | /// Tests that messages can be easily created from an `Arc` in an efficient manner without 270 | /// nested `Arc`s. 271 | #[test] 272 | fn test_message_from_arc() { 273 | let value = 11 as i32; 274 | let arc = Arc::new(value); 275 | let msg = Message::from_arc(arc.clone()); 276 | let read_guard = msg.data.content.read().unwrap(); 277 | match &*read_guard { 278 | MessageContent::Remote(_) => panic!("Expected a Local variant."), 279 | MessageContent::Local(content) => { 280 | let downcasted = content.clone().downcast::().unwrap(); 281 | assert_eq!(value, *downcasted); 282 | assert!(Arc::ptr_eq(&arc, &downcasted)); 283 | } 284 | } 285 | } 286 | 287 | /// Tests the basic downcast functionality for a `Message` type. 288 | #[test] 289 | fn test_message_downcast() { 290 | let value = 11 as i32; 291 | let msg = Message::new(value); 292 | assert_eq!(value, *msg.content_as::().unwrap()); 293 | assert_eq!(None, msg.content_as::()); 294 | } 295 | 296 | /// Tests that messages can be serialized and deserialized properly. 297 | #[test] 298 | fn test_message_serialization() { 299 | let value = 11 as i32; 300 | let msg = Message::new(value); 301 | let serialized = bincode::serialize(&msg).expect("Couldn't serialize."); 302 | let deserialized: Message = 303 | bincode::deserialize(&serialized).expect("Couldn't deserialize."); 304 | let read_guard = deserialized.data.content.read().unwrap(); 305 | match &*read_guard { 306 | MessageContent::Local(_) => panic!("Expected a Remote variant."), 307 | MessageContent::Remote(_) => { 308 | drop(read_guard); 309 | match deserialized.content_as::() { 310 | None => panic!("Could not cast content."), 311 | Some(v) => assert_eq!(value, *v), 312 | } 313 | } 314 | } 315 | } 316 | 317 | /// Tests that `Message`s with `MessageContent::Remote` values are converted to 318 | /// `MessageContent::Local` the first time that they are successfully downcasted. 319 | #[test] 320 | fn test_remote_to_local() { 321 | let value = 11 as i32; 322 | let local = Message::new(value); 323 | let serialized = bincode::serialize(&local).expect("Couldn't serialize."); 324 | let msg: Message = bincode::deserialize(&serialized).expect("Couldn't deserialize."); 325 | let hash = Message::hash_type_id::(); 326 | { 327 | // A failure to downcast should leave the message as it is. 328 | assert_eq!(None, msg.content_as::()); 329 | let read_guard = msg.data.content.read().unwrap(); 330 | assert_eq!(hash, msg.data.type_id_hash); 331 | match &*read_guard { 332 | MessageContent::Local(_) => panic!("Expected a Remote variant."), 333 | MessageContent::Remote(content) => { 334 | assert_eq!(bincode::serialize(&value).unwrap(), *content); 335 | } 336 | } 337 | } 338 | 339 | { 340 | // We will try to downcast the message to the proper type which should work and 341 | // convert the message to a local variant. 342 | assert_eq!(value, *msg.content_as::().unwrap()); 343 | 344 | // Now we test to make sure that it indeed got converted. 345 | let read_guard = msg.data.content.read().unwrap(); 346 | assert_eq!(hash, msg.data.type_id_hash); 347 | match &*read_guard { 348 | MessageContent::Remote(_) => panic!("Expected a Local variant."), 349 | MessageContent::Local(content) => { 350 | assert_eq!(value, *content.clone().downcast::().unwrap()); 351 | } 352 | } 353 | } 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::actors::Aid; 2 | pub use crate::actors::AidError; 3 | #[cfg(feature = "actor-pool")] 4 | pub use crate::actors::AidPool; 5 | pub use crate::actors::Context; 6 | #[cfg(feature = "actor-pool")] 7 | pub use crate::actors::RandomAidPool; 8 | pub use crate::actors::Status; 9 | #[cfg(feature = "actor-pool")] 10 | pub use crate::actors::SyncAidPool; 11 | pub use crate::executor::ShutdownResult; 12 | pub use crate::message::Message; 13 | pub use crate::system::ActorSystem; 14 | pub use crate::system::ActorSystemConfig; 15 | pub use crate::system::SystemError; 16 | pub use crate::system::SystemMsg; 17 | pub use crate::system::WireMessage; 18 | pub use crate::ActorResult; 19 | pub use crate::Panic; 20 | pub use crate::StdError; 21 | -------------------------------------------------------------------------------- /src/system.rs: -------------------------------------------------------------------------------- 1 | //! Implements the [`ActorSystem`] and related types of Maxim. 2 | //! 3 | //! When the [`ActorSystem`] starts up, a number of Reactors will be spawned that will iterate over 4 | //! Actor's inbound messages, processing them asynchronously. Actors will be ran as many times as 5 | //! they can over a given time slice until they are pending or have no more messages. If the Actor 6 | //! is Pending, it will be re-queued when the pending future wakes it. If the Actor has no more 7 | //! messages, it will be returned to the Executor until it has messages again. This process cycles 8 | //! until the [`ActorSystem`] is shutdown. 9 | //! 10 | //! The user should refer to test cases and examples as "how-to" guides for using Maxim. 11 | 12 | #[cfg(feature = "actor-pool")] 13 | use crate::actors::ActorPoolBuilder; 14 | use crate::actors::{Actor, ActorBuilder, ActorStream}; 15 | use crate::executor::MaximExecutor; 16 | use crate::prelude::*; 17 | use crate::system::system_actor::SystemActor; 18 | use dashmap::DashMap; 19 | use log::{debug, error, info, trace, warn}; 20 | use once_cell::sync::OnceCell; 21 | use secc::{SeccReceiver, SeccSender}; 22 | use serde::{Deserialize, Serialize}; 23 | use std::collections::{BinaryHeap, HashSet}; 24 | use std::error::Error; 25 | use std::fmt; 26 | use std::sync::atomic::{AtomicBool, Ordering}; 27 | use std::sync::{Arc, Condvar, Mutex}; 28 | use std::thread; 29 | use std::thread::JoinHandle; 30 | use std::time::{Duration, Instant}; 31 | use uuid::Uuid; 32 | 33 | mod system_actor; 34 | 35 | // Holds an ActorSystem in a std::thread_local so that the Aid deserializer and other types can 36 | // obtain a clone if needed at any time. This needs to be set by each Reactor that is processing 37 | // messages with the actors. 38 | std::thread_local! { 39 | static ACTOR_SYSTEM: OnceCell = OnceCell::new(); 40 | } 41 | 42 | /// An enum containing messages that are sent to actors by the actor system itself and are 43 | /// universal to all actors. 44 | #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] 45 | pub enum SystemMsg { 46 | /// A message that is sent by the system and guaranteed to be the first message that the 47 | /// actor receives in its lifetime. 48 | Start, 49 | 50 | /// A message that instructs an actor to shut down. The actor receiving this message should 51 | /// shut down all open file handles and any other resources and return a [`Status::Stop`] 52 | /// from the call to the message processor. Regardless of the return from the actor's 53 | /// message processor the actor will be shut down by the actor system. 54 | Stop, 55 | 56 | /// A message sent to an actor when a monitored actor is stopped and thus not able to 57 | /// process additional messages. The value is the `aid` of the actor that stopped. 58 | Stopped { aid: Aid, error: Option }, 59 | } 60 | 61 | /// A type used for sending messages to other actor systems. 62 | #[derive(Clone, Serialize, Deserialize)] 63 | pub enum WireMessage { 64 | /// A message sent as a response to another actor system connecting to this actor system. 65 | Hello { 66 | /// The `aid` for the system actor on the actor system sending the message. 67 | system_actor_aid: Aid, 68 | }, 69 | /// A container for a message from one actor on one system to an actor on another system. 70 | ActorMessage { 71 | /// The UUID of the [`Aid`] that the message is being sent to. 72 | actor_uuid: Uuid, 73 | /// The UUID of the system that the destination [`Aid`] is local to. 74 | system_uuid: Uuid, 75 | /// The message to be sent. 76 | message: Message, 77 | }, 78 | /// A container for sending a message with a specified duration delay. 79 | DelayedActorMessage { 80 | /// The duration to use to delay the message. 81 | duration: Duration, 82 | /// The UUID of the [`Aid`] that the message is being sent to. 83 | actor_uuid: Uuid, 84 | /// The UUID of the system that the destination [`Aid`] is local to. 85 | system_uuid: Uuid, 86 | /// The message to be sent. 87 | message: Message, 88 | }, 89 | } 90 | 91 | /// Configuration structure for the Maxim actor system. Note that this configuration implements 92 | /// serde serialize and deserialize to allow users to read the config from any serde supported 93 | /// means. 94 | #[derive(Clone, Debug, Serialize, Deserialize)] 95 | pub struct ActorSystemConfig { 96 | /// The default size for the channel that is created for each actor. This can be overridden on 97 | /// a per-actor basis during spawning as well. Making the default channel size bigger allows 98 | /// for more bandwidth in sending messages to actors but also takes more memory. Also the 99 | /// user should consider if their actor needs a large channel then it might need to be 100 | /// refactored or the threads size should be increased because messages aren't being processed 101 | /// fast enough. The default value for this is 32. 102 | pub message_channel_size: u16, 103 | /// Max duration to wait between attempts to send to an actor's message channel. This is used 104 | /// to poll a busy channel that is at its capacity limit. The larger this value is, the longer 105 | /// `send` will wait for capacity in the channel but the user should be aware that if the 106 | /// system is often waiting on capacity that channel may be too small or the actor may need to 107 | /// be refactored to process messages faster. The default value is 1 millisecond. 108 | pub send_timeout: Duration, 109 | /// The size of the thread pool which governs how many worker threads there are in the system. 110 | /// The number of threads should be carefully considered to have sufficient parallelism but not 111 | /// over-schedule the CPU on the target hardware. The default value is 4 * the number of logical 112 | /// CPUs. 113 | pub thread_pool_size: u16, 114 | /// The threshold at which the dispatcher thread will warn the user that the message took too 115 | /// long to process. If this warning is being logged then the user probably should reconsider 116 | /// how their message processing works and refactor big tasks into a number of smaller tasks. 117 | /// The default value is 1 millisecond. 118 | pub warn_threshold: Duration, 119 | /// This controls how long a processor will spend working on messages for an actor before 120 | /// yielding to work on other actors in the system. The dispatcher will continue to pluck 121 | /// messages off the actor's channel and process them until this time slice is exceeded. Note 122 | /// that actors themselves can exceed this in processing a single message and if so, only one 123 | /// message will be processed before yielding. The default value is 1 millisecond. 124 | pub time_slice: Duration, 125 | /// While Reactors will constantly attempt to get more work, they may run out. At that point, 126 | /// they will idle for this duration, or until they get a wakeup notification. Said 127 | /// notifications can be missed, so it's best to not set this too high. The default value is 10 128 | /// milliseconds. This implementation is backed by a [`Condvar`]. 129 | pub thread_wait_time: Duration, 130 | /// Determines whether the actor system will immediately start when it is created. The default 131 | /// value is true. 132 | pub start_on_launch: bool, 133 | } 134 | 135 | impl ActorSystemConfig { 136 | /// Return a new config with the changed `message_channel_size`. 137 | pub fn message_channel_size(mut self, value: u16) -> Self { 138 | self.message_channel_size = value; 139 | self 140 | } 141 | 142 | /// Return a new config with the changed `send_timeout`. 143 | pub fn send_timeout(mut self, value: Duration) -> Self { 144 | self.send_timeout = value; 145 | self 146 | } 147 | 148 | /// Return a new config with the changed `thread_pool_size`. 149 | pub fn thread_pool_size(mut self, value: u16) -> Self { 150 | self.thread_pool_size = value; 151 | self 152 | } 153 | 154 | /// Return a new config with the changed `warn_threshold`. 155 | pub fn warn_threshold(mut self, value: Duration) -> Self { 156 | self.warn_threshold = value; 157 | self 158 | } 159 | 160 | /// Return a new config with the changed `time_slice`. 161 | pub fn time_slice(mut self, value: Duration) -> Self { 162 | self.time_slice = value; 163 | self 164 | } 165 | 166 | /// Return a new config with the changed `thread_wait_time`. 167 | pub fn thread_wait_time(mut self, value: Duration) -> Self { 168 | self.thread_wait_time = value; 169 | self 170 | } 171 | } 172 | 173 | impl Default for ActorSystemConfig { 174 | /// Create the config with the default values. 175 | fn default() -> ActorSystemConfig { 176 | ActorSystemConfig { 177 | thread_pool_size: (num_cpus::get() * 4) as u16, 178 | warn_threshold: Duration::from_millis(1), 179 | time_slice: Duration::from_millis(1), 180 | thread_wait_time: Duration::from_millis(100), 181 | message_channel_size: 32, 182 | send_timeout: Duration::from_millis(1), 183 | start_on_launch: true, 184 | } 185 | } 186 | } 187 | 188 | /// Errors produced by the ActorSystem 189 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] 190 | pub enum SystemError { 191 | /// An error returned when an actor is already using a local name at the time the user tries 192 | /// to register that name for a new actor. The error contains the name that was attempted 193 | /// to be registered. 194 | NameAlreadyUsed(String), 195 | } 196 | 197 | impl std::fmt::Display for SystemError { 198 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 199 | write!(f, "{:?}", self) 200 | } 201 | } 202 | 203 | impl Error for SystemError {} 204 | 205 | /// Information for communicating with a remote actor system. 206 | pub struct RemoteInfo { 207 | /// The UUID of the remote system. 208 | pub system_uuid: Uuid, 209 | /// The channel to use to send messages to the remote system. 210 | pub sender: SeccSender, 211 | /// The channel to use to receive messages from the remote system. 212 | pub receiver: SeccReceiver, 213 | /// The AID to the system actor for the remote system. 214 | pub system_actor_aid: Aid, 215 | /// The handle returned by the thread processing remote messages. 216 | // FIXME (Issue #76) Add graceful shutdown for threads handling remotes. 217 | _handle: JoinHandle<()>, 218 | } 219 | 220 | /// Stores a message that will be sent to an actor with a delay. 221 | struct DelayedMessage { 222 | /// A unique identifier for a message. 223 | uuid: Uuid, 224 | /// The Aid that the message will be sent to. 225 | destination: Aid, 226 | /// The minimum instant that the message should be sent. 227 | instant: Instant, 228 | /// The message to sent. 229 | message: Message, 230 | } 231 | 232 | impl std::cmp::PartialEq for DelayedMessage { 233 | fn eq(&self, other: &Self) -> bool { 234 | self.uuid == other.uuid 235 | } 236 | } 237 | 238 | impl std::cmp::Eq for DelayedMessage {} 239 | 240 | impl std::cmp::PartialOrd for DelayedMessage { 241 | fn partial_cmp(&self, other: &DelayedMessage) -> Option { 242 | Some(other.instant.cmp(&self.instant)) // Uses an inverted sort. 243 | } 244 | } 245 | 246 | impl std::cmp::Ord for DelayedMessage { 247 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 248 | self.partial_cmp(other) 249 | .expect("DelayedMessage::partial_cmp() returned None; can't happen") 250 | } 251 | } 252 | 253 | /// Contains the inner data used by the actor system. 254 | pub(crate) struct ActorSystemData { 255 | /// Unique version 4 UUID for this actor system. 256 | pub(crate) uuid: Uuid, 257 | /// The config for the actor system which was passed to it when created. 258 | pub(crate) config: ActorSystemConfig, 259 | /// Holds handles to the pool of threads processing the work channel. 260 | threads: Mutex>>, 261 | /// The Executor responsible for managing the runtime of the Actors 262 | executor: MaximExecutor, 263 | /// Whether the ActorSystem has started or not. 264 | started: AtomicBool, 265 | /// A flag and condvar that can be used to send a signal when the system begins to shutdown. 266 | shutdown_triggered: Arc<(Mutex, Condvar)>, 267 | /// Holds the [`Actor`] objects keyed by the [`Aid`]. 268 | actors_by_aid: Arc>>, 269 | /// Holds a map of the actor ids by the UUID in the actor id. UUIDs of actor ids are assigned 270 | /// when an actor is spawned using version 4 UUIDs. 271 | aids_by_uuid: Arc>, 272 | /// Holds a map of user assigned names to actor ids set when the actors were spawned. Note 273 | /// that only actors with an assigned name will be in this map. 274 | aids_by_name: Arc>, 275 | /// Holds a map of monitors where the key is the `aid` of the actor being monitored and 276 | /// the value is a vector of `aid`s that are monitoring the actor. 277 | monitoring_by_monitored: Arc>>, 278 | /// Holds a map of information objects about links to remote actor systems. 279 | remotes: Arc>, 280 | /// Holds the messages that have been enqueued for delayed send. 281 | delayed_messages: Arc<(Mutex>, Condvar)>, 282 | } 283 | 284 | /// An actor system that contains and manages the actors spawned inside it. 285 | #[derive(Clone)] 286 | pub struct ActorSystem { 287 | /// This field means the user doesnt have to worry about declaring `Arc` all 288 | /// over the place but can just use `ActorSystem` instead. Wrapping the data also allows 289 | /// `&self` semantics on the methods which feels more ergonomic. 290 | pub(crate) data: Arc, 291 | } 292 | 293 | impl ActorSystem { 294 | /// Creates an actor system with the given config. The user should benchmark how many slots 295 | /// are needed in the work channel, the number of threads they need in the system and and so 296 | /// on in order to satisfy the requirements of the software they are creating. 297 | pub fn create(config: ActorSystemConfig) -> ActorSystem { 298 | let uuid = Uuid::new_v4(); 299 | let threads = Mutex::new(Vec::with_capacity(config.thread_pool_size as usize)); 300 | let shutdown_triggered = Arc::new((Mutex::new(false), Condvar::new())); 301 | 302 | let executor = MaximExecutor::new(shutdown_triggered.clone()); 303 | 304 | let start_on_launch = config.start_on_launch; 305 | 306 | // Creates the actor system with the thread pools and actor map initialized. 307 | let system = ActorSystem { 308 | data: Arc::new(ActorSystemData { 309 | uuid, 310 | config, 311 | threads, 312 | executor, 313 | started: AtomicBool::new(false), 314 | shutdown_triggered, 315 | actors_by_aid: Arc::new(DashMap::default()), 316 | aids_by_uuid: Arc::new(DashMap::default()), 317 | aids_by_name: Arc::new(DashMap::default()), 318 | monitoring_by_monitored: Arc::new(DashMap::default()), 319 | remotes: Arc::new(DashMap::default()), 320 | delayed_messages: Arc::new((Mutex::new(BinaryHeap::new()), Condvar::new())), 321 | }), 322 | }; 323 | 324 | // Starts the actor system if configured to do so 325 | if start_on_launch { 326 | system.start(); 327 | } 328 | 329 | system 330 | } 331 | 332 | /// Starts an unstarted ActorSystem. The function will do nothing if the ActorSystem has already been started. 333 | pub fn start(&self) { 334 | if !self 335 | .data 336 | .started 337 | .compare_and_swap(false, true, Ordering::Relaxed) 338 | { 339 | info!("ActorSystem {} has spawned", self.data.uuid); 340 | self.data.executor.init(self); 341 | 342 | // We have the thread pool in a mutex to avoid a chicken & egg situation with the actor 343 | // system not being created yet but needed by the thread. We put this code in a block to 344 | // get around rust borrow constraints without unnecessarily copying things. 345 | { 346 | let mut guard = self.data.threads.lock().unwrap(); 347 | 348 | // Start the thread that reads from the `delayed_messages` queue. 349 | // FIXME Put in ability to confirm how many of these to start. 350 | for _ in 0..1 { 351 | let thread = self.start_send_after_thread(); 352 | guard.push(thread); 353 | } 354 | } 355 | 356 | // Launch the SystemActor and give it the name "System" 357 | self.spawn() 358 | .name("System") 359 | .with(SystemActor, SystemActor::processor) 360 | .unwrap(); 361 | } 362 | } 363 | 364 | /// Starts a thread that monitors the delayed_messages and sends the messages when their 365 | /// delays have elapsed. 366 | // FIXME Add a graceful shutdown to this thread and notifications. 367 | fn start_send_after_thread(&self) -> JoinHandle<()> { 368 | let system = self.clone(); 369 | let delayed_messages = self.data.delayed_messages.clone(); 370 | thread::spawn(move || { 371 | while !*system.data.shutdown_triggered.0.lock().unwrap() { 372 | let (ref mutex, ref condvar) = &*delayed_messages; 373 | let mut data = mutex.lock().unwrap(); 374 | match data.peek() { 375 | None => { 376 | // wait to be notified something is added. 377 | let _ = condvar.wait(data).unwrap(); 378 | } 379 | Some(msg) => { 380 | let now = Instant::now(); 381 | if now >= msg.instant { 382 | trace!("Sending delayed message"); 383 | msg.destination 384 | .send(msg.message.clone()) 385 | .unwrap_or_else(|error| { 386 | warn!( 387 | "Cannot send scheduled message to {}: Error {:?}", 388 | msg.destination, error 389 | ); 390 | }); 391 | data.pop(); 392 | } else { 393 | let duration = msg.instant.duration_since(now); 394 | let _result = condvar.wait_timeout(data, duration).unwrap(); 395 | } 396 | } 397 | } 398 | } 399 | }) 400 | } 401 | 402 | /// Returns a reference to the config for this actor system. 403 | pub fn config(&self) -> &ActorSystemConfig { 404 | &self.data.config 405 | } 406 | 407 | /// Locates the sender for the remote actor system with the given Uuid. 408 | pub(crate) fn remote_sender(&self, system_uuid: &Uuid) -> Option> { 409 | self.data 410 | .remotes 411 | .get(system_uuid) 412 | .map(|info| info.sender.clone()) 413 | } 414 | 415 | /// Adds a connection to a remote actor system. When the connection is established the 416 | /// actor system will announce itself to the remote system with a [`WireMessage::Hello`]. 417 | pub fn connect( 418 | &self, 419 | sender: &SeccSender, 420 | receiver: &SeccReceiver, 421 | ) -> Uuid { 422 | // Announce ourselves to the other system and get their info. 423 | let hello = WireMessage::Hello { 424 | system_actor_aid: self.system_actor_aid(), 425 | }; 426 | sender.send(hello).unwrap(); 427 | debug!("Sending hello from {}", self.data.uuid); 428 | 429 | // FIXME (Issue #75) Make error handling in ActorSystem::connect more robust. 430 | let system_actor_aid = 431 | match receiver.receive_await_timeout(self.data.config.thread_wait_time) { 432 | Ok(message) => match message { 433 | WireMessage::Hello { system_actor_aid } => system_actor_aid, 434 | _ => panic!("Expected first message to be a Hello"), 435 | }, 436 | Err(e) => panic!("Expected to read a Hello message {:?}", e), 437 | }; 438 | 439 | // Starts a thread to read incoming wire messages and process them. 440 | let system = self.clone(); 441 | let receiver_clone = receiver.clone(); 442 | let thread_timeout = self.data.config.thread_wait_time; 443 | let sys_uuid = system_actor_aid.system_uuid(); 444 | let handle = thread::spawn(move || { 445 | system.init_current(); 446 | // FIXME (Issue #76) Add graceful shutdown for threads handling remotes including 447 | // informing remote that the system is exiting. 448 | while !*system.data.shutdown_triggered.0.lock().unwrap() { 449 | match receiver_clone.receive_await_timeout(thread_timeout) { 450 | Err(_) => (), // not an error, just loop and try again. 451 | Ok(wire_msg) => system.process_wire_message(&sys_uuid, &wire_msg), 452 | } 453 | } 454 | }); 455 | 456 | // Save the info and thread to the remotes map. 457 | let info = RemoteInfo { 458 | system_uuid: system_actor_aid.system_uuid(), 459 | sender: sender.clone(), 460 | receiver: receiver.clone(), 461 | _handle: handle, 462 | system_actor_aid, 463 | }; 464 | 465 | let uuid = info.system_uuid; 466 | self.data.remotes.insert(uuid.clone(), info); 467 | uuid 468 | } 469 | 470 | /// Disconnects this actor system from the remote actor system with the given UUID. 471 | // FIXME Connectivity management needs a lot of work and testing. 472 | pub fn disconnect(&self, system_uuid: Uuid) -> Result<(), AidError> { 473 | self.data.remotes.remove(&system_uuid); 474 | Ok(()) 475 | } 476 | 477 | /// Connects two actor systems using two channels directly. This can be used as a utility 478 | /// in testing or to link two actor systems directly within the same process. 479 | pub fn connect_with_channels(system1: &ActorSystem, system2: &ActorSystem) { 480 | let (tx1, rx1) = secc::create::(32, system1.data.config.thread_wait_time); 481 | let (tx2, rx2) = secc::create::(32, system2.data.config.thread_wait_time); 482 | 483 | // We will do this in 2 threads because one connect would block waiting on a message 484 | // from the other actor system, causing a deadlock. 485 | let system1_clone = system1.clone(); 486 | let system2_clone = system2.clone(); 487 | let h1 = thread::spawn(move || system1_clone.connect(&tx1, &rx2)); 488 | let h2 = thread::spawn(move || system2_clone.connect(&tx2, &rx1)); 489 | 490 | // Wait for the completion of the connection. 491 | h1.join().unwrap(); 492 | h2.join().unwrap(); 493 | } 494 | 495 | /// A helper function to process a wire message from another actor system. The passed uuid 496 | /// is the uuid of the remote that sent the message. 497 | // FIXME (Issue #74) Make error handling in ActorSystem::process_wire_message more robust. 498 | fn process_wire_message(&self, _uuid: &Uuid, wire_message: &WireMessage) { 499 | match wire_message { 500 | WireMessage::ActorMessage { 501 | actor_uuid, 502 | system_uuid, 503 | message, 504 | } => { 505 | if let Some(aid) = self.find_aid(&system_uuid, &actor_uuid) { 506 | aid.send(message.clone()).unwrap_or_else(|error| { 507 | warn!("Could not send wire message to {}. Error: {}", aid, error); 508 | }) 509 | } 510 | } 511 | WireMessage::DelayedActorMessage { 512 | duration, 513 | actor_uuid, 514 | system_uuid, 515 | message, 516 | } => { 517 | self.find_aid(&system_uuid, &actor_uuid) 518 | .map(|aid| self.send_after(message.clone(), aid, *duration)) 519 | .expect("Error not handled yet"); 520 | } 521 | WireMessage::Hello { system_actor_aid } => { 522 | debug!("{:?} Got Hello from {}", self.data.uuid, system_actor_aid); 523 | } 524 | } 525 | } 526 | 527 | /// Initializes this actor system to use for the current thread which is necessary if the 528 | /// user wishes to serialize and deserialize [`Aid`]s. 529 | /// 530 | /// ## Contract 531 | /// You must run this exactly once per thread where needed. 532 | pub fn init_current(&self) { 533 | ACTOR_SYSTEM.with(|actor_system| { 534 | actor_system 535 | .set(self.clone()) 536 | .expect("Unable to set ACTOR_SYSTEM."); 537 | }); 538 | } 539 | 540 | /// Fetches a clone of a reference to the actor system for the current thread. 541 | #[inline] 542 | pub fn current() -> ActorSystem { 543 | ACTOR_SYSTEM.with(|actor_system| { 544 | actor_system 545 | .get() 546 | .expect("Thread local ACTOR_SYSTEM not set! See `ActorSystem::init_current()`") 547 | .clone() 548 | }) 549 | } 550 | 551 | /// Returns the unique UUID for this actor system. 552 | #[inline] 553 | pub fn uuid(&self) -> Uuid { 554 | self.data.uuid 555 | } 556 | 557 | /// Triggers a shutdown but doesn't wait for the Reactors to stop. 558 | pub fn trigger_shutdown(&self) { 559 | let (ref mutex, ref condvar) = &*self.data.shutdown_triggered; 560 | *mutex.lock().unwrap() = true; 561 | condvar.notify_all(); 562 | } 563 | 564 | /// Awaits the Executor shutting down all Reactors. This is backed by a barrier that Reactors 565 | /// will wait on after [`ActorSystem::trigger_shutdown`] is called, blocking until all Reactors 566 | /// have stopped. 567 | pub fn await_shutdown(&self, timeout: impl Into>) -> ShutdownResult { 568 | info!("System awaiting shutdown"); 569 | 570 | let start = Instant::now(); 571 | let timeout = timeout.into(); 572 | 573 | let result = match timeout { 574 | Some(dur) => self.await_shutdown_trigger_with_timeout(dur), 575 | None => self.await_shutdown_trigger_without_timeout(), 576 | }; 577 | 578 | if let Some(r) = result { 579 | return r; 580 | } 581 | 582 | let timeout = { 583 | match timeout { 584 | Some(timeout) => { 585 | let elapsed = Instant::now().duration_since(start); 586 | if let Some(t) = timeout.checked_sub(elapsed) { 587 | Some(t) 588 | } else { 589 | return ShutdownResult::TimedOut; 590 | } 591 | } 592 | None => None, 593 | } 594 | }; 595 | 596 | // Wait for the executor to finish shutting down 597 | self.data.executor.await_shutdown(timeout) 598 | } 599 | 600 | fn await_shutdown_trigger_with_timeout(&self, mut dur: Duration) -> Option { 601 | let (ref mutex, ref condvar) = &*self.data.shutdown_triggered; 602 | let mut guard = mutex.lock().unwrap(); 603 | while !*guard { 604 | let started = Instant::now(); 605 | let (new_guard, timeout) = match condvar.wait_timeout(guard, dur) { 606 | Ok(ret) => ret, 607 | Err(_) => return Some(ShutdownResult::Panicked), 608 | }; 609 | 610 | if timeout.timed_out() { 611 | return Some(ShutdownResult::TimedOut); 612 | } 613 | 614 | guard = new_guard; 615 | dur -= started.elapsed(); 616 | } 617 | None 618 | } 619 | 620 | fn await_shutdown_trigger_without_timeout(&self) -> Option { 621 | let (ref mutex, ref condvar) = &*self.data.shutdown_triggered; 622 | let mut guard = mutex.lock().unwrap(); 623 | while !*guard { 624 | guard = match condvar.wait(guard) { 625 | Ok(ret) => ret, 626 | Err(_) => return Some(ShutdownResult::Panicked), 627 | }; 628 | } 629 | None 630 | } 631 | 632 | /// Triggers a shutdown of the system and returns only when all Reactors have shutdown. 633 | pub fn trigger_and_await_shutdown( 634 | &self, 635 | timeout: impl Into>, 636 | ) -> ShutdownResult { 637 | self.trigger_shutdown(); 638 | self.await_shutdown(timeout) 639 | } 640 | 641 | // An internal helper to register an actor in the actor system. 642 | pub(crate) fn register_actor( 643 | &self, 644 | actor: Arc, 645 | stream: ActorStream, 646 | ) -> Result { 647 | let aids_by_name = &self.data.aids_by_name; 648 | let actors_by_aid = &self.data.actors_by_aid; 649 | let aids_by_uuid = &self.data.aids_by_uuid; 650 | let aid = actor.context.aid.clone(); 651 | if let Some(name_string) = &aid.name() { 652 | if aids_by_name.contains_key(name_string) { 653 | return Err(SystemError::NameAlreadyUsed(name_string.clone())); 654 | } else { 655 | aids_by_name.insert(name_string.clone(), aid.clone()); 656 | } 657 | } 658 | actors_by_aid.insert(aid.clone(), actor); 659 | aids_by_uuid.insert(aid.uuid(), aid.clone()); 660 | self.data.executor.register_actor(stream); 661 | aid.send(Message::new(SystemMsg::Start)).unwrap(); // Actor was just made 662 | Ok(aid) 663 | } 664 | 665 | /// Creates a single use builder for this actor system that allows a user to build actors 666 | /// using a chained syntax while optionally providing configuration parameters if desired. 667 | /// 668 | /// # Examples 669 | /// ``` 670 | /// use maxim::prelude::*; 671 | /// 672 | /// let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 673 | /// 674 | /// async fn handler(mut count: usize, _: Context, _: Message) -> ActorResult { 675 | /// count += 1; 676 | /// Ok(Status::done(count)) 677 | /// } 678 | /// 679 | /// let state = 0 as usize; 680 | /// 681 | /// let aid1 = system.spawn().with(state, handler).unwrap(); 682 | /// let aid2 = system.spawn().name("Foo").with(state, handler).unwrap(); 683 | /// let aid3 = system.spawn().channel_size(10).with(state, handler).unwrap(); 684 | /// ``` 685 | pub fn spawn(&self) -> ActorBuilder { 686 | ActorBuilder { 687 | system: self.clone(), 688 | name: None, 689 | channel_size: None, 690 | } 691 | } 692 | 693 | /// Create a one use actor pool builder that can be used to create a pool of actors. 694 | /// 695 | /// The state and the handler function will be cloned and the `count` of actors will be spawned 696 | /// and their [`Aid`]s added to an [`AidPool`]. With the [`AidPool`] you can send messages to 697 | /// the pool to have one of the actors in the pool handle each message. The method that is used 698 | /// to select an actor to handle the message depends on the [`AidPool`] implmentation. The most 699 | /// common actor pool implementation is [`RandomAidPool`]. 700 | /// 701 | /// # Examples 702 | /// ``` 703 | /// use maxim::prelude::*; 704 | /// 705 | /// let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 706 | /// 707 | /// async fn handler(mut count: usize, _: Context, _: Message) -> ActorResult { 708 | /// // Do something 709 | /// Ok(Status::done(count)) 710 | /// } 711 | /// 712 | /// let state = 0 as usize; 713 | /// 714 | /// let mut aid_pool: RandomAidPool = system.spawn_pool(10).with(state, handler).unwrap(); 715 | /// // Send a message to one of the actors in the pool 716 | /// aid_pool.send_new(()).unwrap(); 717 | /// ``` 718 | #[cfg(feature = "actor-pool")] 719 | pub fn spawn_pool(&self, count: usize) -> ActorPoolBuilder { 720 | ActorPoolBuilder::new( 721 | ActorBuilder { 722 | system: self.clone(), 723 | name: None, 724 | channel_size: None, 725 | }, 726 | count, 727 | ) 728 | } 729 | 730 | /// Schedules the `aid` for work. Note that this is the only time that we have to use the 731 | /// lookup table. This function gets called when an actor goes from 0 receivable messages to 732 | /// 1 receivable message. If the actor has more receivable messages then this will not be 733 | /// needed to be called because the dispatcher threads will handle the process of resending 734 | /// the actor to the work channel. 735 | // TODO Put tests verifying the resend on multiple messages. 736 | pub(crate) fn schedule(&self, aid: Aid) { 737 | let actors_by_aid = &self.data.actors_by_aid; 738 | if actors_by_aid.contains_key(&aid) { 739 | self.data.executor.wake(aid); 740 | } else { 741 | // The actor was removed from the map so ignore the problem and just log 742 | // a warning. 743 | warn!( 744 | "Attempted to schedule actor with aid {:?} on system with node_id {:?} but 745 | the actor does not exist.", 746 | aid, 747 | self.data.uuid.to_string(), 748 | ); 749 | } 750 | } 751 | 752 | /// Stops an actor by shutting down its channels and removing it from the actors list and 753 | /// telling the [`Aid`] to not allow messages to be sent to the actor since the receiving 754 | /// side of the actor is gone. 755 | /// 756 | /// This is something that should rarely be called from the outside as it is much better to 757 | /// send the actor a [`SystemMsg::Stop`] message and allow it to stop gracefully. 758 | pub fn stop_actor(&self, aid: &Aid) { 759 | self.internal_stop_actor(aid, None); 760 | } 761 | 762 | /// Internal implementation of stop_actor, so we have the ability to send an error along with 763 | /// the notification of stop. 764 | pub(crate) fn internal_stop_actor(&self, aid: &Aid, error: impl Into>) { 765 | { 766 | let actors_by_aid = &self.data.actors_by_aid; 767 | let aids_by_uuid = &self.data.aids_by_uuid; 768 | let aids_by_name = &self.data.aids_by_name; 769 | actors_by_aid.remove(aid); 770 | aids_by_uuid.remove(&aid.uuid()); 771 | if let Some(name_string) = aid.name() { 772 | aids_by_name.remove(&name_string); 773 | } 774 | aid.stop().unwrap(); 775 | } 776 | 777 | // Notify all of the actors monitoring the actor that is stopped and remove the 778 | // actor from the map of monitors. 779 | if let Some((_, monitoring)) = self.data.monitoring_by_monitored.remove(&aid) { 780 | let error = error.into().map(|e| format!("{}", e)); 781 | for m_aid in monitoring { 782 | let value = SystemMsg::Stopped { 783 | aid: aid.clone(), 784 | error: error.clone(), 785 | }; 786 | m_aid.send(Message::new(value)).unwrap_or_else(|error| { 787 | error!( 788 | "Could not send 'Stopped' to monitoring actor {}: Error: {:?}", 789 | m_aid, error 790 | ); 791 | }); 792 | } 793 | } 794 | } 795 | 796 | /// Checks to see if the actor with the given [`Aid`] is alive within this actor system. 797 | pub fn is_actor_alive(&self, aid: &Aid) -> bool { 798 | let actors_by_aid = &self.data.actors_by_aid; 799 | actors_by_aid.contains_key(aid) 800 | } 801 | 802 | /// Look up an [`Aid`] by the unique UUID of the actor and either returns the located 803 | /// [`Aid`] in a [`Option::Some`] or [`Option::None`] if not found. 804 | pub fn find_aid_by_uuid(&self, uuid: &Uuid) -> Option { 805 | let aids_by_uuid = &self.data.aids_by_uuid; 806 | aids_by_uuid.get(uuid).map(|aid| aid.clone()) 807 | } 808 | 809 | /// Look up an [`Aid`] by the user assigned name of the actor and either returns the 810 | /// located [`Aid`] in a [`Option::Some`] or [`Option::None`] if not found. 811 | pub fn find_aid_by_name(&self, name: &str) -> Option { 812 | let aids_by_name = &self.data.aids_by_name; 813 | aids_by_name.get(&name.to_string()).map(|aid| aid.clone()) 814 | } 815 | 816 | /// A helper that finds an [`Aid`] on this system from the `system_uuid` and `actor_uuid` 817 | /// passed to the function. If the `system_uuid` doesn't match this system then a `None` will 818 | /// be returned. Also if the `system_uuid` matches but the actor is not found a `None` will 819 | /// be returned. 820 | fn find_aid(&self, system_uuid: &Uuid, actor_uuid: &Uuid) -> Option { 821 | if self.uuid() == *system_uuid { 822 | self.find_aid_by_uuid(&actor_uuid) 823 | } else { 824 | None 825 | } 826 | } 827 | 828 | /// Returns the [`Aid`] to the "System" actor for this actor system. 829 | pub fn system_actor_aid(&self) -> Aid { 830 | self.find_aid_by_name(&"System").unwrap() 831 | } 832 | 833 | /// Adds a monitor so that `monitoring` will be informed if `monitored` stops. 834 | pub fn monitor(&self, monitoring: &Aid, monitored: &Aid) { 835 | let mut monitoring_by_monitored = self 836 | .data 837 | .monitoring_by_monitored 838 | .get_raw_mut_from_key(&monitored); 839 | let monitoring_vec = monitoring_by_monitored 840 | .entry(monitored.clone()) 841 | .or_insert(HashSet::new()); 842 | monitoring_vec.insert(monitoring.clone()); 843 | } 844 | 845 | /// Asynchronously send a message to the system actors on all connected actor systems. 846 | // FIXME (Issue #72) Add try_send ability. 847 | pub fn send_to_system_actors(&self, message: Message) { 848 | let remotes = &*self.data.remotes; 849 | trace!("Sending message to Remote System Actors"); 850 | for remote in remotes.iter() { 851 | let aid = &remote.value().system_actor_aid; 852 | aid.send(message.clone()).unwrap_or_else(|error| { 853 | error!("Could not send to system actor {}. Error: {}", aid, error) 854 | }); 855 | } 856 | } 857 | 858 | /// Schedules a `message` to be sent to the `destination` [`Aid`] after a `delay`. Note 859 | /// That this method makes a best attempt at sending the message on time but the message may 860 | /// not be sent on exactly the delay passed. However, the message will never be sent before 861 | /// the given delay. 862 | pub(crate) fn send_after(&self, message: Message, destination: Aid, delay: Duration) { 863 | let instant = Instant::now().checked_add(delay).unwrap(); 864 | let entry = DelayedMessage { 865 | uuid: Uuid::new_v4(), 866 | destination, 867 | instant, 868 | message, 869 | }; 870 | let (ref mutex, ref condvar) = &*self.data.delayed_messages; 871 | let mut data = mutex.lock().unwrap(); 872 | data.push(entry); 873 | condvar.notify_all(); 874 | } 875 | 876 | #[cfg(test)] 877 | pub(crate) fn executor(&self) -> &MaximExecutor { 878 | &self.data.executor 879 | } 880 | } 881 | 882 | impl fmt::Debug for ActorSystem { 883 | fn fmt(&self, formatter: &'_ mut fmt::Formatter) -> fmt::Result { 884 | write!( 885 | formatter, 886 | "ActorSystem{{uuid: {}, config: {:?}}}", 887 | self.data.uuid.to_string(), 888 | self.data.config, 889 | ) 890 | } 891 | } 892 | 893 | #[cfg(test)] 894 | mod tests { 895 | use super::*; 896 | use crate::system::system_actor::SystemActorMessage; 897 | use crate::tests::*; 898 | use futures::future; 899 | use std::thread; 900 | 901 | // A helper to start two actor systems and connect them. 902 | fn start_and_connect_two_systems() -> (ActorSystem, ActorSystem) { 903 | let system1 = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 904 | let system2 = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 905 | ActorSystem::connect_with_channels(&system1, &system2); 906 | (system1, system2) 907 | } 908 | 909 | /// Helper to wait for 2 actor systems to shutdown or panic if they don't do so within 910 | /// 2000 milliseconds. 911 | fn await_two_system_shutdown(system1: ActorSystem, system2: ActorSystem) { 912 | let h1 = thread::spawn(move || { 913 | system1.await_shutdown(None); 914 | }); 915 | 916 | let h2 = thread::spawn(move || { 917 | system2.await_shutdown(None); 918 | }); 919 | 920 | h1.join().unwrap(); 921 | h2.join().unwrap(); 922 | } 923 | 924 | /// Test that verifies that the actor system shutdown mechanisms that wait for a specific 925 | /// timeout work properly. 926 | #[test] 927 | fn test_shutdown_await_timeout() { 928 | use std::time::Duration; 929 | 930 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 931 | system 932 | .spawn() 933 | .with((), |_state: (), context: Context, _: Message| { 934 | async move { 935 | // Block for enough time so we can test timeout twice 936 | sleep(100); 937 | context.system.trigger_shutdown(); 938 | Ok(Status::done(())) 939 | } 940 | }) 941 | .unwrap(); 942 | 943 | // Expecting to timeout 944 | assert_eq!( 945 | system.await_shutdown(Duration::from_millis(10)), 946 | ShutdownResult::TimedOut 947 | ); 948 | 949 | // Expecting to NOT timeout 950 | assert_eq!( 951 | system.await_shutdown(Duration::from_millis(200)), 952 | ShutdownResult::Ok 953 | ); 954 | 955 | // Validate that if the system is already shutdown the method doesn't hang. 956 | // FIXME Design a means that this cannot ever hang the test. 957 | system.await_shutdown(None); 958 | } 959 | 960 | /// This test verifies that an actor can be found by its uuid. 961 | #[test] 962 | fn test_find_by_uuid() { 963 | init_test_log(); 964 | 965 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 966 | let aid = system.spawn().with((), simple_handler).unwrap(); 967 | aid.send_new(11).unwrap(); 968 | await_received(&aid, 2, 1000).unwrap(); 969 | let found = system.find_aid_by_uuid(&aid.uuid()).unwrap(); 970 | assert!(Aid::ptr_eq(&aid, &found)); 971 | 972 | assert_eq!(None, system.find_aid_by_uuid(&Uuid::new_v4())); 973 | 974 | system.trigger_and_await_shutdown(None); 975 | } 976 | 977 | /// This test verifies that an actor can be found by its name if it has one. 978 | #[test] 979 | fn test_find_by_name() { 980 | init_test_log(); 981 | 982 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 983 | let aid = system.spawn().name("A").with((), simple_handler).unwrap(); 984 | aid.send_new(11).unwrap(); 985 | await_received(&aid, 2, 1000).unwrap(); 986 | let found = system.find_aid_by_name(&aid.name().unwrap()).unwrap(); 987 | assert!(Aid::ptr_eq(&aid, &found)); 988 | 989 | assert_eq!(None, system.find_aid_by_name("B")); 990 | 991 | system.trigger_and_await_shutdown(None); 992 | } 993 | 994 | /// This tests the find_aid function that takes a system uuid and an actor uuid. 995 | #[test] 996 | fn test_find_aid() { 997 | init_test_log(); 998 | 999 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1000 | let aid = system.spawn().name("A").with((), simple_handler).unwrap(); 1001 | await_received(&aid, 1, 1000).unwrap(); 1002 | let found = system.find_aid(&aid.system_uuid(), &aid.uuid()).unwrap(); 1003 | assert!(Aid::ptr_eq(&aid, &found)); 1004 | 1005 | assert_eq!(None, system.find_aid(&aid.system_uuid(), &Uuid::new_v4())); 1006 | assert_eq!(None, system.find_aid(&Uuid::new_v4(), &aid.uuid())); 1007 | 1008 | system.trigger_and_await_shutdown(None); 1009 | } 1010 | 1011 | /// Tests that actors that are stopped are removed from all relevant lookup maps and 1012 | /// are reported as not being alive. 1013 | #[test] 1014 | fn test_stop_actor() { 1015 | init_test_log(); 1016 | 1017 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1018 | let aid = system.spawn().name("A").with((), simple_handler).unwrap(); 1019 | aid.send_new(11).unwrap(); 1020 | await_received(&aid, 2, 1000).unwrap(); 1021 | 1022 | // Now we stop the actor. 1023 | system.stop_actor(&aid); 1024 | assert_eq!(false, system.is_actor_alive(&aid)); 1025 | 1026 | // Verify the actor is NOT in the maps. 1027 | let sys_clone = system.clone(); 1028 | let actors_by_aid = &sys_clone.data.actors_by_aid; 1029 | assert_eq!(false, actors_by_aid.contains_key(&aid)); 1030 | let aids_by_uuid = &sys_clone.data.aids_by_uuid; 1031 | assert_eq!(false, aids_by_uuid.contains_key(&aid.uuid())); 1032 | assert_eq!(None, system.find_aid_by_name("A")); 1033 | assert_eq!(None, system.find_aid_by_uuid(&aid.uuid())); 1034 | 1035 | system.trigger_and_await_shutdown(None); 1036 | } 1037 | 1038 | /// This test verifies that the system can send a message after a particular delay. 1039 | // FIXME need separate test for remotes. 1040 | #[test] 1041 | fn test_send_after() { 1042 | init_test_log(); 1043 | 1044 | info!("Preparing test"); 1045 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1046 | let aid = system.spawn().name("A").with((), simple_handler).unwrap(); 1047 | await_received(&aid, 1, 1000).unwrap(); 1048 | info!("Test prepared, sending delayed message"); 1049 | 1050 | system.send_after(Message::new(11), aid.clone(), Duration::from_millis(10)); 1051 | info!("Sleeping for initial check"); 1052 | sleep(5); 1053 | assert_eq!(1, aid.received().unwrap()); 1054 | info!("Sleeping till we're 100% sure we should have the message"); 1055 | sleep(10); 1056 | assert_eq!(2, aid.received().unwrap()); 1057 | 1058 | system.trigger_and_await_shutdown(None); 1059 | } 1060 | 1061 | /// Tests that if we execute two send_after calls, one for a longer duration than the 1062 | /// second, that the message will be sent for the second one before the first one enqueued 1063 | /// and that the second one will still arrive properly. 1064 | #[test] 1065 | fn test_send_after_before_current() { 1066 | init_test_log(); 1067 | 1068 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1069 | 1070 | let aid1 = system.spawn().name("A").with((), simple_handler).unwrap(); 1071 | await_received(&aid1, 1, 1000).unwrap(); 1072 | let aid2 = system.spawn().name("B").with((), simple_handler).unwrap(); 1073 | await_received(&aid2, 1, 1000).unwrap(); 1074 | 1075 | aid1.send_after(Message::new(11), Duration::from_millis(50)) 1076 | .unwrap(); 1077 | 1078 | aid2.send_after(Message::new(11), Duration::from_millis(10)) 1079 | .unwrap(); 1080 | 1081 | assert_eq!(1, aid1.received().unwrap()); 1082 | assert_eq!(1, aid2.received().unwrap()); 1083 | 1084 | // We overshoot the timing on the asserts because when the tests are run the CPU is 1085 | // busy and the timing can be tricky. 1086 | sleep(15); 1087 | assert_eq!(1, aid1.received().unwrap()); 1088 | assert_eq!(2, aid2.received().unwrap()); 1089 | 1090 | sleep(50); 1091 | assert_eq!(2, aid1.received().unwrap()); 1092 | assert_eq!(2, aid2.received().unwrap()); 1093 | 1094 | system.trigger_and_await_shutdown(None); 1095 | } 1096 | 1097 | /// This test verifies that the system does not panic if we schedule to an actor that does 1098 | /// not exist in the lookup map. This can happen if a message is sent to an actor after the 1099 | /// actor is stopped but before the system notifies the [`Aid`] that the actor has been 1100 | /// stopped. 1101 | #[test] 1102 | fn test_actor_not_in_map() { 1103 | init_test_log(); 1104 | 1105 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1106 | let aid = system.spawn().with((), simple_handler).unwrap(); 1107 | await_received(&aid, 1, 1000).unwrap(); // Now it is started for sure. 1108 | 1109 | // We force remove the actor from the system without calling stop so now it cannot 1110 | // be scheduled. 1111 | let sys_clone = system.clone(); 1112 | let actors_by_aid = &sys_clone.data.actors_by_aid; 1113 | actors_by_aid.remove(&aid); 1114 | 1115 | // Send a message to the actor which should not schedule it but write out a warning. 1116 | aid.send_new(11).unwrap(); 1117 | 1118 | system.trigger_and_await_shutdown(None); 1119 | } 1120 | 1121 | /// Tests connection between two different actor systems using channels. 1122 | #[test] 1123 | fn test_connect_with_channels() { 1124 | let system1 = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1125 | let system2 = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1126 | ActorSystem::connect_with_channels(&system1, &system2); 1127 | { 1128 | system1 1129 | .data 1130 | .remotes 1131 | .get(&system2.data.uuid) 1132 | .expect("Unable to find connection with system 2 in system 1"); 1133 | } 1134 | { 1135 | system2 1136 | .data 1137 | .remotes 1138 | .get(&system1.data.uuid) 1139 | .expect("Unable to find connection with system 1 in system 2"); 1140 | } 1141 | } 1142 | 1143 | // Tests that monitors work in the actor system and send a message to monitoring actors 1144 | // when monitored actors stop. 1145 | #[test] 1146 | fn test_monitors() { 1147 | init_test_log(); 1148 | 1149 | let tracker = AssertCollect::new(); 1150 | async fn monitor_handler( 1151 | state: (Aid, AssertCollect), 1152 | _: Context, 1153 | message: Message, 1154 | ) -> ActorResult<(Aid, AssertCollect)> { 1155 | if let Some(msg) = message.content_as::() { 1156 | match &*msg { 1157 | SystemMsg::Stopped { aid, error } => { 1158 | state 1159 | .1 1160 | .assert(Aid::ptr_eq(&state.0, aid), "Pointers are not equal!"); 1161 | state.1.assert(error.is_none(), "Actor was errored!"); 1162 | Ok(Status::done(state)) 1163 | } 1164 | SystemMsg::Start => Ok(Status::done(state)), 1165 | _ => state.1.panic("Received some other message!"), 1166 | } 1167 | } else { 1168 | state.1.panic("Received some other message!") 1169 | } 1170 | } 1171 | 1172 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1173 | let monitored = system.spawn().with((), simple_handler).unwrap(); 1174 | let not_monitoring = system.spawn().with((), simple_handler).unwrap(); 1175 | let monitoring1 = system 1176 | .spawn() 1177 | .with((monitored.clone(), tracker.clone()), monitor_handler) 1178 | .unwrap(); 1179 | let monitoring2 = system 1180 | .spawn() 1181 | .with((monitored.clone(), tracker.clone()), monitor_handler) 1182 | .unwrap(); 1183 | system.monitor(&monitoring1, &monitored); 1184 | system.monitor(&monitoring2, &monitored); 1185 | 1186 | { 1187 | // Validate the monitors are there in a block to release mutex afterwards. 1188 | let monitoring_by_monitored = &system.data.monitoring_by_monitored; 1189 | let m_set = monitoring_by_monitored.get(&monitored).unwrap(); 1190 | assert!(m_set.contains(&monitoring1)); 1191 | assert!(m_set.contains(&monitoring2)); 1192 | } 1193 | 1194 | // Stop the actor and it should be out of the monitors map. 1195 | system.stop_actor(&monitored); 1196 | await_received(&monitoring1, 2, 1000).unwrap(); 1197 | await_received(&monitoring2, 2, 1000).unwrap(); 1198 | await_received(¬_monitoring, 1, 1000).unwrap(); 1199 | 1200 | system.trigger_and_await_shutdown(None); 1201 | tracker.collect(); 1202 | } 1203 | 1204 | #[test] 1205 | fn test_monitor_gets_panics_errors() { 1206 | init_test_log(); 1207 | 1208 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1209 | let tracker = AssertCollect::new(); 1210 | let t = tracker.clone(); 1211 | let aid = system 1212 | .spawn() 1213 | .with((), |_: (), _: Context, msg: Message| { 1214 | if let Some(_) = msg.content_as::() { 1215 | debug!("Not panicking this time"); 1216 | return future::ok(Status::done(())); 1217 | } 1218 | 1219 | debug!("About to panic"); 1220 | panic!("I panicked") 1221 | }) 1222 | .unwrap(); 1223 | let monitor = system 1224 | .spawn() 1225 | .with(aid.clone(), move |state: Aid, _: Context, msg: Message| { 1226 | if let Some(msg) = msg.content_as::() { 1227 | match &*msg { 1228 | SystemMsg::Stopped { aid, error } => { 1229 | t.assert(*aid == state, "Aid is not expected Aid"); 1230 | t.assert(error.is_some(), "Expected error"); 1231 | t.assert( 1232 | error.as_ref().unwrap() == "I panicked", 1233 | "Error message does not match", 1234 | ); 1235 | future::ok(Status::stop(state)) 1236 | } 1237 | SystemMsg::Start => future::ok(Status::done(state)), 1238 | _ => t.panic("Unexpected message received!"), 1239 | } 1240 | } else { 1241 | t.panic("Unexpected message received!") 1242 | } 1243 | }) 1244 | .unwrap(); 1245 | system.monitor(&monitor, &aid); 1246 | aid.send_new(()).unwrap(); 1247 | await_received(&monitor, 2, 1000).unwrap(); 1248 | system.trigger_and_await_shutdown(Duration::from_millis(1000)); 1249 | tracker.collect(); 1250 | } 1251 | 1252 | /// This test verifies that the concept of named actors works properly. When a user wants 1253 | /// to declare a named actor they cannot register the same name twice. When the actor using 1254 | /// the name currently stops the name should be removed from the registered names and be 1255 | /// available again. 1256 | #[test] 1257 | fn test_named_actor_restrictions() { 1258 | init_test_log(); 1259 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1260 | 1261 | let aid1 = system.spawn().name("A").with((), simple_handler).unwrap(); 1262 | await_received(&aid1, 1, 1000).unwrap(); 1263 | 1264 | let aid2 = system.spawn().name("B").with((), simple_handler).unwrap(); 1265 | await_received(&aid2, 1, 1000).unwrap(); 1266 | 1267 | // Spawn an actor that attempts to overwrite "A" in the names and make sure the 1268 | // attempt returns an error to be handled. 1269 | let result = system.spawn().name("A").with((), simple_handler); 1270 | assert_eq!(Err(SystemError::NameAlreadyUsed("A".to_string())), result); 1271 | 1272 | // Verify that the same actor has "A" name and is still up. 1273 | let found1 = system.find_aid_by_name("A").unwrap(); 1274 | assert_eq!(true, system.is_actor_alive(&aid1)); 1275 | assert!(Aid::ptr_eq(&aid1, &found1)); 1276 | 1277 | // Stop "B" and verify that the ActorSystem's maps are cleaned up. 1278 | system.stop_actor(&aid2); 1279 | assert_eq!(None, system.find_aid_by_name("B")); 1280 | assert_eq!(None, system.find_aid_by_uuid(&aid2.uuid())); 1281 | 1282 | // Now we should be able to crate a new actor with the name "B". 1283 | let aid3 = system.spawn().name("B").with((), simple_handler).unwrap(); 1284 | await_received(&aid3, 1, 1000).unwrap(); 1285 | let found2 = system.find_aid_by_name("B").unwrap(); 1286 | assert!(Aid::ptr_eq(&aid3, &found2)); 1287 | 1288 | system.trigger_and_await_shutdown(None); 1289 | } 1290 | 1291 | /// Tests that remote actors can send and receive messages between each other. 1292 | #[test] 1293 | fn test_remote_actors() { 1294 | // In this test our messages are just structs. 1295 | #[derive(Serialize, Deserialize, Debug)] 1296 | struct Request { 1297 | reply_to: Aid, 1298 | } 1299 | 1300 | #[derive(Serialize, Deserialize, Debug)] 1301 | struct Reply {} 1302 | 1303 | init_test_log(); 1304 | let tracker = AssertCollect::new(); 1305 | let t = tracker.clone(); 1306 | let (system1, system2) = start_and_connect_two_systems(); 1307 | 1308 | system1.init_current(); 1309 | let aid = system1 1310 | .spawn() 1311 | .with((), move |_: (), context: Context, message: Message| { 1312 | let t = t.clone(); 1313 | async move { 1314 | if let Some(msg) = message.content_as::() { 1315 | msg.reply_to.send_new(Reply {}).unwrap(); 1316 | context.system.trigger_shutdown(); 1317 | Ok(Status::stop(())) 1318 | } else if let Some(_) = message.content_as::() { 1319 | Ok(Status::done(())) 1320 | } else { 1321 | t.panic("Unexpected message received!") 1322 | } 1323 | } 1324 | }) 1325 | .unwrap(); 1326 | await_received(&aid, 1, 1000).unwrap(); 1327 | 1328 | let t = tracker.clone(); 1329 | let serialized = bincode::serialize(&aid).unwrap(); 1330 | system2 1331 | .spawn() 1332 | .with((), move |_: (), context: Context, message: Message| { 1333 | if let Some(_) = message.content_as::() { 1334 | debug!("Received reply, shutting down"); 1335 | context.system.trigger_shutdown(); 1336 | future::ok(Status::stop(())) 1337 | } else if let Some(msg) = message.content_as::() { 1338 | match &*msg { 1339 | SystemMsg::Start => { 1340 | debug!("Starting request actor"); 1341 | let target_aid: Aid = bincode::deserialize(&serialized).unwrap(); 1342 | target_aid 1343 | .send_new(Request { 1344 | reply_to: context.aid.clone(), 1345 | }) 1346 | .unwrap(); 1347 | future::ok(Status::done(())) 1348 | } 1349 | _ => future::ok(Status::done(())), 1350 | } 1351 | } else { 1352 | t.panic("Unexpected message received!") 1353 | } 1354 | }) 1355 | .unwrap(); 1356 | 1357 | await_two_system_shutdown(system1, system2); 1358 | tracker.collect(); 1359 | } 1360 | 1361 | /// Tests the ability to find an aid on a remote system by name using a `SystemActor`. This 1362 | /// also serves as a test for cross system actor communication as well as testing broadcast 1363 | /// to multiple system actors in the cluster. 1364 | #[test] 1365 | fn test_system_actor_find_by_name() { 1366 | init_test_log(); 1367 | let tracker = AssertCollect::new(); 1368 | let t = tracker.clone(); 1369 | let (system1, system2) = start_and_connect_two_systems(); 1370 | 1371 | let aid1 = system1 1372 | .spawn() 1373 | .name("A") 1374 | .with((), |_: (), context: Context, message: Message| async move { 1375 | if let Some(_) = message.content_as::() { 1376 | context.system.trigger_shutdown(); 1377 | Ok(Status::stop(())) 1378 | } else { 1379 | Ok(Status::done(())) 1380 | } 1381 | }) 1382 | .unwrap(); 1383 | await_received(&aid1, 1, 1000).unwrap(); 1384 | 1385 | system2 1386 | .spawn() 1387 | .with((), move |_: (), context: Context, message: Message| { 1388 | // We have to do this so each async block future gets its own copy. 1389 | let aid1 = aid1.clone(); 1390 | let t = t.clone(); 1391 | async move { 1392 | if let Some(msg) = message.content_as::() { 1393 | match &*msg { 1394 | SystemActorMessage::FindByNameResult { aid: found, .. } => { 1395 | debug!("FindByNameResult received"); 1396 | if let Some(target) = found { 1397 | t.assert( 1398 | target.uuid() == aid1.uuid(), 1399 | "Target is not expected Actor", 1400 | ); 1401 | target.send_new(true).unwrap(); 1402 | context.system.trigger_shutdown(); 1403 | Ok(Status::done(())) 1404 | } else { 1405 | t.panic("Didn't find AID.") 1406 | } 1407 | } 1408 | _ => t.panic("Unexpected message received!"), 1409 | } 1410 | } else if let Some(msg) = message.content_as::() { 1411 | debug!("Actor started, attempting to send FindByName request"); 1412 | if let SystemMsg::Start = &*msg { 1413 | context.system.send_to_system_actors(Message::new( 1414 | SystemActorMessage::FindByName { 1415 | reply_to: context.aid.clone(), 1416 | name: "A".to_string(), 1417 | }, 1418 | )); 1419 | Ok(Status::done(())) 1420 | } else { 1421 | t.panic("Unexpected message received!") 1422 | } 1423 | } else { 1424 | t.panic("Unexpected message received!") 1425 | } 1426 | } 1427 | }) 1428 | .unwrap(); 1429 | 1430 | await_two_system_shutdown(system1, system2); 1431 | tracker.collect(); 1432 | } 1433 | 1434 | /// Tests the ability create an [`AidPool`] and send messages to the pool. 1435 | #[test] 1436 | #[cfg(feature = "actor-pool")] 1437 | fn test_spawn_pool() { 1438 | let tracker = AssertCollect::new(); 1439 | let system = ActorSystem::create(ActorSystemConfig::default().thread_pool_size(2)); 1440 | 1441 | async fn handler(_: (), _: Context, _: Message) -> ActorResult<()> { 1442 | Ok(Status::done(())) 1443 | } 1444 | 1445 | // Create an actor pool 1446 | let mut aid_pool: RandomAidPool = system 1447 | .spawn_pool(3) 1448 | .name("handler") 1449 | .channel_size(100) 1450 | .with((), handler) 1451 | .unwrap(); 1452 | 1453 | // Send a bunch of messages to the pool 1454 | for _ in 0..=100 { 1455 | aid_pool.send_new(0).unwrap(); 1456 | } 1457 | 1458 | // Sleep to make sure we get the messages 1459 | sleep(10); 1460 | 1461 | // Convert the pool to a `Vec` of `Aid`s 1462 | let aids: Vec = aid_pool.into(); 1463 | 1464 | // Make sure each aid in the pool has received at least one message 1465 | for aid in aids { 1466 | assert!(aid.received().unwrap() > 1); 1467 | } 1468 | 1469 | system.trigger_and_await_shutdown(None); 1470 | tracker.collect(); 1471 | } 1472 | } 1473 | -------------------------------------------------------------------------------- /src/system/system_actor.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use log::{debug, error}; 3 | use serde::{Deserialize, Serialize}; 4 | use uuid::Uuid; 5 | 6 | /// The system actor is a unique actor on the system registered with the name "System". 7 | /// This actor provides core functionality that other actors will utilize. 8 | pub(crate) struct SystemActor; 9 | 10 | impl SystemActor { 11 | /// The processor function for the system actor. 12 | pub(crate) async fn processor(self, context: Context, message: Message) -> ActorResult { 13 | // Handle the SystemActorMessage. 14 | if let Some(msg) = message.content_as::() { 15 | // Someone requested that this system actor find an actor by name. 16 | if let SystemActorMessage::FindByName { reply_to, name } = &*msg { 17 | debug!("Attempting to locate Actor by name: {}", name); 18 | let found = context.system.find_aid_by_name(&name); 19 | let reply = Message::new(SystemActorMessage::FindByNameResult { 20 | system_uuid: context.system.uuid(), 21 | name: name.clone(), 22 | aid: found, 23 | }); 24 | // Note that you can't just unwrap or you could panic the dispatcher thread if 25 | // there is a problem sending the reply. In this case, the error is logged but the 26 | // actor moves on. 27 | reply_to.send(reply).unwrap_or_else(|error| { 28 | error!( 29 | "Could not send reply to FindByName to actor {}. Error: {:?}", 30 | reply_to, error 31 | ) 32 | }); 33 | } 34 | Ok(Status::done(self)) 35 | // Do nothing special if we get a SystemMsg. 36 | } else if message.content_as::().is_some() { 37 | Ok(Status::done(self)) 38 | // Log an error if we get an unexpected message kind, but continue processing as normal. 39 | } else { 40 | error!("Unhandled message received."); 41 | Ok(Status::done(self)) 42 | } 43 | } 44 | } 45 | 46 | /// Messages that are sent to and received from the System Actor.Aid 47 | #[derive(Serialize, Deserialize, Debug)] 48 | pub(crate) enum SystemActorMessage { 49 | /// Finds an actor by name. 50 | FindByName { reply_to: Aid, name: String }, 51 | 52 | /// A message sent as a reply to a [`SystemActorMessage::FindByName`] request. 53 | FindByNameResult { 54 | /// The UUID of the system that is responding. 55 | system_uuid: Uuid, 56 | /// The name that was searched for. 57 | name: String, 58 | /// The Aid in a [`Some`] if found or [`None`] if not. 59 | aid: Option, 60 | }, 61 | } 62 | --------------------------------------------------------------------------------