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)
- `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

View file

@ -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

View file

@ -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

View file

@ -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<String, Addr<MCaptcha>>,
sites: BTreeMap<String, (Option<()>, Addr<MCaptcha>)>,
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<MCaptcha>> {
self.sites.get(id)
pub fn get_site<'a, 'b>(&'a mut self, id: &'b str) -> Option<Addr<MCaptcha>> {
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<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
@ -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]
#[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);
}
}

View file

@ -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<AddVisitor> for MCaptcha {
type Result = MessageResult<AddVisitor>;
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<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)]
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<MCaptcha>;
@ -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();
}
}

View file

@ -92,8 +92,8 @@ mod tests {
const MCAPTCHA_NAME: &str = "batsense.net";
async fn boostrap_system() -> System<HashCache> {
let master = Master::new().start();
async fn boostrap_system(gc: u64) -> System<HashCache> {
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();