data: use NonZeroUsize for memcache capacity
This commit is contained in:
parent
89aa01c266
commit
f54a172e07
1 changed files with 21 additions and 20 deletions
|
@ -1,11 +1,10 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
/// Shared in-memory LRU with multiple cache lines.
|
/// Shared in-memory LRU with multiple cache lines.
|
||||||
/// Basically a [`std::collections::HashMap`] that is [`Send`] + [`Sync`],
|
/// Basically a [`std::collections::HashMap`] that is [`Send`] + [`Sync`],
|
||||||
/// and without a mechanism to handle hash collisions.
|
/// and without a mechanism to handle hash collisions.
|
||||||
|
@ -14,8 +13,8 @@ pub struct MemCache<I: HashableId, T: Indexable<I>> {
|
||||||
_phantom: PhantomData<I>,
|
_phantom: PhantomData<I>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HashableId: Copy + fmt::Debug + Hash + PartialEq + Send {}
|
pub trait HashableId: Clone + fmt::Debug + Hash + PartialEq + Send {}
|
||||||
impl<T> HashableId for T where T: Copy + fmt::Debug + Hash + PartialEq + Send {}
|
impl<T> HashableId for T where T: Clone + fmt::Debug + Hash + PartialEq + Send {}
|
||||||
|
|
||||||
pub trait Indexable<I: HashableId>: Clone + Send {
|
pub trait Indexable<I: HashableId>: Clone + Send {
|
||||||
fn get_id(&self) -> I;
|
fn get_id(&self) -> I;
|
||||||
|
@ -23,11 +22,11 @@ pub trait Indexable<I: HashableId>: Clone + Send {
|
||||||
|
|
||||||
impl<I: HashableId, T: Indexable<I>> MemCache<I, T> {
|
impl<I: HashableId, T: Indexable<I>> MemCache<I, T> {
|
||||||
pub fn new() -> MemCache<I, T> {
|
pub fn new() -> MemCache<I, T> {
|
||||||
MemCache::with_capacity(256)
|
MemCache::with_capacity(unsafe { NonZeroUsize::new_unchecked(256) })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_capacity(capacity: usize) -> MemCache<I, T> {
|
pub fn with_capacity(capacity: NonZeroUsize) -> MemCache<I, T> {
|
||||||
assert!(capacity > 0);
|
let capacity = capacity.get();
|
||||||
let mut lines = Vec::with_capacity(capacity);
|
let mut lines = Vec::with_capacity(capacity);
|
||||||
for _ in 0..capacity {
|
for _ in 0..capacity {
|
||||||
lines.push(RwLock::new(None));
|
lines.push(RwLock::new(None));
|
||||||
|
@ -39,7 +38,7 @@ impl<I: HashableId, T: Indexable<I>> MemCache<I, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(&self, id: I) -> Option<Arc<T>> {
|
pub async fn get(&self, id: I) -> Option<Arc<T>> {
|
||||||
let index = self.compute_index(id);
|
let index = self.compute_index(&id);
|
||||||
let guard = self.lines[index].read().await;
|
let guard = self.lines[index].read().await;
|
||||||
let entry = Arc::clone(guard.as_ref()?);
|
let entry = Arc::clone(guard.as_ref()?);
|
||||||
drop(guard);
|
drop(guard);
|
||||||
|
@ -52,7 +51,7 @@ impl<I: HashableId, T: Indexable<I>> MemCache<I, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn put(&self, entry: T) {
|
pub async fn put(&self, entry: T) {
|
||||||
let index = self.compute_index(entry.get_id());
|
let index = self.compute_index(&entry.get_id());
|
||||||
let entry = Arc::new(entry);
|
let entry = Arc::new(entry);
|
||||||
let mut guard = self.lines[index].write().await;
|
let mut guard = self.lines[index].write().await;
|
||||||
let old_entry = guard.replace(entry);
|
let old_entry = guard.replace(entry);
|
||||||
|
@ -64,7 +63,7 @@ impl<I: HashableId, T: Indexable<I>> MemCache<I, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn del(&self, id: I) -> Option<Arc<T>> {
|
pub async fn del(&self, id: I) -> Option<Arc<T>> {
|
||||||
let index = self.compute_index(id);
|
let index = self.compute_index(&id);
|
||||||
let mut guard = self.lines[index].write().await;
|
let mut guard = self.lines[index].write().await;
|
||||||
let entry = guard.as_ref()?;
|
let entry = guard.as_ref()?;
|
||||||
if entry.get_id() == id {
|
if entry.get_id() == id {
|
||||||
|
@ -81,7 +80,7 @@ impl<I: HashableId, T: Indexable<I>> MemCache<I, T> {
|
||||||
F: FnOnce(&mut T),
|
F: FnOnce(&mut T),
|
||||||
{
|
{
|
||||||
// FIXME: Find a way to avoid the deep clone
|
// FIXME: Find a way to avoid the deep clone
|
||||||
let index = self.compute_index(id);
|
let index = self.compute_index(&id);
|
||||||
let mut guard = self.lines[index].write().await;
|
let mut guard = self.lines[index].write().await;
|
||||||
if let Some(entry) = guard.as_ref() {
|
if let Some(entry) = guard.as_ref() {
|
||||||
if entry.get_id() == id {
|
if entry.get_id() == id {
|
||||||
|
@ -93,7 +92,7 @@ impl<I: HashableId, T: Indexable<I>> MemCache<I, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_index(&self, id: I) -> usize {
|
fn compute_index(&self, id: &I) -> usize {
|
||||||
let mut hasher = Djb2::new();
|
let mut hasher = Djb2::new();
|
||||||
id.hash(&mut hasher);
|
id.hash(&mut hasher);
|
||||||
hasher.finish() as usize % self.lines.len()
|
hasher.finish() as usize % self.lines.len()
|
||||||
|
@ -135,6 +134,7 @@ impl Hasher for Djb2 {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
type TestId = Id<TestEntry>;
|
type TestId = Id<TestEntry>;
|
||||||
|
|
||||||
|
@ -152,12 +152,14 @@ mod tests {
|
||||||
|
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
async fn store_stuff() {
|
async fn store_stuff() {
|
||||||
let cache = MemCache::with_capacity(256);
|
let cache = MemCache::new();
|
||||||
for i in 0i64..1024i64 {
|
for i in 0i64..1024i64 {
|
||||||
let id = i.into();
|
let id = i.into();
|
||||||
let entry = TestEntry { id, data: 0 };
|
let entry = TestEntry { id, data: 0 };
|
||||||
cache.put(entry.clone()).await;
|
cache.put(entry.clone()).await;
|
||||||
assert_eq!(cache.get(id).await.map(|e| e.as_ref()), Some(&entry));
|
|
||||||
|
let retrieved_elem = cache.get(id).await.unwrap();
|
||||||
|
assert_eq!(retrieved_elem.as_ref(), &entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut had_entries = false;
|
let mut had_entries = false;
|
||||||
|
@ -176,20 +178,19 @@ mod tests {
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
async fn update_stuff() {
|
async fn update_stuff() {
|
||||||
let id = 420.into();
|
let id = 420.into();
|
||||||
let cache = MemCache::with_capacity(256);
|
let cache = MemCache::new();
|
||||||
cache.put(TestEntry { id, data: 69 }).await;
|
cache.put(TestEntry { id, data: 69 }).await;
|
||||||
cache.update(id, |entry| entry.data = 1312).await;
|
cache.update(id, |entry| entry.data = 1312).await;
|
||||||
assert_eq!(
|
|
||||||
cache.get(id).await.map(|e| e.as_ref()),
|
let retrieved_elem = cache.get(id).await.unwrap();
|
||||||
Some(&TestEntry { id, data: 1312 })
|
assert_eq!(retrieved_elem.as_ref(), &TestEntry { id, data: 1312 });
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
async fn mutate_id() {
|
async fn mutate_id() {
|
||||||
let id = 420.into();
|
let id = 420.into();
|
||||||
let cache = MemCache::with_capacity(256);
|
let cache = MemCache::new();
|
||||||
cache.put(TestEntry { id, data: 69 }).await;
|
cache.put(TestEntry { id, data: 69 }).await;
|
||||||
cache.update(id, |entry| entry.id = 1312.into()).await;
|
cache.update(id, |entry| entry.id = 1312.into()).await;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue