Skip to content

Commit 82071e5

Browse files
committed
fix: async recursion
1 parent 3528e3c commit 82071e5

File tree

5 files changed

+573
-475
lines changed

5 files changed

+573
-475
lines changed

src/cache.rs

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1+
use futures::future::BoxFuture;
12
use tokio::sync::OnceCell as OnceLock;
23

34
use std::{
4-
borrow::{Borrow, Cow}, convert::AsRef, future::Future, hash::{BuildHasherDefault, Hash, Hasher}, io, ops::Deref, path::{Path, PathBuf}, sync::Arc
5+
borrow::{Borrow, Cow},
6+
convert::AsRef,
7+
future::Future,
8+
hash::{BuildHasherDefault, Hash, Hasher},
9+
io,
10+
ops::Deref,
11+
path::{Path, PathBuf},
12+
sync::Arc,
513
};
614

715
use dashmap::{DashMap, DashSet};
@@ -56,7 +64,7 @@ impl<Fs: FileSystem> Cache<Fs> {
5664
) -> Result<Arc<TsConfig>, ResolveError>
5765
where
5866
F: FnOnce(TsConfig) -> Fut,
59-
Fut: Future<Output = Result<TsConfig, ResolveError>>
67+
Fut: Future<Output = Result<TsConfig, ResolveError>>,
6068
{
6169
if let Some(tsconfig_ref) = self.tsconfigs.get(path) {
6270
return Ok(Arc::clone(tsconfig_ref.value()));
@@ -188,23 +196,30 @@ impl CachedPathImpl {
188196
)
189197
}
190198

191-
pub async fn realpath<Fs: FileSystem>(&self, fs: &Fs) -> io::Result<PathBuf> {
192-
self.canonicalized
193-
.get_or_try_init(|| async move {
194-
if fs.symlink_metadata(&self.path).await.is_ok_and(|m| m.is_symlink) {
195-
return fs.canonicalize(&self.path).await.map(Some);
196-
}
197-
if let Some(parent) = self.parent() {
198-
let parent_path = parent.realpath(fs).await?;
199-
return Ok(Some(
200-
parent_path.normalize_with(self.path.strip_prefix(&parent.path).unwrap()),
201-
));
202-
};
203-
Ok(None)
204-
})
205-
.await
206-
.cloned()
207-
.map(|r| r.unwrap_or_else(|| self.path.clone().to_path_buf()))
199+
pub fn realpath<'a, Fs: FileSystem + Send + Sync>(
200+
&'a self,
201+
fs: &'a Fs,
202+
) -> BoxFuture<'a, io::Result<PathBuf>> {
203+
let fut = async move {
204+
self.canonicalized
205+
.get_or_try_init(|| async move {
206+
if fs.symlink_metadata(&self.path).await.is_ok_and(|m| m.is_symlink) {
207+
return fs.canonicalize(&self.path).await.map(Some);
208+
}
209+
if let Some(parent) = self.parent() {
210+
let parent_path = parent.realpath(fs).await?;
211+
return Ok(Some(
212+
parent_path
213+
.normalize_with(self.path.strip_prefix(&parent.path).unwrap()),
214+
));
215+
};
216+
Ok(None)
217+
})
218+
.await
219+
.cloned()
220+
.map(|r| r.unwrap_or_else(|| self.path.clone().to_path_buf()))
221+
};
222+
Box::pin(fut)
208223
}
209224

