use std::path::PathBuf;
use std::collections::HashMap;
use std::sync::Arc;
use std::usize;
use cached_file::CachedFile;
use sized_file::SizedFile;
use priority_function::{PriorityFunction, DEFAULT_PRIORITY_FUNCTION};
#[derive(Debug, PartialEq)]
pub enum CacheInvalidationError {
NoMoreFilesToRemove,
NewPriorityIsNotHighEnough,
NewFileSmallerThanMin,
NewFileLargerThanMax,
NewFileLargerThanCache
}
#[derive(Debug, PartialEq)]
pub enum CacheInvalidationSuccess {
ReplacedFile,
InsertedFileIntoAvailableSpace,
}
#[derive(Debug)]
pub struct Cache {
pub(crate) size_limit: usize, pub(crate) min_file_size: usize, pub(crate) max_file_size: usize, pub(crate) priority_function: PriorityFunction, pub(crate) file_map: HashMap<PathBuf, Arc<SizedFile>>, pub(crate) access_count_map: HashMap<PathBuf, usize>, }
impl Cache {
pub fn new(size_limit: usize) -> Cache {
Cache {
size_limit,
min_file_size: 0,
max_file_size: usize::MAX,
priority_function: DEFAULT_PRIORITY_FUNCTION,
file_map: HashMap::new(),
access_count_map: HashMap::new(),
}
}
pub fn try_store(&mut self, path: PathBuf, file: Arc<SizedFile>) -> Result<CacheInvalidationSuccess, CacheInvalidationError> {
debug!("Possibly storing file: {:?} in the Cache.", path);
if file.size > self.max_file_size {
return Err(CacheInvalidationError::NewFileLargerThanMax)
}
if file.size < self.min_file_size {
return Err(CacheInvalidationError::NewFileSmallerThanMin)
}
if file.size > self.size_limit {
return Err(CacheInvalidationError::NewFileLargerThanCache)
}
let required_space_for_new_file: isize = (self.size_bytes() as isize + file.size as isize) - self.size_limit as isize;
if required_space_for_new_file < 0 {
debug!("Cache has room for the file.");
self.file_map.insert(path, file);
Ok(CacheInvalidationSuccess::InsertedFileIntoAvailableSpace)
} else {
let new_file_access_count: usize = *self.access_count_map.get(&path).unwrap_or(&0usize);
let new_file_priority: usize = (self.priority_function)(new_file_access_count, file.size);
match self.make_room_for_new_file(required_space_for_new_file as usize, new_file_priority) {
Ok(_) => {
debug!("Made room in the cache for new file. Adding new file to cache.");
self.file_map.insert(path, file);
Ok(CacheInvalidationSuccess::ReplacedFile)
}
Err(e) => {
debug!("The file does not have enough priority or is too large to be accepted into the cache.");
return Err(e);
}
}
}
}
fn make_room_for_new_file(&mut self, required_space: usize, new_file_priority: usize) -> Result<(), CacheInvalidationError> {
let mut possibly_freed_space: usize = 0;
let mut priority_score_to_free: usize = 0;
let mut file_paths_to_remove: Vec<PathBuf> = vec![];
let mut priorities: Vec<(PathBuf, usize, usize)> = self.sorted_priorities();
while possibly_freed_space < required_space {
match priorities.pop() {
Some(lowest) => {
let (lowest_key, lowest_file_priority, lowest_file_size) = lowest;
possibly_freed_space += lowest_file_size;
priority_score_to_free += lowest_file_priority;
file_paths_to_remove.push(lowest_key.clone());
if priority_score_to_free > new_file_priority {
return Err( CacheInvalidationError::NewPriorityIsNotHighEnough)
}
}
None => return Err( CacheInvalidationError::NoMoreFilesToRemove),
};
}
for file_key in file_paths_to_remove {
self.file_map.remove(&file_key);
}
return Ok(());
}
fn get(&mut self, path: &PathBuf) -> Option<CachedFile> {
match self.file_map.get(path) {
Some(sized_file) => {
Some(CachedFile {
path: path.clone(),
file: sized_file.clone(),
})
}
None => None, }
}
fn increment_access_count(&mut self, path: &PathBuf) {
let count: &mut usize = self.access_count_map.entry(path.to_path_buf()).or_insert(
0usize,
);
*count += 1; }
pub fn get_or_cache(&mut self, pathbuf: PathBuf) -> Option<CachedFile> {
trace!("{:#?}", self);
{
if let Some(cache_file) = self.get(&pathbuf) {
debug!("Cache hit for file: {:?}", pathbuf);
self.increment_access_count(&pathbuf); return Some(cache_file);
}
}
debug!("Cache missed for file: {:?}", pathbuf);
if let Ok(file) = SizedFile::open(pathbuf.as_path()) {
self.increment_access_count(&pathbuf);
let arc_file: Arc<SizedFile> = Arc::new(file);
let cached_file: CachedFile = CachedFile {
path: pathbuf.clone(),
file: arc_file.clone(),
};
let _ = self.try_store(pathbuf, arc_file); Some(cached_file)
} else {
None
}
}
fn sorted_priorities(&self) -> Vec<(PathBuf, usize, usize)> {
let mut priorities: Vec<(PathBuf, usize, usize)> = self.file_map
.iter()
.map(|file| {
let (file_key, sized_file) = file;
let access_count: usize = self.access_count_map
.get(file_key)
.unwrap_or(&1usize)
.clone();
let size: usize = sized_file.size;
let priority: usize = (self.priority_function)(access_count, size);
(file_key.clone(), priority, size)
})
.collect();
priorities.sort_by(|l, r| r.1.cmp(&l.1));
priorities
}
fn size_bytes(&self) -> usize {
self.file_map.iter().fold(0usize, |size, x| size + x.1.size)
}
}
#[cfg(test)]
mod tests {
extern crate test;
extern crate tempdir;
extern crate rand;
use self::tempdir::TempDir;
use super::*;
use self::test::Bencher;
use self::rand::{StdRng, Rng};
use std::io::{Write, BufWriter};
use std::fs::File;
use rocket::response::Response;
use rocket::response::NamedFile;
use std::io::Read;
const MEG1: usize = 1024 * 1024;
const MEG2: usize = MEG1 * 2;
const MEG5: usize = MEG1 * 5;
const MEG10: usize = MEG1 * 10;
const DIR_TEST: &'static str = "test1";
const FILE_MEG1: &'static str = "meg1.txt";
const FILE_MEG2: &'static str = "meg2.txt";
const FILE_MEG5: &'static str = "meg5.txt";
const FILE_MEG10: &'static str = "meg10.txt";
#[bench]
fn cache_get_10mb(b: &mut Bencher) {
let mut cache: Cache = Cache::new(MEG1 *20); let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_10m = create_test_file(&temp_dir, MEG10, FILE_MEG10);
cache.get_or_cache(path_10m.clone());
b.iter(|| {
let cached_file = cache.get_or_cache(path_10m.clone()).unwrap();
let file: *const SizedFile = Arc::into_raw(cached_file.file);
unsafe {
let _ = (*file).bytes.clone();
let _ = Arc::from_raw(file); }
});
}
#[bench]
fn cache_miss_10mb(b: &mut Bencher) {
let mut cache: Cache = Cache::new(0);
let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_10m = create_test_file(&temp_dir, MEG10, FILE_MEG10);
b.iter(|| {
let cached_file = cache.get_or_cache(path_10m.clone()).unwrap(); let mut response: Response = Response::new();
cached_file.set_response_body(&mut response);
});
}
#[bench]
fn named_file_read_10mb(b: &mut Bencher) {
let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_10m = create_test_file(&temp_dir, MEG10, FILE_MEG10);
b.iter(|| {
let mut named_file = NamedFile::open(path_10m.clone()).unwrap();
let mut v :Vec<u8> = vec![];
named_file.read_to_end(&mut v).unwrap();
});
}
#[bench]
fn cache_get_1mb(b: &mut Bencher) {
let mut cache: Cache = Cache::new(MEG1 *20); let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_1m = create_test_file(&temp_dir, MEG1, FILE_MEG1);
cache.get_or_cache(path_1m.clone());
b.iter(|| {
let cached_file = cache.get_or_cache(path_1m.clone()).unwrap();
let file: *const SizedFile = Arc::into_raw(cached_file.file);
unsafe {
let _ = (*file).bytes.clone();
let _ = Arc::from_raw(file); }
});
}
#[bench]
fn cache_miss_1mb(b: &mut Bencher) {
let mut cache: Cache = Cache::new(0);
let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_1m = create_test_file(&temp_dir, MEG1, FILE_MEG1);
b.iter(|| {
let cached_file = cache.get_or_cache(path_1m.clone()).unwrap(); let mut response: Response = Response::new();
cached_file.set_response_body(&mut response);
});
}
#[bench]
fn named_file_read_1mb(b: &mut Bencher) {
let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_1m = create_test_file(&temp_dir, MEG1, FILE_MEG1);
b.iter(|| {
let mut named_file = NamedFile::open(path_1m.clone()).unwrap();
let mut v :Vec<u8> = vec![];
named_file.read_to_end(&mut v).unwrap();
});
}
#[bench]
fn cache_get_5mb(b: &mut Bencher) {
let mut cache: Cache = Cache::new(MEG1 *20); let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_5m = create_test_file(&temp_dir, MEG5, FILE_MEG5);
cache.get_or_cache(path_5m.clone());
b.iter(|| {
let cached_file = cache.get_or_cache(path_5m.clone()).unwrap();
let file: *const SizedFile = Arc::into_raw(cached_file.file);
unsafe {
let _ = (*file).bytes.clone();
let _ = Arc::from_raw(file); }
});
}
#[bench]
fn cache_miss_5mb(b: &mut Bencher) {
let mut cache: Cache = Cache::new(0);
let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_5m = create_test_file(&temp_dir, MEG5, FILE_MEG5);
b.iter(|| {
let cached_file = cache.get_or_cache(path_5m.clone()).unwrap(); let mut response: Response = Response::new();
cached_file.set_response_body(&mut response);
});
}
#[bench]
fn named_file_read_5mb(b: &mut Bencher) {
let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_5m = create_test_file(&temp_dir, MEG5, FILE_MEG5);
b.iter(|| {
let mut named_file = NamedFile::open(path_5m.clone()).unwrap();
let mut v :Vec<u8> = vec![];
named_file.read_to_end(&mut v).unwrap();
});
}
#[test]
fn file_exceeds_size_limit() {
let mut cache: Cache = Cache::new(MEG1 * 8); let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_10m = create_test_file(&temp_dir, MEG10, FILE_MEG10);
assert_eq!(
cache.try_store(
path_10m.clone(),
Arc::new(SizedFile::open(path_10m.clone()).unwrap()),
),
Err(CacheInvalidationError::NewFileLargerThanCache)
)
}
#[test]
fn file_replaces_other_file() {
let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_1m = create_test_file(&temp_dir, MEG1, FILE_MEG1);
let path_5m = create_test_file(&temp_dir, MEG5, FILE_MEG5);
let mut cache: Cache = Cache::new(5500000); assert_eq!(
cache.try_store(
path_5m.clone(),
Arc::new(SizedFile::open(path_5m.clone()).unwrap()),
),
Ok(CacheInvalidationSuccess::InsertedFileIntoAvailableSpace)
);
cache.increment_access_count(&path_1m); assert_eq!(
cache.try_store(
path_1m.clone(),
Arc::new(SizedFile::open(path_1m.clone()).unwrap()),
),
Err(CacheInvalidationError::NewPriorityIsNotHighEnough)
);
cache.increment_access_count(&path_1m);
assert_eq!(
cache.try_store(
path_1m.clone(),
Arc::new(SizedFile::open(path_1m.clone()).unwrap()),
),
Err(CacheInvalidationError::NewPriorityIsNotHighEnough)
);
cache.increment_access_count(&path_1m);
assert_eq!(
cache.try_store(
path_1m.clone(),
Arc::new(SizedFile::open(path_1m.clone()).unwrap()),
),
Ok(CacheInvalidationSuccess::ReplacedFile)
);
}
fn create_test_file(temp_dir: &TempDir, size: usize, name: &str) -> PathBuf {
let path = temp_dir.path().join(name);
let tmp_file = File::create(path.clone()).unwrap();
let mut rand_data: Vec<u8> = vec![0u8; size];
StdRng::new().unwrap().fill_bytes(rand_data.as_mut());
let mut buffer = BufWriter::new(tmp_file);
buffer.write(&rand_data).unwrap();
path
}
#[test]
fn new_file_replaces_lowest_priority_file() {
let temp_dir = TempDir::new(DIR_TEST).unwrap();
let path_1m = create_test_file(&temp_dir, MEG1, FILE_MEG1);
let path_2m = create_test_file(&temp_dir, MEG2, FILE_MEG2);
let path_5m = create_test_file(&temp_dir, MEG5, FILE_MEG5);
let mut cache: Cache = Cache::new(MEG1 * 7 + 2000);
cache.increment_access_count(&path_5m);
assert_eq!(
cache.try_store(
path_5m.clone(),
Arc::new(SizedFile::open(path_5m.clone()).unwrap()),
),
Ok(CacheInvalidationSuccess::InsertedFileIntoAvailableSpace)
);
cache.increment_access_count(&path_2m);
assert_eq!(
cache.try_store(
path_2m.clone(),
Arc::new(SizedFile::open(path_2m.clone()).unwrap()),
),
Ok(CacheInvalidationSuccess::InsertedFileIntoAvailableSpace)
);
cache.increment_access_count(&path_1m);
assert_eq!(
cache.try_store(
path_1m.clone(),
Arc::new(SizedFile::open(path_1m.clone()).unwrap()),
),
Err(CacheInvalidationError::NewPriorityIsNotHighEnough)
);
cache.increment_access_count(&path_1m);
assert_eq!(
cache.try_store(
path_1m.clone(),
Arc::new(SizedFile::open(path_1m.clone()).unwrap()),
),
Ok(CacheInvalidationSuccess::ReplacedFile)
);
if let None = cache.get(&path_1m) {
assert_eq!(&path_1m, &PathBuf::new()) }
if let None = cache.get(&path_5m) {
assert_eq!(&path_5m, &PathBuf::new()) }
if let Some(_) = cache.get(&path_2m) {
assert_eq!(&path_2m, &PathBuf::new()) }
}
}