Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use papaya instead of dashmap #356

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 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
67 changes: 23 additions & 44 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = { git = "https://github.com/arendjr/papaya.git", branch = "add-get-by-hash" }
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" }
Expand Down
118 changes: 63 additions & 55 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -34,52 +34,30 @@ thread_local! {
#[derive(Default)]
pub struct Cache<Fs> {
pub(crate) fs: Fs,
paths: DashSet<PathEntry<'static>, BuildHasherDefault<IdentityHasher>>,
tsconfigs: DashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
paths: HashSet<CachedPath, 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(CachedPath),
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<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(),
}
}

pub fn clear(&self) {
self.paths.clear();
self.tsconfigs.clear();
self.paths.pin().clear();
self.tsconfigs.pin().clear();
}

#[allow(clippy::cast_possible_truncation)]
Expand All @@ -91,25 +69,17 @@ impl<Fs: FileSystem> Cache<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 = CachedPath(Arc::new(CachedPathImpl::new(
hash,
path.to_path_buf().into_boxed_path(),
parent,
)));
self.paths.insert(PathEntry::Owned(cached_path.clone()));
paths.insert(cached_path.clone());
cached_path
}

Expand All @@ -119,8 +89,9 @@ impl<Fs: FileSystem> Cache<Fs> {
path: &Path,
callback: F, // callback for modifying tsconfig with `extends`
) -> Result<Arc<TsConfig>, ResolveError> {
if let Some(tsconfig_ref) = self.tsconfigs.get(path) {
return Ok(Arc::clone(tsconfig_ref.value()));
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) {
Expand All @@ -142,7 +113,7 @@ impl<Fs: FileSystem> Cache<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)
}
}
Expand Down Expand Up @@ -467,6 +438,43 @@ impl CachedPath {
}
}

impl Hash for CachedPath {
fn hash<H: Hasher>(&self, state: &mut H) {
self.hash.hash(state);
}
}

impl PartialEq for CachedPath {
fn eq(&self, other: &Self) -> bool {
self.path.as_os_str() == other.path.as_os_str()
}
}

impl Eq for CachedPath {}

struct BorrowedCachedPath<'a> {
hash: u64,
path: &'a Path,
}

impl Equivalent<CachedPath> for BorrowedCachedPath<'_> {
fn equivalent(&self, other: &CachedPath) -> 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);
Boshen marked this conversation as resolved.
Show resolved Hide resolved
}
}

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)]
Expand Down
Loading