GC for managing MCaptcha actor instances
This commit is contained in:
parent
a4929922bf
commit
8d7cbfa0ba
6 changed files with 120 additions and 15 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue