Skip to content

Commit

Permalink
feat!: replace FileSystem::canonicalize with FileSystem::read_link
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Dec 11, 2024
1 parent 31ff044 commit bc5b872
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 135 deletions.
104 changes: 82 additions & 22 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,29 @@ use std::{
io,
ops::Deref,
path::{Component, Path, PathBuf},
sync::Arc,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};

use cfg_if::cfg_if;
use dashmap::{DashMap, DashSet};
use once_cell::sync::OnceCell as OnceLock;
use rustc_hash::FxHasher;

use crate::{
context::ResolveContext as Ctx, package_json::PackageJson, FileMetadata, FileSystem,
ResolveError, ResolveOptions, TsConfig,
context::ResolveContext as Ctx, package_json::PackageJson, path::PathUtil, FileMetadata,
FileSystem, ResolveError, ResolveOptions, TsConfig,
};

static THREAD_COUNT: AtomicU64 = AtomicU64::new(1);

thread_local! {
/// Per-thread pre-allocated path that is used to perform operations on paths more quickly.
/// Learned from parcel <https://github.com/parcel-bundler/parcel/blob/a53f8f3ba1025c7ea8653e9719e0a61ef9717079/crates/parcel-resolver/src/cache.rs#L394>
pub static SCRATCH_PATH: UnsafeCell<PathBuf> = UnsafeCell::new(PathBuf::with_capacity(256));
pub static THREAD_ID: u64 = THREAD_COUNT.fetch_add(1, Ordering::SeqCst);
}

#[derive(Default)]
Expand Down Expand Up @@ -148,7 +155,8 @@ pub struct CachedPathImpl {
path: Box<Path>,
parent: Option<CachedPath>,
meta: OnceLock<Option<FileMetadata>>,
canonicalized: OnceLock<Option<CachedPath>>,
canonicalized: OnceLock<Result<CachedPath, ResolveError>>,
canonicalizing: AtomicU64,
node_modules: OnceLock<Option<CachedPath>>,
package_json: OnceLock<Option<(CachedPath, Arc<PackageJson>)>>,
}
Expand All @@ -161,6 +169,7 @@ impl CachedPathImpl {
parent,
meta: OnceLock::new(),
canonicalized: OnceLock::new(),
canonicalizing: AtomicU64::new(0),
node_modules: OnceLock::new(),
package_json: OnceLock::new(),
}
Expand Down Expand Up @@ -212,23 +221,67 @@ impl CachedPath {
)
}

pub fn realpath<Fs: FileSystem>(&self, cache: &Cache<Fs>) -> io::Result<Self> {
self.canonicalized
.get_or_try_init(|| {
if cache.fs.symlink_metadata(&self.path).is_ok_and(|m| m.is_symlink) {
let canonicalized = cache.fs.canonicalize(&self.path)?;
return Ok(Some(cache.value(&canonicalized)));
}
if let Some(parent) = self.parent() {
let parent_path = parent.realpath(cache)?;
let normalized = parent_path
.normalize_with(self.path.strip_prefix(&parent.path).unwrap(), cache);
return Ok(Some(normalized));
};
Ok(None)
pub fn canonicalize<Fs: FileSystem>(&self, cache: &Cache<Fs>) -> Result<PathBuf, ResolveError> {
let cached_path = self.canocalize_impl(cache)?;
let path = cached_path.to_path_buf();
cfg_if! {
if #[cfg(windows)] {
let path = crate::FileSystemOs::strip_windows_prefix(path);
}
}
Ok(path)
}

/// Returns the canonical path, resolving all symbolic links.
///
/// <https://github.com/parcel-bundler/parcel/blob/4d27ec8b8bd1792f536811fef86e74a31fa0e704/crates/parcel-resolver/src/cache.rs#L232>
fn canocalize_impl<Fs: FileSystem>(&self, cache: &Cache<Fs>) -> Result<Self, ResolveError> {
// Check if this thread is already canonicalizing. If so, we have found a circular symlink.
// If a different thread is canonicalizing, OnceLock will queue this thread to wait for the result.
let tid = THREAD_ID.with(|t| *t);
if self.0.canonicalizing.load(Ordering::Acquire) == tid {
return Err(io::Error::new(io::ErrorKind::NotFound, "Circular symlink").into());

Check warning on line 243 in src/cache.rs

View check run for this annotation

Codecov / codecov/patch

src/cache.rs#L243

Added line #L243 was not covered by tests
}

self.0
.canonicalized
.get_or_init(|| {
self.0.canonicalizing.store(tid, Ordering::Release);

let res = self.parent().map_or_else(
|| Ok(self.clone()),
|parent| {
parent.canocalize_impl(cache).and_then(|parent_canonical| {
let path = parent_canonical.normalize_with(
self.path().strip_prefix(parent.path()).unwrap(),
cache,
);

if cache.fs.symlink_metadata(self.path()).is_ok_and(|m| m.is_symlink) {
let link = cache.fs.read_link(path.path())?;
if link.is_absolute() {
return cache.value(&link.normalize()).canocalize_impl(cache);
} else if let Some(dir) = path.parent() {
// Symlink is relative `../../foo.js`, use the path directory
// to resolve this symlink.
return dir.normalize_with(&link, cache).canocalize_impl(cache);
}
debug_assert!(
false,
"Failed to get path parent for {:?}.",
path.path()

Check warning on line 272 in src/cache.rs

View check run for this annotation

Codecov / codecov/patch

src/cache.rs#L268-L272

Added lines #L268 - L272 were not covered by tests
);
}

Ok(path)
})
},
);

self.0.canonicalizing.store(0, Ordering::Release);
res
})
.cloned()
.map(|r| r.unwrap_or_else(|| self.clone()))
.clone()
}

pub fn module_directory<Fs: FileSystem>(
Expand Down Expand Up @@ -269,7 +322,7 @@ impl CachedPath {
return Ok(None);
};
let real_path = if options.symlinks {
self.realpath(cache)?.path().join("package.json")
self.canonicalize(cache)?.join("package.json")
} else {
package_json_path.clone()
};
Expand Down Expand Up @@ -382,7 +435,14 @@ impl CachedPath {
path.pop();
}
Component::Normal(c) => {
path.push(c);
cfg_if! {
if #[cfg(target_family = "wasm")] {
// Need to trim the extra \0 introduces by https://github.com/nodejs/uvwasi/issues/262
path.push(c.to_string_lossy().trim_end_matches('\0'));
} else {
path.push(c);
}
}
}
Component::Prefix(..) | Component::RootDir => {
unreachable!("Path {:?} Subpath {:?}", self.path, subpath)
Expand Down
Loading

0 comments on commit bc5b872

Please sign in to comment.