210225
pub async fn module_directory<Fs: FileSystem>(
@@ -233,7 +248,7 @@ impl CachedPathImpl {
233248
/// # Errors
234249
///
235250
/// * [ResolveError::JSON]
236-
pub async fn find_package_json<Fs: FileSystem>(
251+
pub async fn find_package_json<Fs: FileSystem + Send + Sync>(
237252
&self,
238253
fs: &Fs,
239254
options: &ResolveOptions,
@@ -263,7 +278,7 @@ impl CachedPathImpl {
263278
/// # Errors
264279
///
265280
/// * [ResolveError::JSON]
266-
pub async fn package_json<Fs: FileSystem>(
281+
pub async fn package_json<Fs: FileSystem + Send + Sync>(
267282
&self,
268283
fs: &Fs,
269284
options: &ResolveOptions,

src/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ impl ResolveContext {
8181
pub fn test_for_infinite_recursion(&mut self) -> Result<(), ResolveError> {
8282
self.depth += 1;
8383
// 64 should be more than enough for detecting infinite recursion.
84-
if self.depth > 64 {
84+
if self.depth > 32 {
8585
return Err(ResolveError::Recursion);
8686
}
8787
Ok(())

src/file_system.rs

Lines changed: 81 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use cfg_if::cfg_if;
2+
use futures::future::BoxFuture;
23
use std::{
34
fs, io,
45
path::{Path, PathBuf},
@@ -19,7 +20,7 @@ pub trait FileSystem {
1920
/// because object safety requirements, it is especially useful, when
2021
/// you want to store multiple `dyn FileSystem` in a `Vec` or use a `ResolverGeneric<Fs>` in
2122
/// napi env.
22-
async fn read_to_string(&self, path: &Path) -> io::Result<String>;
23+
fn read_to_string<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<String>>;
2324

2425
/// See [std::fs::metadata]
2526
///
@@ -30,7 +31,7 @@ pub trait FileSystem {
3031
/// because object safety requirements, it is especially useful, when
3132
/// you want to store multiple `dyn FileSystem` in a `Vec` or use a `ResolverGeneric<Fs>` in
3233
/// napi env.
33-
async fn metadata(&self, path: &Path) -> io::Result<FileMetadata>;
34+
fn metadata<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<FileMetadata>>;
3435

3536
/// See [std::fs::symlink_metadata]
3637
///
@@ -42,7 +43,7 @@ pub trait FileSystem {
4243
/// because object safety requirements, it is especially useful, when
4344
/// you want to store multiple `dyn FileSystem` in a `Vec` or use a `ResolverGeneric<Fs>` in
4445
/// napi env.
45-
async fn symlink_metadata(&self, path: &Path) -> io::Result<FileMetadata>;
46+
fn symlink_metadata<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<FileMetadata>>;
4647

4748
/// See [std::fs::canonicalize]
4849
///
@@ -54,7 +55,7 @@ pub trait FileSystem {
5455
/// because object safety requirements, it is especially useful, when
5556
/// you want to store multiple `dyn FileSystem` in a `Vec` or use a `ResolverGeneric<Fs>` in
5657
/// napi env.
57-
async fn canonicalize(&self, path: &Path) -> io::Result<PathBuf>;
58+
fn canonicalize<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<PathBuf>>;
5859
}
5960

6061
/// Metadata information about a file
@@ -120,92 +121,102 @@ impl Default for FileSystemOs {
120121
// }
121122

122123
impl FileSystem for FileSystemOs {
123-
async fn read_to_string(&self, path: &Path) -> io::Result<String> {
124-
cfg_if! {
125-
if #[cfg(feature = "yarn_pnp")] {
126-
match VPath::from(path)? {
127-
VPath::Zip(info) => {
128-
self.pnp_lru.read_to_string(info.physical_base_path(), info.zip_path)
124+
fn read_to_string<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<String>> {
125+
let fut = async move {
126+
cfg_if! {
127+
if #[cfg(feature = "yarn_pnp")] {
128+
match VPath::from(path)? {
129+
VPath::Zip(info) => {
130+
self.pnp_lru.read_to_string(info.physical_base_path(), info.zip_path)
131+
}
132+
VPath::Virtual(info) => tokio::fs::read_to_string(&info.physical_base_path()).await,
133+
VPath::Native(path) => tokio::fs::read_to_string(&path).await,
129134
}
130-
VPath::Virtual(info) => tokio::fs::read_to_string(&info.physical_base_path()).await,
131-
VPath::Native(path) => tokio::fs::read_to_string(&path).await,
135+
} else {
136+
tokio::fs::read_to_string(path).await
132137
}
133-
} else {
134-
tokio::fs::read_to_string(path).await
135138
}
136-
}
139+
};
140+
Box::pin(fut)
137141
}
138142

139-
async fn metadata(&self, path: &Path) -> io::Result<FileMetadata> {
140-
cfg_if! {
141-
if #[cfg(feature = "yarn_pnp")] {
142-
match VPath::from(path)? {
143-
VPath::Zip(info) => self
144-
.pnp_lru
145-
.file_type(info.physical_base_path(), info.zip_path)
146-
.map(FileMetadata::from),
147-
VPath::Virtual(info) => {
148-
tokio::fs::metadata(info.physical_base_path()).await.map(FileMetadata::from)
143+
fn metadata<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<FileMetadata>> {
144+
let fut = async move {
145+
cfg_if! {
146+
if #[cfg(feature = "yarn_pnp")] {
147+
match VPath::from(path)? {
148+
VPath::Zip(info) => self
149+
.pnp_lru
150+
.file_type(info.physical_base_path(), info.zip_path)
151+
.map(FileMetadata::from),
152+
VPath::Virtual(info) => {
153+
tokio::fs::metadata(info.physical_base_path()).await.map(FileMetadata::from)
154+
}
155+
VPath::Native(path) => tokio::fs::metadata(path).await.map(FileMetadata::from),
149156
}
150-
VPath::Native(path) => tokio::fs::metadata(path).await.map(FileMetadata::from),
157+
} else {
158+
tokio::fs::metadata(path).await.map(FileMetadata::from)
151159
}
152-
} else {
153-
tokio::fs::metadata(path).await.map(FileMetadata::from)
154160
}
155-
}
161+
};
162+
Box::pin(fut)
156163
}
157164

158-
async fn symlink_metadata(&self, path: &Path) -> io::Result<FileMetadata> {
159-
tokio::fs::symlink_metadata(path).await.map(FileMetadata::from)
165+
fn symlink_metadata<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<FileMetadata>> {
166+
let fut = async move { tokio::fs::symlink_metadata(path).await.map(FileMetadata::from) };
167+
Box::pin(fut)
160168
}
161169

162-
async fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
163-
cfg_if! {
164-
if #[cfg(feature = "yarn_pnp")] {
165-
match VPath::from(path)? {
166-
VPath::Zip(info) => {
167-
dunce::canonicalize(info.physical_base_path().join(info.zip_path))
170+
fn canonicalize<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<PathBuf>> {
171+
let fut = async move {
172+
cfg_if! {
173+
if #[cfg(feature = "yarn_pnp")] {
174+
match VPath::from(path)? {
175+
VPath::Zip(info) => {
176+
dunce::canonicalize(info.physical_base_path().join(info.zip_path))
177+
}
178+
VPath::Virtual(info) => dunce::canonicalize(info.physical_base_path()),
179+
VPath::Native(path) => dunce::canonicalize(path),
168180
}
169-
VPath::Virtual(info) => dunce::canonicalize(info.physical_base_path()),
170-
VPath::Native(path) => dunce::canonicalize(path),
171-
}
172-
} else if #[cfg(not(target_os = "wasi"))]{
173-
dunce::canonicalize(path)
174-
} else {
175-
use std::path::Component;
176-
let mut path_buf = path.to_path_buf();
177-
loop {
178-
let link = tokio::fs::read_link(&path_buf).await?;
179-
path_buf.pop();
180-
for component in link.components() {
181-
match component {
182-
Component::ParentDir => {
183-
path_buf.pop();
184-
}
185-
Component::Normal(seg) => {
186-
#[cfg(target_family = "wasm")]
187-
// Need to trim the extra \0 introduces by https://github.com/nodejs/uvwasi/issues/262
188-
{
189-
path_buf.push(seg.to_string_lossy().trim_end_matches('\0'));
181+
} else if #[cfg(not(target_os = "wasi"))]{
182+
dunce::canonicalize(path)
183+
} else {
184+
use std::path::Component;
185+
let mut path_buf = path.to_path_buf();
186+
loop {
187+
let link = tokio::fs::read_link(&path_buf).await?;
188+
path_buf.pop();
189+
for component in link.components() {
190+
match component {
191+
Component::ParentDir => {
192+
path_buf.pop();
190193
}
191-
#[cfg(not(target_family = "wasm"))]
192-
{
193-
path_buf.push(seg);
194+
Component::Normal(seg) => {
195+
#[cfg(target_family = "wasm")]
196+
// Need to trim the extra \0 introduces by https://github.com/nodejs/uvwasi/issues/262
197+
{
198+
path_buf.push(seg.to_string_lossy().trim_end_matches('\0'));
199+
}
200+
#[cfg(not(target_family = "wasm"))]
201+
{
202+
path_buf.push(seg);
203+
}
194204
}
205+
Component::RootDir => {
206+
path_buf = PathBuf::from("/");
207+
}
208+
Component::CurDir | Component::Prefix(_) => {}
195209
}
196-
Component::RootDir => {
197-
path_buf = PathBuf::from("/");
198-
}
199-
Component::CurDir | Component::Prefix(_) => {}
210+
}
211+
if !tokio::fs::symlink_metadata(&path_buf).await?.is_symlink() {
212+
break;
200213
}
201214
}
202-
if !tokio::fs::symlink_metadata(&path_buf).await?.is_symlink() {
203-
break;
204-
}
215+
Ok(path_buf)
205216
}
206-
Ok(path_buf)
207217
}
208-
}
218+
};
219+
Box::pin(fut)
209220
}
210221
}
211222

0 commit comments

Comments
 (0)