0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-19 11:52:52 -05:00
denoland-deno/resolvers/node/cache.rs
David Sherret 61faa32920
perf: node resolution cache (#27838)
This adds a cache for node resolution, which makes repeat lookups about
15x faster.
2025-02-03 20:25:10 -05:00

197 lines
5.2 KiB
Rust

// Copyright 2018-2025 the Deno authors. MIT license.
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use sys_traits::BaseFsCanonicalize;
use sys_traits::BaseFsRead;
use sys_traits::BaseFsReadDir;
use sys_traits::FileType;
use sys_traits::FsCanonicalize;
use sys_traits::FsMetadata;
use sys_traits::FsMetadataValue;
use sys_traits::FsRead;
use sys_traits::FsReadDir;
pub trait NodeResolutionCache:
std::fmt::Debug + crate::sync::MaybeSend + crate::sync::MaybeSync
{
fn get_canonicalized(
&self,
path: &Path,
) -> Option<Result<PathBuf, std::io::Error>>;
fn set_canonicalized(&self, from: PathBuf, to: &std::io::Result<PathBuf>);
fn get_file_type(&self, path: &Path) -> Option<Option<FileType>>;
fn set_file_type(&self, path: PathBuf, value: Option<FileType>);
}
thread_local! {
static CANONICALIZED_CACHE: RefCell<HashMap<PathBuf, Option<PathBuf>>> = RefCell::new(HashMap::new());
static FILE_TYPE_CACHE: RefCell<HashMap<PathBuf, Option<FileType>>> = RefCell::new(HashMap::new());
}
// We use thread local caches here because it's just more convenient
// and easily allows workers to have separate caches.
#[derive(Debug)]
pub struct NodeResolutionThreadLocalCache;
impl NodeResolutionThreadLocalCache {
pub fn clear() {
CANONICALIZED_CACHE.with_borrow_mut(|cache| cache.clear());
FILE_TYPE_CACHE.with_borrow_mut(|cache| cache.clear());
}
}
impl NodeResolutionCache for NodeResolutionThreadLocalCache {
fn get_canonicalized(
&self,
path: &Path,
) -> Option<Result<PathBuf, std::io::Error>> {
CANONICALIZED_CACHE.with_borrow(|cache| {
let item = cache.get(path)?;
Some(match item {
Some(value) => Ok(value.clone()),
None => Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Not found.",
)),
})
})
}
fn set_canonicalized(&self, from: PathBuf, to: &std::io::Result<PathBuf>) {
CANONICALIZED_CACHE.with_borrow_mut(|cache| match to {
Ok(to) => {
cache.insert(from, Some(to.clone()));
}
Err(err) => {
if err.kind() == std::io::ErrorKind::NotFound {
cache.insert(from, None);
}
}
});
}
fn get_file_type(&self, path: &Path) -> Option<Option<FileType>> {
FILE_TYPE_CACHE.with_borrow(|cache| cache.get(path).cloned())
}
fn set_file_type(&self, path: PathBuf, value: Option<FileType>) {
FILE_TYPE_CACHE.with_borrow_mut(|cache| {
cache.insert(path, value);
})
}
}
#[allow(clippy::disallowed_types)]
pub type NodeResolutionCacheRc = crate::sync::MaybeArc<dyn NodeResolutionCache>;
#[derive(Debug, Default)]
pub struct NodeResolutionSys<TSys> {
sys: TSys,
cache: Option<NodeResolutionCacheRc>,
}
impl<TSys: Clone> Clone for NodeResolutionSys<TSys> {
fn clone(&self) -> Self {
Self {
sys: self.sys.clone(),
cache: self.cache.clone(),
}
}
}
impl<TSys: FsMetadata> NodeResolutionSys<TSys> {
pub fn new(sys: TSys, store: Option<NodeResolutionCacheRc>) -> Self {
Self { sys, cache: store }
}
pub fn is_file(&self, path: &Path) -> bool {
match self.get_file_type(path) {
Ok(file_type) => file_type.is_file(),
Err(_) => false,
}
}
pub fn is_dir(&self, path: &Path) -> bool {
match self.get_file_type(path) {
Ok(file_type) => file_type.is_dir(),
Err(_) => false,
}
}
pub fn exists_(&self, path: &Path) -> bool {
self.get_file_type(path).is_ok()
}
pub fn get_file_type(&self, path: &Path) -> std::io::Result<FileType> {
{
if let Some(maybe_value) =
self.cache.as_ref().and_then(|c| c.get_file_type(path))
{
return match maybe_value {
Some(value) => Ok(value),
None => Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Not found.",
)),
};
}
}
match self.sys.fs_metadata(path) {
Ok(metadata) => {
if let Some(cache) = &self.cache {
cache.set_file_type(path.to_path_buf(), Some(metadata.file_type()));
}
Ok(metadata.file_type())
}
Err(err) => {
if let Some(cache) = &self.cache {
cache.set_file_type(path.to_path_buf(), None);
}
Err(err)
}
}
}
}
impl<TSys: FsCanonicalize> BaseFsCanonicalize for NodeResolutionSys<TSys> {
fn base_fs_canonicalize(&self, from: &Path) -> std::io::Result<PathBuf> {
if let Some(cache) = &self.cache {
if let Some(result) = cache.get_canonicalized(from) {
return result;
}
}
let result = self.sys.base_fs_canonicalize(from);
if let Some(cache) = &self.cache {
cache.set_canonicalized(from.to_path_buf(), &result);
}
result
}
}
impl<TSys: FsReadDir> BaseFsReadDir for NodeResolutionSys<TSys> {
type ReadDirEntry = TSys::ReadDirEntry;
#[inline(always)]
fn base_fs_read_dir(
&self,
path: &Path,
) -> std::io::Result<
Box<dyn Iterator<Item = std::io::Result<Self::ReadDirEntry>> + '_>,
> {
self.sys.base_fs_read_dir(path)
}
}
impl<TSys: FsRead> BaseFsRead for NodeResolutionSys<TSys> {
#[inline(always)]
fn base_fs_read(
&self,
path: &Path,
) -> std::io::Result<std::borrow::Cow<'static, [u8]>> {
self.sys.base_fs_read(path)
}
}