Allow configuring the number of days to expire documents (#18)

* Allow configuring the number of days to expire documents

* Update server tests about server data

* Update test_cleanup for testing a different expiry value

* Remove unnecessary Arc<> while passing expiry_days to cleaner

* Rename `ServerData` to `ServerConfig`

* Update tests to rename the `ServerData`

* Update rustpad-server/tests/cleanup.rs

Co-authored-by: Eric Zhang <ekzhang1@gmail.com>
This commit is contained in:
Orhun Parmaksız 2021-07-16 02:19:43 +03:00 committed by GitHub
parent c0cd505fc6
commit f50f987626
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 52 additions and 26 deletions

1
.env
View File

@ -1,2 +1,3 @@
# Environment variables for development
RUST_LOG=info
EXPIRY_DAYS=1

View File

@ -44,9 +44,25 @@ struct Stats {
num_documents: usize,
}
/// Server configuration.
#[derive(Debug)]
pub struct ServerConfig {
/// Number of days to clean up documents after inactivity.
pub expiry_days: u32,
}
impl Default for ServerConfig {
fn default() -> Self {
Self { expiry_days: 1 }
}
}
/// A combined filter handling all server routes.
pub fn server() -> BoxedFilter<(impl Reply,)> {
warp::path("api").and(backend()).or(frontend()).boxed()
pub fn server(config: ServerConfig) -> BoxedFilter<(impl Reply,)> {
warp::path("api")
.and(backend(config))
.or(frontend())
.boxed()
}
/// Construct routes for static files from React.
@ -55,9 +71,9 @@ fn frontend() -> BoxedFilter<(impl Reply,)> {
}
/// Construct backend routes, including WebSocket handlers.
fn backend() -> BoxedFilter<(impl Reply,)> {
fn backend(config: ServerConfig) -> BoxedFilter<(impl Reply,)> {
let state: Arc<DashMap<String, Document>> = Default::default();
tokio::spawn(cleaner(Arc::clone(&state)));
tokio::spawn(cleaner(Arc::clone(&state), config.expiry_days));
let state_filter = warp::any().map(move || Arc::clone(&state));
@ -106,15 +122,14 @@ fn backend() -> BoxedFilter<(impl Reply,)> {
}
const HOUR: Duration = Duration::from_secs(3600);
const DAY: Duration = Duration::from_secs(24 * 3600);
// Reclaims memory for documents after a day of inactivity.
async fn cleaner(state: Arc<DashMap<String, Document>>) {
// Reclaims memory for documents.
async fn cleaner(state: Arc<DashMap<String, Document>>, expiry_days: u32) {
loop {
time::sleep(HOUR).await;
let mut keys = Vec::new();
for entry in &*state {
if entry.last_accessed.elapsed() > DAY {
if entry.last_accessed.elapsed() > HOUR * 24 * expiry_days {
keys.push(entry.key().clone());
}
}

View File

@ -1,4 +1,4 @@
use rustpad_server::server;
use rustpad_server::{server, ServerConfig};
#[tokio::main]
async fn main() {
@ -10,5 +10,12 @@ async fn main() {
.parse()
.expect("Unable to parse PORT");
warp::serve(server()).run(([0, 0, 0, 0], port)).await;
let config = ServerConfig {
expiry_days: std::env::var("EXPIRY_DAYS")
.unwrap_or_else(|_| String::from("1"))
.parse()
.expect("Unable to parse EXPIRY_DAYS"),
};
warp::serve(server(config)).run(([0, 0, 0, 0], port)).await;
}

View File

@ -5,7 +5,7 @@ use std::time::Duration;
use anyhow::Result;
use common::*;
use operational_transform::OperationSeq;
use rustpad_server::server;
use rustpad_server::{server, ServerConfig};
use serde_json::json;
use tokio::time;
@ -14,7 +14,10 @@ pub mod common;
#[tokio::test]
async fn test_cleanup() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig {
expiry_days: 2,
..ServerConfig::default()
});
expect_text(&filter, "old", "").await;
@ -39,7 +42,7 @@ async fn test_cleanup() -> Result<()> {
let hour = Duration::from_secs(3600);
time::pause();
time::advance(23 * hour).await;
time::advance(47 * hour).await;
expect_text(&filter, "old", "hello").await;
time::advance(3 * hour).await;

View File

@ -6,7 +6,7 @@ use anyhow::Result;
use common::*;
use log::info;
use operational_transform::OperationSeq;
use rustpad_server::server;
use rustpad_server::{server, ServerConfig};
use serde_json::json;
use tokio::time;
@ -15,7 +15,7 @@ pub mod common;
#[tokio::test]
async fn test_single_operation() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig::default());
expect_text(&filter, "foobar", "").await;
@ -54,7 +54,7 @@ async fn test_single_operation() -> Result<()> {
#[tokio::test]
async fn test_invalid_operation() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig::default());
expect_text(&filter, "foobar", "").await;
@ -80,7 +80,7 @@ async fn test_invalid_operation() -> Result<()> {
#[tokio::test]
async fn test_concurrent_transform() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig::default());
// Connect the first client
let mut client = connect(&filter, "foobar").await?;
@ -199,7 +199,7 @@ async fn test_concurrent_transform() -> Result<()> {
#[tokio::test]
async fn test_set_language() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig::default());
let mut client = connect(&filter, "foobar").await?;
let msg = client.recv().await?;

View File

@ -6,7 +6,7 @@ use anyhow::{anyhow, Result};
use common::*;
use log::info;
use operational_transform::OperationSeq;
use rustpad_server::server;
use rustpad_server::{server, ServerConfig};
use serde_json::{json, Value};
use tokio::time::Instant;
@ -15,7 +15,7 @@ pub mod common;
#[tokio::test]
async fn test_lost_wakeups() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig::default());
expect_text(&filter, "stress", "").await;
@ -74,7 +74,7 @@ async fn test_lost_wakeups() -> Result<()> {
#[tokio::test]
async fn test_large_document() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig::default());
expect_text(&filter, "stress", "").await;

View File

@ -2,7 +2,7 @@
use anyhow::Result;
use common::*;
use rustpad_server::server;
use rustpad_server::{server, ServerConfig};
use serde_json::json;
pub mod common;
@ -10,7 +10,7 @@ pub mod common;
#[tokio::test]
async fn test_two_users() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig::default());
let mut client = connect(&filter, "foobar").await?;
assert_eq!(client.recv().await?, json!({ "Identity": 0 }));
@ -54,7 +54,7 @@ async fn test_two_users() -> Result<()> {
#[tokio::test]
async fn test_invalid_user() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig::default());
let mut client = connect(&filter, "foobar").await?;
assert_eq!(client.recv().await?, json!({ "Identity": 0 }));
@ -69,7 +69,7 @@ async fn test_invalid_user() -> Result<()> {
#[tokio::test]
async fn test_leave_rejoin() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig::default());
let mut client = connect(&filter, "foobar").await?;
assert_eq!(client.recv().await?, json!({ "Identity": 0 }));
@ -114,7 +114,7 @@ async fn test_leave_rejoin() -> Result<()> {
#[tokio::test]
async fn test_cursors() -> Result<()> {
pretty_env_logger::try_init().ok();
let filter = server();
let filter = server(ServerConfig::default());
let mut client = connect(&filter, "foobar").await?;
assert_eq!(client.recv().await?, json!({ "Identity": 0 }));