GC for managing MCaptcha actor instances

This commit is contained in:
Aravinth Manivannan 2021-03-15 22:14:47 +05:30
parent a4929922bf
commit 8d7cbfa0ba
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
6 changed files with 120 additions and 15 deletions

View file

@ -3,3 +3,7 @@
- `serde::{Serialize, Deserialize}` impls (shouldn't break anything) - `serde::{Serialize, Deserialize}` impls (shouldn't break anything)
- `MCaptcha` throws error when duration is 0 - `MCaptcha` throws error when duration is 0
- `Visitor` is changed to `AddVisitor` - `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

View file

@ -25,7 +25,7 @@ async fn main() -> std::io::Result<()> {
// start master actor. Master actor is responsible for managing MCaptcha actors // start master actor. Master actor is responsible for managing MCaptcha actors
// each mCaptcha system should have only one master // 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 // Create system. System encapsulates master and cache and provides useful abstraction
// each mCaptcha system should have only one system // each mCaptcha system should have only one system

View file

@ -71,7 +71,7 @@
//! //!
//! // start master actor. Master actor is responsible for managing MCaptcha actors //! // start master actor. Master actor is responsible for managing MCaptcha actors
//! // each mCaptcha system should have only one master //! // 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 //! // Create system. System encapsulates master and cache and provides useful abstraction
//! // each mCaptcha system should have only one system //! // each mCaptcha system should have only one system

View file

@ -17,7 +17,9 @@
*/ */
//! [Master] actor module that manages [MCaptcha] actors //! [Master] actor module that manages [MCaptcha] actors
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::time::Duration;
use actix::clock::delay_for;
use actix::dev::*; use actix::dev::*;
use derive_builder::Builder; use derive_builder::Builder;
@ -29,30 +31,53 @@ use crate::mcaptcha::MCaptcha;
/// so a "master" actor is needed to manage them all /// so a "master" actor is needed to manage them all
#[derive(Clone)] #[derive(Clone)]
pub struct Master { pub struct Master {
sites: BTreeMap<String, Addr<MCaptcha>>, sites: BTreeMap<String, (Option<()>, Addr<MCaptcha>)>,
gc: u64,
} }
impl Master { impl Master {
/// add [MCaptcha] actor to [Master] /// add [MCaptcha] actor to [Master]
pub fn add_site(&mut self, details: AddSite) { 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 /// create new master
pub fn new() -> Self { /// accepts a `u64` to configure garbage collection period
pub fn new(gc: u64) -> Self {
Master { Master {
sites: BTreeMap::new(), sites: BTreeMap::new(),
gc,
} }
} }
/// get [MCaptcha] actor from [Master] /// get [MCaptcha] actor from [Master]
pub fn get_site<'a, 'b>(&'a self, id: &'b str) -> Option<&'a Addr<MCaptcha>> { pub fn get_site<'a, 'b>(&'a mut self, id: &'b str) -> Option<Addr<MCaptcha>> {
self.sites.get(id) 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 { impl Actor for Master {
type Context = Context<Self>; type Context = Context<Self>;
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 /// Message to get an [MCaptcha] actor from master
@ -73,6 +98,53 @@ impl Handler<GetSite> for Master {
} }
} }
/// Message to clean up master of [MCaptcha] actors with zero visitor count
#[derive(Message)]
#[rtype(result = "()")]
pub struct CleanUp;
impl Handler<CleanUp> 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<RemoveSite> 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] /// Message to add an [MCaptcha] actor to [Master]
#[derive(Message, Builder)] #[derive(Message, Builder)]
#[rtype(result = "()")] #[rtype(result = "()")]
@ -96,7 +168,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn master_actor_works() { async fn master_actor_works() {
let addr = Master::new().start(); let addr = Master::new(1).start();
let id = "yo"; let id = "yo";
let mcaptcha = get_counter().start(); let mcaptcha = get_counter().start();
@ -105,6 +177,7 @@ mod tests {
.addr(mcaptcha.clone()) .addr(mcaptcha.clone())
.build() .build()
.unwrap(); .unwrap();
addr.send(msg).await.unwrap(); addr.send(msg).await.unwrap();
let mcaptcha_addr = addr.send(GetSite(id.into())).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(); let addr_doesnt_exist = addr.send(GetSite("a".into())).await.unwrap();
assert!(addr_doesnt_exist.is_none()); 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);
} }
} }

View file

@ -74,6 +74,7 @@
use std::time::Duration; use std::time::Duration;
use actix::clock::delay_for;
use actix::dev::*; use actix::dev::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -194,6 +195,7 @@ pub struct AddVisitor;
/// Struct representing the return datatime of /// Struct representing the return datatime of
/// [AddVisitor] message. Contains MCaptcha lifetime /// [AddVisitor] message. Contains MCaptcha lifetime
/// and difficulty factor /// and difficulty factor
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AddVisitorResult { pub struct AddVisitorResult {
pub duration: u64, pub duration: u64,
pub difficulty_factor: u32, pub difficulty_factor: u32,
@ -212,8 +214,6 @@ impl Handler<AddVisitor> for MCaptcha {
type Result = MessageResult<AddVisitor>; type Result = MessageResult<AddVisitor>;
fn handle(&mut self, _: AddVisitor, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _: AddVisitor, ctx: &mut Self::Context) -> Self::Result {
use actix::clock::delay_for;
let addr = ctx.address(); let addr = ctx.address();
let duration: Duration = Duration::new(self.duration.clone(), 0); let duration: Duration = Duration::new(self.duration.clone(), 0);
@ -242,6 +242,19 @@ impl Handler<GetCurrentVisitorCount> for MCaptcha {
} }
} }
/// Message to stop [MCaptcha]
#[derive(Message)]
#[rtype(result = "()")]
pub struct Stop;
impl Handler<Stop> for MCaptcha {
type Result = ();
fn handle(&mut self, _: Stop, ctx: &mut Self::Context) -> Self::Result {
ctx.stop()
}
}
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
@ -251,7 +264,7 @@ pub mod tests {
// (visitor count, level) // (visitor count, level)
pub const LEVEL_1: (u32, u32) = (50, 50); pub const LEVEL_1: (u32, u32) = (50, 50);
pub const LEVEL_2: (u32, u32) = (500, 500); pub const LEVEL_2: (u32, u32) = (500, 500);
pub const DURATION: u64 = 10; pub const DURATION: u64 = 5;
type MyActor = Addr<MCaptcha>; type MyActor = Addr<MCaptcha>;
@ -357,4 +370,12 @@ pub mod tests {
assert_eq!(count, 4); 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();
}
} }

View file

@ -92,8 +92,8 @@ mod tests {
const MCAPTCHA_NAME: &str = "batsense.net"; const MCAPTCHA_NAME: &str = "batsense.net";
async fn boostrap_system() -> System<HashCache> { async fn boostrap_system(gc: u64) -> System<HashCache> {
let master = Master::new().start(); let master = Master::new(gc).start();
let mcaptcha = get_counter().start(); let mcaptcha = get_counter().start();
let pow = get_config(); let pow = get_config();
@ -123,14 +123,14 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn get_pow_works() { 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(); let pow = actors.get_pow(MCAPTCHA_NAME.into()).await.unwrap();
assert_eq!(pow.difficulty_factor, LEVEL_1.0); assert_eq!(pow.difficulty_factor, LEVEL_1.0);
} }
#[actix_rt::test] #[actix_rt::test]
async fn verify_pow_works() { 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 work_req = actors.get_pow(MCAPTCHA_NAME.into()).await.unwrap();
let config = get_config(); let config = get_config();