@@ -2,7 +2,7 @@ use std::mem::take;
2
2
3
3
use serde:: { Deserialize , Serialize } ;
4
4
use smallvec:: SmallVec ;
5
- use turbo_tasks:: { CellId , TaskId , backend:: CellContent } ;
5
+ use turbo_tasks:: { CellId , TaskId , TypedSharedReference , backend:: CellContent } ;
6
6
7
7
#[ cfg( feature = "trace_task_dirty" ) ]
8
8
use crate :: backend:: operation:: invalidate:: TaskDirtyCause ;
@@ -24,6 +24,12 @@ pub enum UpdateCellOperation {
24
24
InvalidateWhenCellDependency {
25
25
cell_ref : CellRef ,
26
26
dependent_tasks : SmallVec < [ TaskId ; 4 ] > ,
27
+ content : Option < TypedSharedReference > ,
28
+ queue : AggregationUpdateQueue ,
29
+ } ,
30
+ FinalCellChange {
31
+ cell_ref : CellRef ,
32
+ content : Option < TypedSharedReference > ,
27
33
queue : AggregationUpdateQueue ,
28
34
} ,
29
35
AggregationUpdate {
@@ -36,51 +42,82 @@ pub enum UpdateCellOperation {
36
42
impl UpdateCellOperation {
37
43
pub fn run ( task_id : TaskId , cell : CellId , content : CellContent , mut ctx : impl ExecuteContext ) {
38
44
let mut task = ctx. task ( task_id, TaskDataCategory :: All ) ;
39
- let old_content = if let CellContent ( Some ( new_content) ) = content {
40
- task. insert ( CachedDataItem :: CellData {
41
- cell,
42
- value : new_content. into_typed ( cell. type_id ) ,
43
- } )
44
- } else {
45
- task. remove ( & CachedDataItemKey :: CellData { cell } )
46
- } ;
47
-
48
- if let Some ( in_progress) = remove ! ( task, InProgressCell { cell } ) {
49
- in_progress. event . notify ( usize:: MAX ) ;
50
- }
51
45
52
46
// We need to detect recomputation, because here the content has not actually changed (even
53
47
// if it's not equal to the old content, as not all values implement Eq). We have to
54
48
// assume that tasks are deterministic and pure.
49
+ let should_invalidate = ctx. should_track_dependencies ( )
50
+ && ( task. has_key ( & CachedDataItemKey :: Dirty { } ) ||
51
+ // This is a hack for the streaming hack. Stateful tasks are never recomputed, so this forces invalidation for them in case of this hack.
52
+ task. has_key ( & CachedDataItemKey :: Stateful { } ) ) ;
55
53
56
- if ctx. should_track_dependencies ( )
57
- && ( task. has_key ( & CachedDataItemKey :: Dirty { } )
58
- ||
59
- // This is a hack for the streaming hack. Stateful tasks are never recomputed, so this forces invalidation for them in case of this hack.
60
- task. has_key ( & CachedDataItemKey :: Stateful { } ) )
61
- {
62
- let dependent_tasks = get_many ! (
54
+ if should_invalidate {
55
+ let dependent_tasks: SmallVec < [ TaskId ; 4 ] > = get_many ! (
63
56
task,
64
57
CellDependent { cell: dependent_cell, task }
65
58
if dependent_cell == cell
66
59
=> task
67
60
) ;
68
61
69
- drop ( task) ;
70
- drop ( old_content) ;
62
+ if !dependent_tasks. is_empty ( ) {
63
+ // Slow path: We need to invalidate tasks depending on this cell.
64
+ // To avoid a race condition, we need to remove the old content first,
65
+ // then invalidate dependent tasks and only then update the cell content.
66
+
67
+ // The reason behind this is that we consider tasks that haven't the dirty flag set
68
+ // as "recomputing" tasks. Recomputing tasks won't invalidate
69
+ // dependent tasks, when a cell is changed. This would cause missing invalidating if
70
+ // a task is recomputing while a dependency is in the middle of a cell update (where
71
+ // the value has been changed, but the dependent tasks have not be flagged dirty
72
+ // yet). So to avoid that we first remove the cell content, invalidate all dependent
73
+ // tasks and after that set the new cell content. When the cell content is unset,
74
+ // readers will wait for it to be set via InProgressCell.
75
+
76
+ let old_content = task. remove ( & CachedDataItemKey :: CellData { cell } ) ;
77
+
78
+ drop ( task) ;
79
+ drop ( old_content) ;
71
80
72
- UpdateCellOperation :: InvalidateWhenCellDependency {
73
- cell_ref : CellRef {
74
- task : task_id,
75
- cell,
76
- } ,
77
- dependent_tasks,
78
- queue : AggregationUpdateQueue :: new ( ) ,
81
+ let content = if let CellContent ( Some ( new_content) ) = content {
82
+ Some ( new_content. into_typed ( cell. type_id ) )
83
+ } else {
84
+ None
85
+ } ;
86
+
87
+ UpdateCellOperation :: InvalidateWhenCellDependency {
88
+ cell_ref : CellRef {
89
+ task : task_id,
90
+ cell,
91
+ } ,
92
+ dependent_tasks,
93
+ content,
94
+ queue : AggregationUpdateQueue :: new ( ) ,
95
+ }
96
+ . execute ( & mut ctx) ;
97
+ return ;
79
98
}
80
- . execute ( & mut ctx) ;
99
+ }
100
+
101
+ // Fast path: We don't need to invalidate anything.
102
+ // So we can just update the cell content.
103
+
104
+ let old_content = if let CellContent ( Some ( new_content) ) = content {
105
+ let new_content = new_content. into_typed ( cell. type_id ) ;
106
+ task. insert ( CachedDataItem :: CellData {
107
+ cell,
108
+ value : new_content,
109
+ } )
81
110
} else {
82
- drop ( task) ;
83
- drop ( old_content) ;
111
+ task. remove ( & CachedDataItemKey :: CellData { cell } )
112
+ } ;
113
+
114
+ let in_progress_cell = remove ! ( task, InProgressCell { cell } ) ;
115
+
116
+ drop ( task) ;
117
+ drop ( old_content) ;
118
+
119
+ if let Some ( in_progress) = in_progress_cell {
120
+ in_progress. event . notify ( usize:: MAX ) ;
84
121
}
85
122
}
86
123
}
@@ -93,6 +130,7 @@ impl Operation for UpdateCellOperation {
93
130
UpdateCellOperation :: InvalidateWhenCellDependency {
94
131
cell_ref,
95
132
ref mut dependent_tasks,
133
+ ref mut content,
96
134
ref mut queue,
97
135
} => {
98
136
if let Some ( dependent_task_id) = dependent_tasks. pop ( ) {
@@ -129,9 +167,37 @@ impl Operation for UpdateCellOperation {
129
167
) ;
130
168
}
131
169
if dependent_tasks. is_empty ( ) {
132
- self = UpdateCellOperation :: AggregationUpdate { queue : take ( queue) } ;
170
+ self = UpdateCellOperation :: FinalCellChange {
171
+ cell_ref,
172
+ content : take ( content) ,
173
+ queue : take ( queue) ,
174
+ } ;
133
175
}
134
176
}
177
+ UpdateCellOperation :: FinalCellChange {
178
+ cell_ref : CellRef { task, cell } ,
179
+ content,
180
+ ref mut queue,
181
+ } => {
182
+ let mut task = ctx. task ( task, TaskDataCategory :: Data ) ;
183
+
184
+ if let Some ( content) = content {
185
+ task. add_new ( CachedDataItem :: CellData {
186
+ cell,
187
+ value : content,
188
+ } )
189
+ }
190
+
191
+ let in_progress_cell = remove ! ( task, InProgressCell { cell } ) ;
192
+
193
+ drop ( task) ;
194
+
195
+ if let Some ( in_progress) = in_progress_cell {
196
+ in_progress. event . notify ( usize:: MAX ) ;
197
+ }
198
+
199
+ self = UpdateCellOperation :: AggregationUpdate { queue : take ( queue) } ;
200
+ }
135
201
UpdateCellOperation :: AggregationUpdate { ref mut queue } => {
136
202
if queue. process ( ctx) {
137
203
self = UpdateCellOperation :: Done
0 commit comments