Skip to content

Commit c7815ad

Browse files
authored
Turbopack: cache directory creation (#78729)
### What? Calling create directory for every written file is very expensive. Instead keep a memory cache of already created directories.
1 parent e94b795 commit c7815ad

File tree

3 files changed

+151
-59
lines changed

3 files changed

+151
-59
lines changed

turbopack/crates/turbo-tasks-fs/src/lib.rs

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use mime::Mime;
4848
use rayon::iter::{IntoParallelIterator, ParallelIterator};
4949
pub use read_glob::ReadGlobResult;
5050
use read_glob::{read_glob, track_glob};
51+
use rustc_hash::FxHashSet;
5152
use serde::{Deserialize, Serialize};
5253
use serde_json::Value;
5354
use tokio::{
@@ -59,8 +60,8 @@ use tracing::Instrument;
5960
use turbo_rcstr::RcStr;
6061
use turbo_tasks::{
6162
debug::ValueDebugFormat, effect, mark_session_dependent, mark_stateful, trace::TraceRawVcs,
62-
Completion, InvalidationReason, Invalidator, NonLocalValue, ReadRef, ResolvedVc, ValueToString,
63-
Vc,
63+
ApplyEffectsContext, Completion, InvalidationReason, Invalidator, NonLocalValue, ReadRef,
64+
ResolvedVc, ValueToString, Vc,
6465
};
6566
use turbo_tasks_hash::{
6667
hash_xxh3_hash128, hash_xxh3_hash64, DeterministicHash, DeterministicHasher,
@@ -208,6 +209,12 @@ pub trait FileSystem: ValueToString {
208209
fn metadata(self: Vc<Self>, fs_path: Vc<FileSystemPath>) -> Vc<FileMeta>;
209210
}
210211

212+
#[derive(Default)]
213+
struct DiskFileSystemApplyContext {
214+
/// A cache of already created directories to avoid creating them multiple times.
215+
created_directories: FxHashSet<PathBuf>,
216+
}
217+
211218
#[derive(Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
212219
struct DiskFileSystemInner {
213220
pub name: RcStr,
@@ -393,6 +400,29 @@ impl DiskFileSystemInner {
393400

394401
Ok(())
395402
}
403+
404+
async fn create_directory(self: &Arc<Self>, directory: &Path) -> Result<()> {
405+
let already_created = ApplyEffectsContext::with_or_insert_with(
406+
DiskFileSystemApplyContext::default,
407+
|fs_context| fs_context.created_directories.contains(directory),
408+
);
409+
if !already_created {
410+
let func = |p: &Path| std::fs::create_dir_all(p);
411+
retry_blocking(directory, func)
412+
.concurrency_limited(&self.semaphore)
413+
.instrument(tracing::info_span!(
414+
"create directory",
415+
path = display(directory.display())
416+
))
417+
.await?;
418+
ApplyEffectsContext::with(|fs_context: &mut DiskFileSystemApplyContext| {
419+
fs_context
420+
.created_directories
421+
.insert(directory.to_path_buf())
422+
});
423+
}
424+
Ok(())
425+
}
396426
}
397427

398428
#[turbo_tasks::value(cell = "new", eq = "manual")]
@@ -735,20 +765,13 @@ impl FileSystem for DiskFileSystem {
735765
let create_directory = compare == FileComparison::Create;
736766
if create_directory {
737767
if let Some(parent) = full_path.parent() {
738-
retry_blocking(parent, |p| std::fs::create_dir_all(p))
739-
.concurrency_limited(&inner.semaphore)
740-
.instrument(tracing::info_span!(
741-
"create directory",
742-
path = display(parent.display())
743-
))
744-
.await
745-
.with_context(|| {
746-
format!(
747-
"failed to create directory {} for write to {}",
748-
parent.display(),
749-
full_path.display()
750-
)
751-
})?;
768+
inner.create_directory(parent).await.with_context(|| {
769+
format!(
770+
"failed to create directory {} for write to {}",
771+
parent.display(),
772+
full_path.display()
773+
)
774+
})?;
752775
}
753776
}
754777
let full_path_to_write = full_path.clone();
@@ -872,20 +895,13 @@ impl FileSystem for DiskFileSystem {
872895
let create_directory = old_content.is_none();
873896
if create_directory {
874897
if let Some(parent) = full_path.parent() {
875-
retry_blocking(parent, |path| std::fs::create_dir_all(path))
876-
.concurrency_limited(&inner.semaphore)
877-
.instrument(tracing::info_span!(
878-
"create directory",
879-
path = display(parent.display())
880-
))
881-
.await
882-
.with_context(|| {
883-
format!(
884-
"failed to create directory {} for write to {}",
885-
parent.display(),
886-
full_path.display()
887-
)
888-
})?;
898+
inner.create_directory(parent).await.with_context(|| {
899+
format!(
900+
"failed to create directory {} for write link to {}",
901+
parent.display(),
902+
full_path.display()
903+
)
904+
})?;
889905
}
890906
}
891907

turbopack/crates/turbo-tasks/src/effect.rs

Lines changed: 103 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
use std::{borrow::Cow, future::Future, mem::replace, panic, pin::Pin};
1+
use std::{
2+
any::{Any, TypeId},
3+
borrow::Cow,
4+
future::Future,
5+
mem::replace,
6+
panic,
7+
pin::Pin,
8+
sync::Arc,
9+
};
210

311
use anyhow::{anyhow, Result};
412
use auto_hash_map::AutoSet;
513
use futures::{StreamExt, TryStreamExt};
614
use parking_lot::Mutex;
7-
use rustc_hash::FxHashSet;
15+
use rustc_hash::{FxHashMap, FxHashSet};
16+
use tokio::task_local;
817
use tracing::{Instrument, Span};
918

1019
use crate::{
@@ -90,10 +99,10 @@ impl EffectInstance {
9099
listener.await;
91100
}
92101
State::NotStarted(EffectInner { future }) => {
93-
let join_handle = tokio::spawn(
102+
let join_handle = tokio::spawn(ApplyEffectsContext::in_current_scope(
94103
turbo_tasks_future_scope(turbo_tasks::turbo_tasks(), future)
95104
.instrument(Span::current()),
96-
);
105+
));
97106
let result = match join_handle.await {
98107
Ok(Err(err)) => Err(SharedError::new(err)),
99108
Err(err) => {
@@ -170,20 +179,22 @@ pub async fn apply_effects(source: impl CollectiblesSource) -> Result<()> {
170179
return Ok(());
171180
}
172181
let span = tracing::info_span!("apply effects", count = effects.len());
173-
async move {
174-
// Limit the concurrency of effects
175-
futures::stream::iter(effects)
176-
.map(Ok)
177-
.try_for_each_concurrent(APPLY_EFFECTS_CONCURRENCY_LIMIT, async |effect| {
178-
let Some(effect) = ResolvedVc::try_downcast_type::<EffectInstance>(effect) else {
179-
panic!("Effect must only be implemented by EffectInstance");
180-
};
181-
effect.await?.apply().await
182-
})
183-
.await
184-
}
185-
.instrument(span)
186-
.await
182+
APPLY_EFFECTS_CONTEXT
183+
.scope(Default::default(), async move {
184+
// Limit the concurrency of effects
185+
futures::stream::iter(effects)
186+
.map(Ok)
187+
.try_for_each_concurrent(APPLY_EFFECTS_CONCURRENCY_LIMIT, async |effect| {
188+
let Some(effect) = ResolvedVc::try_downcast_type::<EffectInstance>(effect)
189+
else {
190+
panic!("Effect must only be implemented by EffectInstance");
191+
};
192+
effect.await?.apply().await
193+
})
194+
.await
195+
})
196+
.instrument(span)
197+
.await
187198
}
188199

189200
/// Capture effects from an turbo-tasks operation. Since this captures collectibles it might
@@ -252,17 +263,81 @@ impl Effects {
252263
/// Applies all effects that have been captured by this struct.
253264
pub async fn apply(&self) -> Result<()> {
254265
let span = tracing::info_span!("apply effects", count = self.effects.len());
255-
async move {
256-
// Limit the concurrency of effects
257-
futures::stream::iter(self.effects.iter())
258-
.map(Ok)
259-
.try_for_each_concurrent(APPLY_EFFECTS_CONCURRENCY_LIMIT, async |effect| {
260-
effect.apply().await
266+
APPLY_EFFECTS_CONTEXT
267+
.scope(Default::default(), async move {
268+
// Limit the concurrency of effects
269+
futures::stream::iter(self.effects.iter())
270+
.map(Ok)
271+
.try_for_each_concurrent(APPLY_EFFECTS_CONCURRENCY_LIMIT, async |effect| {
272+
effect.apply().await
273+
})
274+
.await
275+
})
276+
.instrument(span)
277+
.await
278+
}
279+
}
280+
281+
task_local! {
282+
/// The context of the current effects application.
283+
static APPLY_EFFECTS_CONTEXT: Arc<Mutex<ApplyEffectsContext>>;
284+
}
285+
286+
#[derive(Default)]
287+
pub struct ApplyEffectsContext {
288+
data: FxHashMap<TypeId, Box<dyn Any + Send + Sync>>,
289+
}
290+
291+
impl ApplyEffectsContext {
292+
fn in_current_scope<F: Future>(f: F) -> impl Future<Output = F::Output> {
293+
let current = Self::current();
294+
APPLY_EFFECTS_CONTEXT.scope(current, f)
295+
}
296+
297+
fn current() -> Arc<Mutex<Self>> {
298+
APPLY_EFFECTS_CONTEXT
299+
.try_with(|mutex| mutex.clone())
300+
.expect("No effect context found")
301+
}
302+
303+
fn with_context<T, F: FnOnce(&mut Self) -> T>(f: F) -> T {
304+
APPLY_EFFECTS_CONTEXT
305+
.try_with(|mutex| f(&mut mutex.lock()))
306+
.expect("No effect context found")
307+
}
308+
309+
pub fn set<T: Any + Send + Sync>(value: T) {
310+
Self::with_context(|this| {
311+
this.data.insert(TypeId::of::<T>(), Box::new(value));
312+
})
313+
}
314+
315+
pub fn with<T: Any + Send + Sync, R>(f: impl FnOnce(&mut T) -> R) -> Option<R> {
316+
Self::with_context(|this| {
317+
this.data
318+
.get_mut(&TypeId::of::<T>())
319+
.map(|value| {
320+
// Safety: the map is keyed by TypeId
321+
unsafe { value.downcast_mut_unchecked() }
261322
})
262-
.await
263-
}
264-
.instrument(span)
265-
.await
323+
.map(f)
324+
})
325+
}
326+
327+
pub fn with_or_insert_with<T: Any + Send + Sync, R>(
328+
insert_with: impl FnOnce() -> T,
329+
f: impl FnOnce(&mut T) -> R,
330+
) -> R {
331+
Self::with_context(|this| {
332+
let value = this.data.entry(TypeId::of::<T>()).or_insert_with(|| {
333+
let value = insert_with();
334+
Box::new(value)
335+
});
336+
f(
337+
// Safety: the map is keyed by TypeId
338+
unsafe { value.downcast_mut_unchecked() },
339+
)
340+
})
266341
}
267342
}
268343

turbopack/crates/turbo-tasks/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#![feature(arbitrary_self_types_pointers)]
3737
#![feature(new_zeroed_alloc)]
3838
#![feature(never_type)]
39+
#![feature(downcast_unchecked)]
3940

4041
pub mod backend;
4142
mod capture_future;
@@ -89,7 +90,7 @@ use auto_hash_map::AutoSet;
8990
pub use collectibles::CollectiblesSource;
9091
pub use completion::{Completion, Completions};
9192
pub use display::ValueToString;
92-
pub use effect::{apply_effects, effect, get_effects, Effects};
93+
pub use effect::{apply_effects, effect, get_effects, ApplyEffectsContext, Effects};
9394
pub use id::{
9495
ExecutionId, FunctionId, LocalTaskId, SessionId, TaskId, TraitTypeId, ValueTypeId,
9596
TRANSIENT_TASK_BIT,

0 commit comments

Comments
 (0)