diff --git a/Cargo.lock b/Cargo.lock index f2fc808f..c1544af9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,20 +307,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "deranged" version = "0.3.11" @@ -403,12 +389,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - [[package]] name = "hashbrown" version = "0.15.2" @@ -516,7 +496,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -536,16 +516,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.22" @@ -681,15 +651,16 @@ version = "3.0.3" dependencies = [ "cfg-if", "criterion2", - "dashmap", "document-features", "indexmap 2.7.0", "json-strip-comments", "normalize-path", "once_cell", + "papaya", "pnp", "rayon", "rustc-hash", + "seize", "serde", "serde_json", "simdutf8", @@ -699,16 +670,13 @@ dependencies = [ ] [[package]] -name = "parking_lot_core" -version = "0.9.10" +name = "papaya" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "dc7c76487f7eaa00a0fc1d7f88dc6b295aec478d11b0fc79f857b62c2874124c" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", + "equivalent", + "seize", ] [[package]] @@ -863,10 +831,14 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "seize" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "d84b0c858bdd30cb56f5597f8b3bf702ec23829e652cc636a1e5a7b9de46ae93" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "semver" @@ -1196,6 +1168,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index 4b42ac59..35f136e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,11 +64,12 @@ name = "resolver" [dependencies] cfg-if = "1" -dashmap = { version = "6", features = ["raw-api"] } indexmap = { version = "2", features = ["serde"] } json-strip-comments = "1" once_cell = "1" # Use `std::sync::OnceLock::get_or_try_init` when it is stable. +papaya = "0.1.8" rustc-hash = { version = "2" } +seize = { version = "0.4" } serde = { version = "1", features = ["derive"] } # derive for Deserialize from package.json serde_json = { version = "1", features = ["preserve_order"] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]` simdutf8 = { version = "0.1" } diff --git a/src/fs_cache.rs b/src/fs_cache.rs index 615754c7..ff73c24e 100644 --- a/src/fs_cache.rs +++ b/src/fs_cache.rs @@ -13,8 +13,8 @@ use std::{ }; use cfg_if::cfg_if; -use dashmap::{DashMap, DashSet}; use once_cell::sync::OnceCell as OnceLock; +use papaya::{Equivalent, HashMap, HashSet}; use rustc_hash::FxHasher; use crate::{ @@ -38,50 +38,16 @@ thread_local! { #[derive(Default)] pub struct FsCache<Fs> { pub(crate) fs: Fs, - paths: DashSet<PathEntry<'static>, BuildHasherDefault<IdentityHasher>>, - tsconfigs: DashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>, + paths: HashSet<FsCachedPath, BuildHasherDefault<IdentityHasher>>, + tsconfigs: HashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>, } -/// An entry in the path cache. Can also be borrowed for lookups without allocations. -enum PathEntry<'a> { - Owned(FsCachedPath), - Borrowed { hash: u64, path: &'a Path }, -} - -impl Hash for PathEntry<'_> { - fn hash<H: Hasher>(&self, state: &mut H) { - match self { - PathEntry::Owned(entry) => { - entry.hash.hash(state); - } - PathEntry::Borrowed { hash, .. } => { - hash.hash(state); - } - } - } -} - -impl PartialEq for PathEntry<'_> { - fn eq(&self, other: &Self) -> bool { - let self_path = match self { - PathEntry::Owned(info) => &info.path, - PathEntry::Borrowed { path, .. } => *path, - }; - let other_path = match other { - PathEntry::Owned(info) => &info.path, - PathEntry::Borrowed { path, .. } => *path, - }; - self_path.as_os_str() == other_path.as_os_str() - } -} -impl Eq for PathEntry<'_> {} - impl<Fs: FileSystem> Cache for FsCache<Fs> { type Cp = FsCachedPath; fn clear(&self) { - self.paths.clear(); - self.tsconfigs.clear(); + self.paths.pin().clear(); + self.tsconfigs.pin().clear(); } #[allow(clippy::cast_possible_truncation)] @@ -93,17 +59,9 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> { path.as_os_str().hash(&mut hasher); hasher.finish() }; - let key = PathEntry::Borrowed { hash, path }; - // A DashMap is just an array of RwLock<HashSet>, sharded by hash to reduce lock contention. - // This uses the low level raw API to avoid cloning the value when using the `entry` method. - // First, find which shard the value is in, and check to see if we already have a value in the map. - let shard = self.paths.determine_shard(hash as usize); - { - // Scope the read lock. - let map = self.paths.shards()[shard].read(); - if let Some((PathEntry::Owned(entry), _)) = map.get(hash, |v| v.0 == key) { - return entry.clone(); - } + let paths = self.paths.pin(); + if let Some(entry) = paths.get(&BorrowedCachedPath { hash, path }) { + return entry.clone(); } let parent = path.parent().map(|p| self.value(p)); let cached_path = FsCachedPath(Arc::new(CachedPathImpl::new( @@ -111,7 +69,7 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> { path.to_path_buf().into_boxed_path(), parent, ))); - self.paths.insert(PathEntry::Owned(cached_path.clone())); + paths.insert(cached_path.clone()); cached_path } @@ -196,8 +154,9 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> { path: &Path, callback: F, // callback for modifying tsconfig with `extends` ) -> Result<Arc<TsConfig>, ResolveError> { - if let Some(tsconfig) = self.tsconfigs.get(path) { - return Ok(Arc::clone(&tsconfig)); + let tsconfigs = self.tsconfigs.pin(); + if let Some(tsconfig) = tsconfigs.get(path) { + return Ok(Arc::clone(tsconfig)); } let meta = self.fs.metadata(path).ok(); let tsconfig_path = if meta.is_some_and(|m| m.is_file) { @@ -219,14 +178,26 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> { })?; callback(&mut tsconfig)?; let tsconfig = Arc::new(tsconfig.build()); - self.tsconfigs.insert(path.to_path_buf(), Arc::clone(&tsconfig)); + tsconfigs.insert(path.to_path_buf(), Arc::clone(&tsconfig)); Ok(tsconfig) } } impl<Fs: FileSystem> FsCache<Fs> { pub fn new(fs: Fs) -> Self { - Self { fs, paths: DashSet::default(), tsconfigs: DashMap::default() } + Self { + fs, + paths: HashSet::builder() + .hasher(BuildHasherDefault::default()) + .resize_mode(papaya::ResizeMode::Blocking) + .collector(seize::Collector::new().epoch_frequency(None)) + .build(), + tsconfigs: HashMap::builder() + .hasher(BuildHasherDefault::default()) + .resize_mode(papaya::ResizeMode::Blocking) + .collector(seize::Collector::new().epoch_frequency(None)) + .build(), + } } /// Returns the canonical path, resolving all symbolic links. @@ -479,6 +450,29 @@ impl PartialEq for FsCachedPath { impl Eq for FsCachedPath {} +struct BorrowedCachedPath<'a> { + hash: u64, + path: &'a Path, +} + +impl Equivalent<FsCachedPath> for BorrowedCachedPath<'_> { + fn equivalent(&self, other: &FsCachedPath) -> bool { + self.path.as_os_str() == other.path.as_os_str() + } +} + +impl Hash for BorrowedCachedPath<'_> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.hash.hash(state); + } +} + +impl PartialEq for BorrowedCachedPath<'_> { + fn eq(&self, other: &Self) -> bool { + self.path.as_os_str() == other.path.as_os_str() + } +} + /// Since the cache key is memoized, use an identity hasher /// to avoid double cache. #[derive(Default)]