Skip to content

Commit d009d19

Browse files
committed
Turbopack: fix race condition when adding dependencies
1 parent 7896298 commit d009d19

File tree

2 files changed

+53
-26
lines changed
  • turbopack/crates/turbo-tasks-backend/src/backend

2 files changed

+53
-26
lines changed

turbopack/crates/turbo-tasks-backend/src/backend/mod.rs

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,19 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
464464
self.assert_not_persistent_calling_transient(reader, task_id, /* cell_id */ None);
465465

466466
let mut ctx = self.execute_context(turbo_tasks);
467-
let mut task = ctx.task(task_id, TaskDataCategory::All);
467+
let (mut task, reader_task) = if self.should_track_dependencies()
468+
&& !matches!(options.tracking, ReadTracking::Untracked)
469+
&& let Some(reader_id) = reader
470+
&& reader_id != task_id
471+
{
472+
// Having a task_pair here is not optimal, but otherwise this would lead to a race
473+
// condition. See below.
474+
// TODO(sokra): solve that in a more performant way.
475+
let (task, reader) = ctx.task_pair(task_id, reader_id, TaskDataCategory::All);
476+
(task, Some(reader))
477+
} else {
478+
(ctx.task(task_id, TaskDataCategory::All), None)
479+
};
468480

469481
fn listen_to_done_event<B: BackingStorage>(
470482
this: &TurboTasksBackendInner<B>,
@@ -707,18 +719,22 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
707719
)))
708720
}
709721
};
710-
if self.should_track_dependencies()
711-
&& let Some(reader) = reader
722+
if let Some(mut reader_task) = reader_task
712723
&& options.tracking.should_track(result.is_err())
713724
&& (!task.is_immutable() || cfg!(feature = "verify_immutable"))
714725
{
726+
let reader = reader.unwrap();
715727
let _ = task.add(CachedDataItem::OutputDependent {
716728
task: reader,
717729
value: (),
718730
});
719731
drop(task);
720732

721-
let mut reader_task = ctx.task(reader, TaskDataCategory::Data);
733+
// Note that there is a potential race condition here, if the reader task would be
734+
// locked after the task is unlocked. The output might be invalidated at
735+
// this point, but it wouldn't find the backward dependency or see an outdated
736+
// dependency. This way it would loose an invalidation.
737+
722738
if reader_task
723739
.remove(&CachedDataItemKey::OutdatedOutputDependency { target: task_id })
724740
.is_none()
@@ -732,6 +748,7 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
732748

733749
return result;
734750
}
751+
drop(reader_task);
735752

