Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 46 additions & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ indexmap = { version = "2", features = ["serde"] }
json-strip-comments = "3.1"
nodejs-built-in-modules = "1.0.0"
once_cell = "1" # Use `std::sync::OnceLock::get_or_try_init` when it is stable.
papaya = "0.2"
dashmap = { version = "6.2.1", features = ["raw-api"] }
rustc-hash = { version = "2" }
serde = { version = "1", features = ["derive"] } # derive for Deserialize from package.json
serde_json = { version = "1", features = [
Expand Down
30 changes: 0 additions & 30 deletions src/cache/borrowed_path.rs

This file was deleted.

75 changes: 38 additions & 37 deletions src/cache/cache_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ use std::{
};

use cfg_if::cfg_if;
use dashmap::{DashMap, mapref::entry::Entry};
#[cfg(feature = "yarn_pnp")]
use once_cell::sync::OnceCell;
use papaya::{HashMap, HashSet};
use rustc_hash::FxHasher;

use super::borrowed_path::BorrowedCachedPath;
use super::cached_path::{CachedPath, CachedPathImpl};
use super::hasher::IdentityHasher;
use crate::{
Expand All @@ -25,20 +24,20 @@ use crate::{
#[derive(Default)]
pub struct Cache<Fs> {
pub(crate) fs: Fs,
pub(crate) paths: HashSet<CachedPath, BuildHasherDefault<IdentityHasher>>,
pub(crate) paths: DashMap<CachedPath, (), BuildHasherDefault<IdentityHasher>>,
/// Cache for raw/unbuilt tsconfigs (used when extending).
pub(crate) tsconfigs_raw: HashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
pub(crate) tsconfigs_raw: DashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
/// Cache for built/resolved tsconfigs (used for resolution).
pub(crate) tsconfigs_built: HashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
pub(crate) tsconfigs_built: DashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
#[cfg(feature = "yarn_pnp")]
pub(crate) yarn_pnp_manifest: OnceCell<pnp::Manifest>,
}

impl<Fs: FileSystem> Cache<Fs> {
pub fn clear(&self) {
self.paths.pin().clear();
self.tsconfigs_raw.pin().clear();
self.tsconfigs_built.pin().clear();
self.paths.clear();
self.tsconfigs_raw.clear();
self.tsconfigs_built.clear();
}

#[allow(clippy::cast_possible_truncation)]
Expand All @@ -50,9 +49,17 @@ impl<Fs: FileSystem> Cache<Fs> {
path.as_os_str().hash(&mut hasher);
hasher.finish()
};
let paths = self.paths.pin();
if let Some(entry) = paths.get(&BorrowedCachedPath { hash, path }) {
return entry.clone();
// Look up by the memoized `hash`. `IdentityHasher` only accepts a single `write_u64`, so the
// set can't be probed by a borrowed `&Path` through dashmap's `Borrow`-based `get`; instead
// read the shard directly (raw-api) with the precomputed hash and an `OsStr` equality. This
// mirrors the `Equivalent` lookup the original `papaya` set used, and keeps it zero-alloc.
{
let shard = self.paths.shards()[self.paths.determine_shard(hash as usize)].read();
if let Some((cached, _)) =
shard.get(hash, |(k, _)| k.path().as_os_str() == path.as_os_str())
{
return cached.clone();
}
}
let parent = path.parent().map(|p| self.value(p));
let is_node_modules = path.file_name().as_ref().is_some_and(|&name| name == "node_modules");
Expand All @@ -66,8 +73,17 @@ impl<Fs: FileSystem> Cache<Fs> {
inside_node_modules,
parent_weak,
)));
paths.insert(cached_path.clone());
cached_path
// The shard guard above is dropped before the parent recursion, so a concurrent call may
// have inserted this same path in the meantime. Dedup via the entry API so every path keeps
// a single shared `Arc`, which the `canonicalized` / `node_modules` weak-pointer caches rely
// on for identity.
match self.paths.entry(cached_path.clone()) {
Entry::Occupied(occupied) => occupied.key().clone(),
Entry::Vacant(vacant) => {
vacant.insert(());
cached_path
}
}
}

pub(crate) fn canonicalize(&self, path: &CachedPath) -> Result<PathBuf, ResolveError> {
Expand Down Expand Up @@ -229,20 +245,14 @@ impl<Fs: FileSystem> Cache<Fs> {
callback: F, // callback for modifying tsconfig with `extends`
) -> Result<Arc<TsConfig>, ResolveError> {
// For root=true (caller tsconfig), check built cache first
if root {
let tsconfigs_built = self.tsconfigs_built.pin();
if let Some(tsconfig) = tsconfigs_built.get(path) {
return Ok(Arc::clone(tsconfig));
}
if root && let Some(tsconfig) = self.tsconfigs_built.get(path) {
return Ok(Arc::clone(tsconfig.value()));
}

// Check raw cache (callback applied, not built) - only for root=false
// For root=true, we need to run the callback to ensure extends are processed
if !root {
let tsconfigs_raw = self.tsconfigs_raw.pin();
if let Some(tsconfig) = tsconfigs_raw.get(path) {
return Ok(Arc::clone(tsconfig));
}
if !root && let Some(tsconfig) = self.tsconfigs_raw.get(path) {
return Ok(Arc::clone(tsconfig.value()));
}

// Not in any cache, parse from file
Expand Down Expand Up @@ -277,13 +287,13 @@ impl<Fs: FileSystem> Cache<Fs> {
// Cache raw version (callback applied, not built)
tsconfig.set_should_build(false);
let raw_tsconfig = Arc::new(tsconfig.clone());
self.tsconfigs_raw.pin().insert(path.to_path_buf(), Arc::clone(&raw_tsconfig));
self.tsconfigs_raw.insert(path.to_path_buf(), Arc::clone(&raw_tsconfig));

if root {
// Build and cache built version
tsconfig.set_should_build(true);
let tsconfig = Arc::new(tsconfig.build());
self.tsconfigs_built.pin().insert(path.to_path_buf(), Arc::clone(&tsconfig));
self.tsconfigs_built.insert(path.to_path_buf(), Arc::clone(&tsconfig));
Ok(tsconfig)
} else {
// Return unbuilt version
Expand Down Expand Up @@ -322,18 +332,9 @@ impl<Fs: FileSystem> Cache<Fs> {
pub fn new(fs: Fs) -> Self {
Self {
fs,
paths: HashSet::builder()
.hasher(BuildHasherDefault::default())
.resize_mode(papaya::ResizeMode::Blocking)
.build(),
tsconfigs_raw: HashMap::builder()
.hasher(BuildHasherDefault::default())
.resize_mode(papaya::ResizeMode::Blocking)
.build(),
tsconfigs_built: HashMap::builder()
.hasher(BuildHasherDefault::default())
.resize_mode(papaya::ResizeMode::Blocking)
.build(),
paths: DashMap::with_hasher(BuildHasherDefault::default()),
tsconfigs_raw: DashMap::with_hasher(BuildHasherDefault::default()),
tsconfigs_built: DashMap::with_hasher(BuildHasherDefault::default()),
#[cfg(feature = "yarn_pnp")]
yarn_pnp_manifest: OnceCell::new(),
}
Expand Down
18 changes: 0 additions & 18 deletions src/cache/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
mod borrowed_path;
mod cache_impl;
mod cached_meta;
mod cached_path;
Expand All @@ -10,27 +9,10 @@ pub use cached_path::CachedPath;

#[cfg(test)]
mod tests {
use super::borrowed_path::BorrowedCachedPath;
use super::cache_impl::Cache;
use crate::FileSystem;
use std::path::Path;

#[test]
fn test_borrowed_cached_path_eq() {
let path1 = Path::new("/foo/bar");
let path2 = Path::new("/foo/bar");
let path3 = Path::new("/foo/baz");

let borrowed1 = BorrowedCachedPath { hash: 1, path: path1 };
let borrowed2 = BorrowedCachedPath { hash: 2, path: path2 };
let borrowed3 = BorrowedCachedPath { hash: 1, path: path3 };

// Same path should be equal even with different hash
assert_eq!(borrowed1, borrowed2);
// Different path should not be equal even with same hash
assert_ne!(borrowed1, borrowed3);
}

#[test]
fn test_cached_path_debug() {
#[cfg(feature = "yarn_pnp")]
Expand Down
Loading