├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── aktors.iml └── src ├── actor.rs ├── lib.rs └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 5 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 6 | Cargo.lock 7 | 8 | .idea/ 9 | .vscode/ 10 | aktors.iml 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aktors" 3 | version = "0.2.7" 4 | edition = "2018" 5 | 6 | description = "Driver for derive-aktor" 7 | license = "MIT" 8 | 9 | authors = ["insanitybit ", "d0nut "] 10 | 11 | [dependencies] 12 | async-trait = "0.1.24" 13 | 14 | [dependencies.tokio] 15 | version = "0.2.13" 16 | features = ["sync", "rt-core", "full"] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Colin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rust actor library with a bit of inspiration from Akka/Pykka 2 | 3 | Built with the 'fibers' crate. 4 | 5 | # Example 6 | 7 | # Actors 8 | 9 | We first declare our MyActor struct like any other. We take a 'handle' so that we can spawn 10 | child actors. (Note that the id is entirely unnecessary, an ActorRef will have its own unique identifier.) 11 | 12 | ```rust 13 | struct MyActor 14 | where H: Send + Spawn + Clone + 'static 15 | { 16 | handle: H, 17 | id: String, 18 | } 19 | 20 | impl MyActor 21 | where H: Send + Spawn + Clone + 'static 22 | { 23 | pub fn new(h: H) -> MyActor { 24 | MyActor { 25 | handle: h, 26 | id: Uuid::new_v4().to_string(), 27 | } 28 | } 29 | } 30 | ``` 31 | 32 | We then impl Actor for MyActor. We get a message (Box) and we downcast it to the type we expect. 33 | 34 | We then create a child actor and send it its own message (our message + 1). 35 | 36 | This will loop (concurrently, yielding after each on_message call) forever, counting eternally. 37 | 38 | ```rust 39 | impl Actor for MyActor 40 | where H: Send + Spawn + Clone + 'static 41 | { 42 | fn on_message(&mut self, msg: Box) { 43 | if let Some(number) = msg.downcast_ref::() { 44 | if number % 1000 == 0 { 45 | println!("{} got {}", self.id, number); 46 | } 47 | 48 | // if *number == 0 {panic!("zero!")}; 49 | let new_actor = Box::new(MyActor::new(self.handle.clone())) as Box; 50 | let actor_ref = actor_of(self.handle.clone(), new_actor); 51 | actor_ref.sender.send(Box::new(number + 1)); 52 | drop(actor_ref); 53 | } else { 54 | panic!("downcast error"); 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | To actually execute the actor we need to create an Execution context, and use the actor_of function. 61 | 62 | ```rust 63 | let system = ThreadPoolExecutor::with_thread_count(2).unwrap(); 64 | 65 | let actor = MyActor::new(system.handle()); 66 | let mut actor_ref = actor_of(system.handle(), Box::new(actor)); 67 | actor_ref.send(Box::new(0 as u64)); 68 | drop(actor_ref); 69 | 70 | let _ = system.run(); 71 | ``` 72 | 73 | # Supervisors 74 | 75 | Using the above MyActor we create a ChildSpec, which will return a MyActor. The supervisor uses 76 | this function to generate new actors and replace dead ones. 77 | 78 | We then create the predefined Supervisor, and pass it to actor_of. 79 | 80 | We can then send messages to the supervisor, using the SuperVisorMessage structure. We provide 81 | the name we gave to the child, which the supervisor uses internally to route messages. 82 | 83 | (Currently supervisors do not catch panics - this will change) 84 | 85 | ```rust 86 | let system = ThreadPoolExecutor::with_thread_count(2).unwrap(); 87 | let handle = system.handle(); 88 | 89 | let child_spec = ChildSpec::new("worker child".to_owned(), 90 | move |handle| Box::new(MyActor::new(handle)) as Box, 91 | Restart::Temporary, 92 | Shutdown::Eventually, 93 | ActorKind::Worker); 94 | 95 | let mut supervisor_ref = 96 | actor_of(handle.clone(), 97 | Box::new(Supervisor::new(handle.clone(), vec![child_spec])) as Box); 98 | 99 | supervisor_ref.send(Box::new(SupervisorMessage { 100 | id: "worker child".to_owned(), 101 | msg: Box::new(1000000 as u64), 102 | })); 103 | 104 | 105 | drop(supervisor_ref); 106 | 107 | let _ = system.run(); 108 | ``` 109 | -------------------------------------------------------------------------------- /aktors.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/actor.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use std::fmt::Debug; 3 | 4 | use tokio::sync::mpsc::{channel, Receiver, Sender, error::TryRecvError}; 5 | 6 | use std::sync::Arc; 7 | use std::sync::atomic::{AtomicUsize, Ordering}; 8 | use std::time::Duration; 9 | 10 | #[async_trait] 11 | pub trait Actor { 12 | async fn route_message(&mut self, message: M); 13 | fn get_actor_name(&self) -> &str; 14 | fn close(&mut self); 15 | } 16 | 17 | pub struct Router 18 | where A: Actor, 19 | { 20 | actor_impl: Option, 21 | receiver: Receiver, 22 | inner_rc: Arc, 23 | queue_len: Arc, 24 | } 25 | 26 | impl Router 27 | where A: Actor, 28 | { 29 | pub fn new( 30 | actor_impl: A, 31 | receiver: Receiver, 32 | inner_rc: Arc, 33 | queue_len: Arc, 34 | ) -> Self { 35 | Self { 36 | actor_impl: Some(actor_impl), 37 | receiver, 38 | inner_rc, 39 | queue_len, 40 | } 41 | } 42 | } 43 | 44 | pub async fn route_wrapper(mut router: Router) 45 | where A: Actor, 46 | { 47 | let mut empty_tries = 0; 48 | 49 | loop { 50 | let msg = tokio::time::timeout( 51 | Duration::from_millis(empty_tries + 210), 52 | router.receiver.recv() 53 | ).await; 54 | 55 | match msg { 56 | Ok(Some(msg)) => { 57 | empty_tries = 0; 58 | 59 | router.queue_len.clone().fetch_sub(1, std::sync::atomic::Ordering::SeqCst); 60 | 61 | router.actor_impl.as_mut().expect("route_message actor_impl was None").route_message(msg).await; 62 | 63 | let inner_rc = router.inner_rc.load(Ordering::SeqCst); 64 | let queue_len = router.queue_len.load(Ordering::SeqCst); 65 | 66 | if queue_len > 0 { 67 | continue 68 | } 69 | if inner_rc <= 1 { 70 | if let Some(actor_impl) = router.actor_impl.as_mut() { 71 | actor_impl.close(); 72 | } 73 | router.receiver.close(); 74 | router.actor_impl = None; 75 | break; 76 | } 77 | } 78 | // Queue was empty for timeout duration 79 | Err(_) => { 80 | if empty_tries > 90 { 81 | empty_tries = 0; 82 | } 83 | empty_tries += 1; 84 | 85 | let inner_rc = router.inner_rc.load(Ordering::SeqCst); 86 | let queue_len = router.queue_len.load(Ordering::SeqCst); 87 | 88 | if queue_len > 0 { 89 | continue 90 | } 91 | 92 | if inner_rc <= 1 { 93 | if let Some(ref mut actor_impl) = router.actor_impl.as_mut() { 94 | actor_impl.close(); 95 | } 96 | router.receiver.close(); 97 | router.actor_impl = None; 98 | break; 99 | } 100 | } 101 | // Disconnected 102 | Ok(None) => { 103 | empty_tries = 0; 104 | let inner_rc = router.inner_rc.load(Ordering::SeqCst); 105 | let queue_len = router.queue_len.load(Ordering::SeqCst); 106 | 107 | if queue_len > 0{ 108 | continue 109 | } 110 | 111 | if inner_rc <= 1 { 112 | if let Some(ref mut actor_impl) = router.actor_impl.as_mut() { 113 | actor_impl.close(); 114 | } 115 | router.receiver.close(); 116 | router.actor_impl = None; 117 | break; 118 | } 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod tests; 2 | pub mod actor; 3 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanitybit/aktors/4bbfbd4da5bbc39526c832bd6cbac565f335fe1b/src/tests.rs --------------------------------------------------------------------------------