736753
let note = move || {
737754
let reader_desc = reader.map(|r| self.get_task_desc_fn(r));
@@ -768,29 +785,23 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
768785
) -> Result<Result<TypedCellContent, EventListener>> {
769786
self.assert_not_persistent_calling_transient(reader, task_id, Some(cell));
770787

771-
fn add_cell_dependency<B: BackingStorage>(
772-
backend: &TurboTasksBackendInner<B>,
788+
fn add_cell_dependency(
789+
task_id: TaskId,
773790
mut task: impl TaskGuard,
774791
reader: Option<TaskId>,
792+
reader_task: Option<impl TaskGuard>,
775793
cell: CellId,
776-
task_id: TaskId,
777-
ctx: &mut impl ExecuteContext<'_>,
778794
) {
779-
if backend.should_track_dependencies()
780-
&& let Some(reader) = reader
781-
// We never want to have a dependency on ourselves, otherwise we end up in a
782-
// loop of re-executing the same task.
783-
&& reader != task_id
795+
if let Some(mut reader_task) = reader_task
784796
&& (!task.is_immutable() || cfg!(feature = "verify_immutable"))
785797
{
786798
let _ = task.add(CachedDataItem::CellDependent {
787799
cell,
788-
task: reader,
800+
task: reader.unwrap(),
789801
value: (),
790802
});
791803
drop(task);
792804

793-
let mut reader_task = ctx.task(reader, TaskDataCategory::Data);
794805
let target = CellRef {
795806
task: task_id,
796807
cell,
@@ -805,7 +816,20 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
805816
}
806817

807818
let mut ctx = self.execute_context(turbo_tasks);
808-
let mut task = ctx.task(task_id, TaskDataCategory::Data);
819+
let (mut task, reader_task) = if self.should_track_dependencies()
820+
&& matches!(options.tracking, ReadTracking::Tracked)
821+
&& let Some(reader_id) = reader
822+
&& reader_id != task_id
823+
{
824+
// Having a task_pair here is not optimal, but otherwise this would lead to a race
825+
// condition. See below.
826+
// TODO(sokra): solve that in a more performant way.
827+
let (task, reader) = ctx.task_pair(task_id, reader_id, TaskDataCategory::Data);
828+
(task, Some(reader))
829+
} else {
830+
(ctx.task(task_id, TaskDataCategory::Data), None)
831+
};
832+
809833
let content = if options.final_read_hint {
810834
remove!(task, CellData { cell })
811835
} else if let Some(content) = get!(task, CellData { cell }) {
@@ -816,7 +840,7 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
816840
};
817841
if let Some(content) = content {
818842
if options.tracking.should_track(false) {
819-
add_cell_dependency(self, task, reader, cell, task_id, &mut ctx);
843+
add_cell_dependency(task_id, task, reader, reader_task, cell);
820844
}
821845
return Ok(Ok(TypedCellContent(
822846
cell.type_id,
@@ -843,7 +867,7 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
843867
.copied();
844868
let Some(max_id) = max_id else {
845869
if options.tracking.should_track(true) {
846-
add_cell_dependency(self, task, reader, cell, task_id, &mut ctx);
870+
add_cell_dependency(task_id, task, reader, reader_task, cell);
847871
}
848872
bail!(
849873
"Cell {cell:?} no longer exists in task {} (no cell of this type exists)",
@@ -852,7 +876,7 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
852876
};
853877
if cell.index >= max_id {
854878
if options.tracking.should_track(true) {
855-
add_cell_dependency(self, task, reader, cell, task_id, &mut ctx);
879+
add_cell_dependency(task_id, task, reader, reader_task, cell);
856880
}
857881
bail!(
858882
"Cell {cell:?} no longer exists in task {} (index out of bounds)",

turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,21 @@ enum TransactionState<'a, 'tx, B: BackingStorage> {
4747
}
4848

4949
pub trait ExecuteContext<'e>: Sized {
50+
type TaskGuardImpl: TaskGuard + 'e;
5051
fn child_context<'l, 'r>(&'r self) -> impl ChildExecuteContext<'l> + use<'e, 'l, Self>
5152
where
5253
'e: 'l;
5354
fn session_id(&self) -> SessionId;
54-
fn task(&mut self, task_id: TaskId, category: TaskDataCategory) -> impl TaskGuard + 'e;
55+
fn task(&mut self, task_id: TaskId, category: TaskDataCategory) -> Self::TaskGuardImpl;
5556
fn is_once_task(&self, task_id: TaskId) -> bool;
5657
fn task_pair(
5758
&mut self,
5859
task_id1: TaskId,
5960
task_id2: TaskId,
6061
category: TaskDataCategory,
61-
) -> (impl TaskGuard + 'e, impl TaskGuard + 'e);
62+
) -> (Self::TaskGuardImpl, Self::TaskGuardImpl);
6263
fn schedule(&mut self, task_id: TaskId);
63-
fn schedule_task(&self, task: impl TaskGuard + '_);
64+
fn schedule_task(&self, task: Self::TaskGuardImpl);
6465
fn operation_suspend_point<T>(&mut self, op: &T)
6566
where
6667
T: Clone + Into<AnyOperation>;
@@ -162,6 +163,8 @@ impl<'e, 'tx, B: BackingStorage> ExecuteContext<'e> for ExecuteContextImpl<'e, '
162163
where
163164
'tx: 'e,
164165
{
166+
type TaskGuardImpl = TaskGuardImpl<'e, B>;
167+
165168
fn child_context<'l, 'r>(&'r self) -> impl ChildExecuteContext<'l> + use<'e, 'tx, 'l, B>
166169
where
167170
'e: 'l,
@@ -176,7 +179,7 @@ where
176179
self.backend.session_id()
177180
}
178181

179-
fn task(&mut self, task_id: TaskId, category: TaskDataCategory) -> impl TaskGuard + 'e {
182+
fn task(&mut self, task_id: TaskId, category: TaskDataCategory) -> Self::TaskGuardImpl {
180183
let mut task = self.backend.storage.access_mut(task_id);
181184
if !task.state().is_restored(category) {
182185
if task_id.is_transient() {
@@ -224,7 +227,7 @@ where
224227
task_id1: TaskId,
225228
task_id2: TaskId,
226229
category: TaskDataCategory,
227-
) -> (impl TaskGuard + 'e, impl TaskGuard + 'e) {
230+
) -> (Self::TaskGuardImpl, Self::TaskGuardImpl) {
228231
let (mut task1, mut task2) = self.backend.storage.access_pair_mut(task_id1, task_id2);
229232
let is_restored1 = task1.state().is_restored(category);
230233
let is_restored2 = task2.state().is_restored(category);
@@ -277,7 +280,7 @@ where
277280
self.schedule_task(task);
278281
}
279282

280-
fn schedule_task(&self, mut task: impl TaskGuard + '_) {
283+
fn schedule_task(&self, mut task: Self::TaskGuardImpl) {
281284
if let Some(tasks_to_prefetch) = task.prefetch() {
282285
self.turbo_tasks
283286
.schedule_backend_background_job(TurboTasksBackendJob::Prefetch {
@@ -367,7 +370,7 @@ pub trait TaskGuard: Debug {
367370
fn is_immutable(&self) -> bool;
368371
}
369372

370-
struct TaskGuardImpl<'a, B: BackingStorage> {
373+
pub struct TaskGuardImpl<'a, B: BackingStorage> {
371374
task_id: TaskId,
372375
task: StorageWriteGuard<'a>,
373376
backend: &'a TurboTasksBackendInner<B>,

0 commit comments

Comments
 (0)