diff --git a/Cargo.lock b/Cargo.lock index 55a1865..089a51c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,6 +306,7 @@ dependencies = [ "async-lock", "base64-turbo", "boml", + "dashmap", "form_urlencoded", "giallo", "log", diff --git a/Cargo.toml b/Cargo.toml index f1f02a7..b09bfbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ async-lock = "3.4.2" base64-turbo = "0.2.0" # TOML parser boml = "2.0.0" +# Concurrent hash map for caching +dashmap = "6.2.1" #flate2 = "1.1.8" # request parsing form_urlencoded = "1.2.2" diff --git a/src/cache.rs b/src/cache.rs index 964e0e4..9e1d10f 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,47 +1,50 @@ +use dashmap::DashMap; use std::{ borrow::Borrow, - collections::{HashMap, VecDeque}, hash::Hash, - time::Instant, + sync::atomic::{AtomicU64, Ordering::Relaxed}, }; pub struct CacheEntry { inner: T, - time: u64, + time: AtomicU64, } #[derive(Default)] -pub struct Cache { +pub struct Cache { ttl: u64, size: usize, - content: HashMap>, + content: DashMap>, } impl Cache { - pub fn sweep(&mut self) { + pub fn sweep(&self) { let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); - self.content.retain(|_k, entry| entry.time + self.ttl < now); + self.content + .retain(|_k, entry| entry.time.load(Relaxed) + self.ttl < now); } - fn sweep_oldest(&mut self) { + fn sweep_oldest(&self) { // TODO sweep the N oldest let mut oldest = None; let mut time = u64::MAX; - for (key, entry) in self.content.iter() { - if entry.time < time { - time = entry.time; - oldest = Some(key); + for item in self.content.iter() { + let (key, entry) = item.pair(); + let entry_time = entry.time.load(Relaxed); + if entry_time < time { + time = entry_time; + oldest = Some(key.clone()); } } - if let Some(oldest) = oldest.cloned() { + if let Some(oldest) = oldest { self.content.remove(&oldest); } } - pub fn fetch(&mut self, key: impl Borrow, f: impl Fn(K) -> Option) -> Option { - if let Some(entry) = self.content.get_mut(key.borrow()) { + pub fn fetch(&self, key: impl Borrow, f: impl Fn(K) -> Option) -> Option { + if let Some(entry) = self.content.get(key.borrow()) { let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); - entry.time = now; + entry.time.store(now, Relaxed); Some(entry.inner.clone()) } else if let Some(inner) = (f)(key.borrow().clone()) { if self.content.len() >= self.size { @@ -49,7 +52,7 @@ impl Cache { } let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); let entry = CacheEntry { - time: now, + time: AtomicU64::new(now), inner: inner.clone(), }; self.content.insert(key.borrow().clone(), entry); diff --git a/src/server.rs b/src/server.rs index facd319..85a3b23 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,7 +1,6 @@ use crate::{cache, config::Config, repo::ReadRepoMetadataError, templates}; use askama::Template; -use async_lock::Mutex; use log::error; use std::{ io::{ErrorKind, Read}, @@ -15,14 +14,12 @@ pub fn make_router(config: &'static Config) -> impl Handler { hl_registry.link_grammars(); let hl_registry: &'static _ = Box::leak(Box::new(hl_registry)); - let metadata_cache: &'static _ = Box::leak(Box::new(Mutex::new(cache::Cache::< + let metadata_cache: &'static _ = Box::leak(Box::new(cache::Cache::< String, templates::Directory, - >::default()))); - let file_cache: &'static _ = Box::leak(Box::new(Mutex::new(cache::Cache::< - (String, String), - String, - >::default()))); + >::default())); + let file_cache: &'static _ = + Box::leak(Box::new(cache::Cache::<(String, String), String>::default())); let client: &'static _ = Box::leak(Box::new(async_lock::Mutex::new( crate::api_client::make_client(), ))); @@ -133,11 +130,8 @@ pub fn make_router(config: &'static Config) -> impl Handler { } Some(root) }; - // TODO replace mutex with better thing (less contention or async mutex) - let Some(root) = metadata_cache - .lock() - .await - .fetch(repo_hash_str.to_string(), fetch_metadata) + let Some(root) = + metadata_cache.fetch(repo_hash_str.to_string(), fetch_metadata) else { return conn.with_status(404); }; @@ -169,7 +163,7 @@ pub fn make_router(config: &'static Config) -> impl Handler { Some(buf) }; - let Some(file_content) = file_cache.lock().await.fetch( + let Some(file_content) = file_cache.fetch( (repo_hash_str.to_string(), file_hash.to_string()), fetch_file, ) else { diff --git a/src/templates.rs b/src/templates.rs index 8a4f90b..1f15daf 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,7 +1,4 @@ -use std::{ - collections::{BTreeMap, BTreeSet, HashMap, btree_map, btree_set}, - iter::Peekable, -}; +use std::{collections::BTreeMap, iter::Peekable}; use log::warn; use trillium_askama::Template;