Skip to content

Commit cc28e95

Browse files
authored
fix: don't drop canonicalized path by cache clear (#791)
`Canonicalized path was dropped` error started to happen when I started to call `resolver.cache_clear()` frequently (so that I can clear the cache for each resolution to support vitest-dev/vitest#8754 (comment)). My guess of the cause is: - `resolver.cache_clear()` is called while a resolution is happening - `self.paths` is cleared because of that call - this `Arc::downgrade` removes the last strong reference: https://github.com/oxc-project/oxc-resolver/blob/9de30883232082e425ae0c837c69adf07e8570e6/src/cache/cache_impl.rs#L283 - this `weak.upgrade()` errors: https://github.com/oxc-project/oxc-resolver/blob/9de30883232082e425ae0c837c69adf07e8570e6/src/cache/cache_impl.rs#L288 This PR solves that by using `Mutex<Weak<_>>>` instead of `OnceLock<Weak<_>>`. Since `weak.upgrade()` may return None, I changed the code to resolve when `weak` returned None. The previous code assumed that `weak.upgrade()` always returns a value and that allowed us to use `OnceLock`. I guess this will have a negative impact on perf...
1 parent cbed279 commit cc28e95

File tree

2 files changed

+39
-53
lines changed

2 files changed

+39
-53
lines changed

src/cache/cache_impl.rs

Lines changed: 36 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -234,60 +234,46 @@ impl<Fs: FileSystem> Cache<Fs> {
234234
return Err(io::Error::new(io::ErrorKind::NotFound, "Circular symlink").into());
235235
}
236236

237-
path.canonicalized
238-
.get_or_init(|| {
239-
path.canonicalizing.store(tid, Ordering::Release);
240-
241-
let res = path.parent().map_or_else(
242-
|| Ok(path.normalize_root(self)),
243-
|parent| {
244-
self.canonicalize_impl(&parent).and_then(|parent_canonical| {
245-
let normalized = parent_canonical.normalize_with(
246-
path.path().strip_prefix(parent.path()).unwrap(),
247-
self,
248-
);
237+
let mut canonicalized_guard = path.canonicalized.lock().unwrap();
238+
let canonicalized = canonicalized_guard.clone()?;
239+
if let Some(cached_path) = canonicalized.upgrade() {
240+
return Ok(CachedPath(cached_path));
241+
}
249242

250-
if self.fs.symlink_metadata(path.path()).is_ok_and(|m| m.is_symlink) {
251-
let link = self.fs.read_link(normalized.path())?;
252-
if link.is_absolute() {
253-
return self.canonicalize_impl(&self.value(&link.normalize()));
254-
} else if let Some(dir) = normalized.parent() {
255-
// Symlink is relative `../../foo.js`, use the path directory
256-
// to resolve this symlink.
257-
return self
258-
.canonicalize_impl(&dir.normalize_with(&link, self));
259-
}
260-
debug_assert!(
261-
false,
262-
"Failed to get path parent for {}.",
263-
normalized.path().display()
264-
);
265-
}
243+
path.canonicalizing.store(tid, Ordering::Release);
266244

267-
Ok(normalized)
268-
})
269-
},
270-
);
245+
let res = path.parent().map_or_else(
246+
|| Ok(path.normalize_root(self)),
247+
|parent| {
248+
self.canonicalize_impl(&parent).and_then(|parent_canonical| {
249+
let normalized = parent_canonical
250+
.normalize_with(path.path().strip_prefix(parent.path()).unwrap(), self);
271251

272-
path.canonicalizing.store(0, Ordering::Release);
273-
// Store the canonicalized path in the cache before downgrading to weak reference
274-
// This ensures there's always at least one strong reference to prevent dropping
275-
if let Ok(ref cp) = res {
276-
// Only insert if not already present to avoid unnecessary operations
277-
let paths = self.paths.pin();
278-
if !paths.contains(cp) {
279-
paths.insert(cp.clone());
252+
if self.fs.symlink_metadata(path.path()).is_ok_and(|m| m.is_symlink) {
253+
let link = self.fs.read_link(normalized.path())?;
254+
if link.is_absolute() {
255+
return self.canonicalize_impl(&self.value(&link.normalize()));
256+
} else if let Some(dir) = normalized.parent() {
257+
// Symlink is relative `../../foo.js`, use the path directory
258+
// to resolve this symlink.
259+
return self.canonicalize_impl(&dir.normalize_with(&link, self));
260+
}
261+
debug_assert!(
262+
false,
263+
"Failed to get path parent for {}.",
264+
normalized.path().display()
265+
);
280266
}
281-
}
282-
// Convert to Weak reference for storage
283-
res.map(|cp| Arc::downgrade(&cp.0))
284-
})
285-
.as_ref()
286-
.map_err(Clone::clone)
287-
.and_then(|weak| {
288-
weak.upgrade().map(CachedPath).ok_or_else(|| {
289-
ResolveError::from(io::Error::other("Canonicalized path was dropped"))
267+
268+
Ok(normalized)
290269
})
291-
})
270+
},
271+
);
272+
273+
path.canonicalizing.store(0, Ordering::Release);
274+
// Convert to Weak reference for storage
275+
*canonicalized_guard = res.as_ref().map_err(Clone::clone).map(|cp| Arc::downgrade(&cp.0));
276+
277+
res
292278
}
293279
}

src/cache/cached_path.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{
44
hash::{Hash, Hasher},
55
ops::Deref,
66
path::{Component, Path, PathBuf},
7-
sync::{Arc, Weak, atomic::AtomicU64},
7+
sync::{Arc, Mutex, Weak, atomic::AtomicU64},
88
};
99

1010
use cfg_if::cfg_if;
@@ -27,7 +27,7 @@ pub struct CachedPathImpl {
2727
pub is_node_modules: bool,
2828
pub inside_node_modules: bool,
2929
pub meta: OnceLock<Option<FileMetadata>>,
30-
pub canonicalized: OnceLock<Result<Weak<CachedPathImpl>, ResolveError>>,
30+
pub canonicalized: Mutex<Result<Weak<CachedPathImpl>, ResolveError>>,
3131
pub canonicalizing: AtomicU64,
3232
pub node_modules: OnceLock<Option<Weak<CachedPathImpl>>>,
3333
pub package_json: OnceLock<Option<Arc<PackageJson>>>,
@@ -49,7 +49,7 @@ impl CachedPathImpl {
4949
is_node_modules,
5050
inside_node_modules,
5151
meta: OnceLock::new(),
52-
canonicalized: OnceLock::new(),
52+
canonicalized: Mutex::new(Ok(Weak::new())),
5353
canonicalizing: AtomicU64::new(0),
5454
node_modules: OnceLock::new(),
5555
package_json: OnceLock::new(),

0 commit comments

Comments
 (0)