diff --git a/packages/utils/node-resolver-rs/src/cache.rs b/packages/utils/node-resolver-rs/src/cache.rs index 1a7fc402f..03e3ef82d 100644 --- a/packages/utils/node-resolver-rs/src/cache.rs +++ b/packages/utils/node-resolver-rs/src/cache.rs @@ -1,13 +1,13 @@ use std::borrow::Cow; +use std::collections::HashMap; use std::fmt; use std::ops::Deref; use std::path::Path; use std::path::PathBuf; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use atlaspack_core::types::File; use atlaspack_filesystem::{FileSystemRealPathCache, FileSystemRef}; -use atlaspack_shared_map::ThreadLocalHashMap; use crate::package_json::PackageJson; use crate::package_json::SourceField; @@ -15,20 +15,21 @@ use crate::tsconfig::TsConfig; use crate::tsconfig::TsConfigWrapper; use crate::ResolverError; +type DefaultHasher = xxhash_rust::xxh3::Xxh3Builder; +type LockedHashMap = RwLock>; + pub struct Cache { pub fs: FileSystemRef, /// These map paths to parsed config files. They aren't really 'static, but Rust doens't have a good /// way to associate a lifetime with owned data stored in the same struct. We only vend temporary references /// from our public methods so this is ok for now. FrozenMap is an append only map, which doesn't require &mut /// to insert into. Since each value is in a Box, it won't move and therefore references are stable. - #[allow(clippy::type_complexity)] - packages: ThreadLocalHashMap, ResolverError>>>, - #[allow(clippy::type_complexity)] - tsconfigs: ThreadLocalHashMap, ResolverError>>>, + packages: LockedHashMap, ResolverError>>>, + tsconfigs: LockedHashMap, ResolverError>>>, // In particular just the is_dir_cache spends around 8% of the time on a large project resolution // hashing paths. Instead of using a hashmap we should try a trie here. - is_dir_cache: ThreadLocalHashMap, - is_file_cache: ThreadLocalHashMap, + is_dir_cache: LockedHashMap, + is_file_cache: LockedHashMap, realpath_cache: FileSystemRealPathCache, } @@ -91,31 +92,39 @@ impl Cache { pub fn new(fs: FileSystemRef) -> Self { Self { fs, - packages: ThreadLocalHashMap::new(), - tsconfigs: ThreadLocalHashMap::new(), - is_file_cache: ThreadLocalHashMap::new(), - is_dir_cache: ThreadLocalHashMap::new(), + packages: RwLock::new(HashMap::with_hasher(DefaultHasher::default())), + tsconfigs: RwLock::new(HashMap::with_hasher(DefaultHasher::default())), + is_file_cache: RwLock::new(HashMap::with_hasher(DefaultHasher::default())), + is_dir_cache: RwLock::new(HashMap::with_hasher(DefaultHasher::default())), realpath_cache: FileSystemRealPathCache::default(), } } pub fn is_file(&self, path: &Path) -> bool { - if let Some(is_file) = self.is_file_cache.get(path) { - return is_file; + if let Some(is_file) = self.is_file_cache.read().unwrap().get(path) { + return *is_file; } let is_file = self.fs.is_file(path); - self.is_file_cache.insert(path.to_path_buf(), is_file); + self + .is_file_cache + .write() + .unwrap() + .insert(path.to_path_buf(), is_file); is_file } pub fn is_dir(&self, path: &Path) -> bool { - if let Some(is_file) = self.is_dir_cache.get(path) { - return is_file; + if let Some(is_file) = self.is_dir_cache.read().unwrap().get(path) { + return *is_file; } let is_file = self.fs.is_dir(path); - self.is_dir_cache.insert(path.to_path_buf(), is_file); + self + .is_dir_cache + .write() + .unwrap() + .insert(path.to_path_buf(), is_file); is_file } @@ -124,9 +133,11 @@ impl Cache { } pub fn read_package(&self, path: Cow) -> Arc, ResolverError>> { - if let Some(pkg) = self.packages.get(path.as_ref()) { + let read = self.packages.read().unwrap(); + if let Some(pkg) = read.get(path.as_ref()) { return pkg.clone(); } + drop(read); fn read_package<'a>( fs: &'a FileSystemRef, @@ -169,7 +180,11 @@ impl Cache { // Since we have exclusive access to packages, let entry = Arc::new(package.map(Arc::new)); - self.packages.insert(path.clone(), entry.clone()); + let _ = self + .packages + .write() + .unwrap() + .insert(path.clone(), entry.clone()); entry.clone() } @@ -179,9 +194,11 @@ impl Cache { path: &Path, process: F, ) -> Arc, ResolverError>> { - if let Some(tsconfig) = self.tsconfigs.get(path) { + let read = self.tsconfigs.read().unwrap(); + if let Some(tsconfig) = read.get(path) { return tsconfig.clone(); } + drop(read); fn read_tsconfig Result<(), ResolverError>>( fs: &FileSystemRef, @@ -206,7 +223,11 @@ impl Cache { // after insert let tsconfig = read_tsconfig(&self.fs, path, process).map(Arc::new); let tsconfig = Arc::new(tsconfig); - self.tsconfigs.insert(PathBuf::from(path), tsconfig.clone()); + let _ = self + .tsconfigs + .write() + .unwrap() + .insert(PathBuf::from(path), tsconfig.clone()); tsconfig }