Concurrent cache
This commit is contained in:
parent
39ebfcb1db
commit
789ba410f7
5 changed files with 31 additions and 34 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -306,6 +306,7 @@ dependencies = [
|
|||
"async-lock",
|
||||
"base64-turbo",
|
||||
"boml",
|
||||
"dashmap",
|
||||
"form_urlencoded",
|
||||
"giallo",
|
||||
"log",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
37
src/cache.rs
37
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<T> {
|
||||
inner: T,
|
||||
time: u64,
|
||||
time: AtomicU64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Cache<K, V> {
|
||||
pub struct Cache<K: Eq + Hash, V> {
|
||||
ttl: u64,
|
||||
size: usize,
|
||||
content: HashMap<K, CacheEntry<V>>,
|
||||
content: DashMap<K, CacheEntry<V>>,
|
||||
}
|
||||
|
||||
impl<K: Clone + Eq + Hash, V: Clone> Cache<K, V> {
|
||||
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<K>, f: impl Fn(K) -> Option<V>) -> Option<V> {
|
||||
if let Some(entry) = self.content.get_mut(key.borrow()) {
|
||||
pub fn fetch(&self, key: impl Borrow<K>, f: impl Fn(K) -> Option<V>) -> Option<V> {
|
||||
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<K: Clone + Eq + Hash, V: Clone> Cache<K, V> {
|
|||
}
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue