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)
|
- `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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue