From 8d7cbfa0baee2e6c0855003b2c14ac3e81892bbb Mon Sep 17 00:00:00 2001 From: realaravinth Date: Mon, 15 Mar 2021 22:14:47 +0530 Subject: [PATCH] GC for managing MCaptcha actor instances --- CHANGELOG.md | 4 ++ examples/simple.rs | 2 +- src/lib.rs | 2 +- src/master.rs | 92 +++++++++++++++++++++++++++++++++++++++++++--- src/mcaptcha.rs | 27 ++++++++++++-- src/system.rs | 8 ++-- 6 files changed, 120 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1299ecf..2ea9dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,7 @@ - `serde::{Serialize, Deserialize}` impls (shouldn't break anything) - `MCaptcha` throws error when duration is 0 - `Visitor` is changed to `AddVisitor` +- `Master` packs a garbage collector to stop and get rid of inactive + `MCaptcha` actors +- `Master` constructor accepts a parameter to configure GC(see previous + point) period diff --git a/examples/simple.rs b/examples/simple.rs index a386a73..07553a2 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -25,7 +25,7 @@ async fn main() -> std::io::Result<()> { // start master actor. Master actor is responsible for managing MCaptcha actors // each mCaptcha system should have only one master - let master = Master::new().start(); + let master = Master::new(60).start(); // Create system. System encapsulates master and cache and provides useful abstraction // each mCaptcha system should have only one system diff --git a/src/lib.rs b/src/lib.rs index f642720..d70173c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ //! //! // start master actor. Master actor is responsible for managing MCaptcha actors //! // each mCaptcha system should have only one master -//! let master = Master::new().start(); +//! let master = Master::new(5).start(); //! //! // Create system. System encapsulates master and cache and provides useful abstraction //! // each mCaptcha system should have only one system diff --git a/src/master.rs b/src/master.rs index 0a04de3..777e690 100644 --- a/src/master.rs +++ b/src/master.rs @@ -17,7 +17,9 @@ */ //! [Master] actor module that manages [MCaptcha] actors use std::collections::BTreeMap; +use std::time::Duration; +use actix::clock::delay_for; use actix::dev::*; use derive_builder::Builder; @@ -29,30 +31,53 @@ use crate::mcaptcha::MCaptcha; /// so a "master" actor is needed to manage them all #[derive(Clone)] pub struct Master { - sites: BTreeMap>, + sites: BTreeMap, Addr)>, + gc: u64, } impl Master { /// add [MCaptcha] actor to [Master] pub fn add_site(&mut self, details: AddSite) { - self.sites.insert(details.id, details.addr.to_owned()); + self.sites + .insert(details.id, (None, details.addr.to_owned())); } /// create new master - pub fn new() -> Self { + /// accepts a `u64` to configure garbage collection period + pub fn new(gc: u64) -> Self { Master { sites: BTreeMap::new(), + gc, } } /// get [MCaptcha] actor from [Master] - pub fn get_site<'a, 'b>(&'a self, id: &'b str) -> Option<&'a Addr> { - self.sites.get(id) + pub fn get_site<'a, 'b>(&'a mut self, id: &'b str) -> Option> { + let mut r = None; + if let Some((read_val, addr)) = self.sites.get_mut(id) { + r = Some(addr.clone()); + *read_val = Some(()); + }; + r + } + + /// remvoes [MCaptcha] actor from [Master] + pub fn rm_site(&mut self, id: &str) { + self.sites.remove(id); } } impl Actor for Master { type Context = Context; + + fn started(&mut self, ctx: &mut Self::Context) { + let addr = ctx.address(); + let task = async move { + addr.send(CleanUp).await.unwrap(); + } + .into_actor(self); + ctx.spawn(task); + } } /// Message to get an [MCaptcha] actor from master @@ -73,6 +98,53 @@ impl Handler for Master { } } +/// Message to clean up master of [MCaptcha] actors with zero visitor count +#[derive(Message)] +#[rtype(result = "()")] +pub struct CleanUp; + +impl Handler for Master { + type Result = (); + + fn handle(&mut self, _: CleanUp, ctx: &mut Self::Context) -> Self::Result { + let sites = self.sites.clone(); + let gc = self.gc; + let master = ctx.address(); + println!("init cleanup up"); + let task = async move { + for (id, (new, addr)) in sites.iter() { + use crate::mcaptcha::{GetCurrentVisitorCount, Stop}; + let visitor_count = addr.send(GetCurrentVisitorCount).await.unwrap(); + println!("{}", visitor_count); + if visitor_count == 0 && new.is_some() { + addr.send(Stop).await.unwrap(); + master.send(RemoveSite(id.to_owned())).await.unwrap(); + println!("cleaned up"); + } + } + + let duration = Duration::new(gc, 0); + delay_for(duration).await; + master.send(CleanUp).await.unwrap(); + } + .into_actor(self); + ctx.spawn(task); + } +} + +/// Message to delete [MCaptcha] actor +#[derive(Message)] +#[rtype(result = "()")] +pub struct RemoveSite(pub String); + +impl Handler for Master { + type Result = (); + + fn handle(&mut self, m: RemoveSite, _ctx: &mut Self::Context) -> Self::Result { + self.rm_site(&m.0); + } +} + /// Message to add an [MCaptcha] actor to [Master] #[derive(Message, Builder)] #[rtype(result = "()")] @@ -96,7 +168,7 @@ mod tests { #[actix_rt::test] async fn master_actor_works() { - let addr = Master::new().start(); + let addr = Master::new(1).start(); let id = "yo"; let mcaptcha = get_counter().start(); @@ -105,6 +177,7 @@ mod tests { .addr(mcaptcha.clone()) .build() .unwrap(); + addr.send(msg).await.unwrap(); let mcaptcha_addr = addr.send(GetSite(id.into())).await.unwrap(); @@ -112,5 +185,12 @@ mod tests { let addr_doesnt_exist = addr.send(GetSite("a".into())).await.unwrap(); assert!(addr_doesnt_exist.is_none()); + + let timer_expire = Duration::new(DURATION, 0); + delay_for(timer_expire).await; + delay_for(timer_expire).await; + + let mcaptcha_addr = addr.send(GetSite(id.into())).await.unwrap(); + assert_eq!(mcaptcha_addr, None); } } diff --git a/src/mcaptcha.rs b/src/mcaptcha.rs index 79e930c..377fd36 100644 --- a/src/mcaptcha.rs +++ b/src/mcaptcha.rs @@ -74,6 +74,7 @@ use std::time::Duration; +use actix::clock::delay_for; use actix::dev::*; use serde::{Deserialize, Serialize}; @@ -194,6 +195,7 @@ pub struct AddVisitor; /// Struct representing the return datatime of /// [AddVisitor] message. Contains MCaptcha lifetime /// and difficulty factor +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct AddVisitorResult { pub duration: u64, pub difficulty_factor: u32, @@ -212,8 +214,6 @@ impl Handler for MCaptcha { type Result = MessageResult; fn handle(&mut self, _: AddVisitor, ctx: &mut Self::Context) -> Self::Result { - use actix::clock::delay_for; - let addr = ctx.address(); let duration: Duration = Duration::new(self.duration.clone(), 0); @@ -242,6 +242,19 @@ impl Handler for MCaptcha { } } +/// Message to stop [MCaptcha] +#[derive(Message)] +#[rtype(result = "()")] +pub struct Stop; + +impl Handler for MCaptcha { + type Result = (); + + fn handle(&mut self, _: Stop, ctx: &mut Self::Context) -> Self::Result { + ctx.stop() + } +} + #[cfg(test)] pub mod tests { use super::*; @@ -251,7 +264,7 @@ pub mod tests { // (visitor count, level) pub const LEVEL_1: (u32, u32) = (50, 50); pub const LEVEL_2: (u32, u32) = (500, 500); - pub const DURATION: u64 = 10; + pub const DURATION: u64 = 5; type MyActor = Addr; @@ -357,4 +370,12 @@ pub mod tests { assert_eq!(count, 4); } + + #[actix_rt::test] + #[should_panic] + async fn stop_works() { + let addr: MyActor = get_counter().start(); + addr.send(Stop).await.unwrap(); + addr.send(AddVisitor).await.unwrap(); + } } diff --git a/src/system.rs b/src/system.rs index b58da70..55344b7 100644 --- a/src/system.rs +++ b/src/system.rs @@ -92,8 +92,8 @@ mod tests { const MCAPTCHA_NAME: &str = "batsense.net"; - async fn boostrap_system() -> System { - let master = Master::new().start(); + async fn boostrap_system(gc: u64) -> System { + let master = Master::new(gc).start(); let mcaptcha = get_counter().start(); let pow = get_config(); @@ -123,14 +123,14 @@ mod tests { #[actix_rt::test] async fn get_pow_works() { - let actors = boostrap_system().await; + let actors = boostrap_system(10).await; let pow = actors.get_pow(MCAPTCHA_NAME.into()).await.unwrap(); assert_eq!(pow.difficulty_factor, LEVEL_1.0); } #[actix_rt::test] async fn verify_pow_works() { - let actors = boostrap_system().await; + let actors = boostrap_system(10).await; let work_req = actors.get_pow(MCAPTCHA_NAME.into()).await.unwrap(); let config = get_config();