@@ -170,7 +170,7 @@ where
170170
171171 // Our provisional value from the previous iteration, when doing fixpoint iteration.
172172 // This is different from `opt_old_memo` which might be from a different revision.
173- let mut last_provisional_memo : Option < & Memo < ' db , C > > = None ;
173+ let mut last_provisional_memo_opt : Option < & Memo < ' db , C > > = None ;
174174
175175 // TODO: Can we seed those somehow?
176176 let mut last_stale_tracked_ids: Vec < ( Identity , Id ) > = Vec :: new ( ) ;
@@ -194,7 +194,7 @@ where
194194 // Only use the last provisional memo if it was a cycle head in the last iteration. This is to
195195 // force at least two executions.
196196 if old_memo. cycle_heads ( ) . contains ( & database_key_index) {
197- last_provisional_memo = Some ( old_memo) ;
197+ last_provisional_memo_opt = Some ( old_memo) ;
198198 }
199199
200200 iteration_count = old_memo. revisions . iteration ( ) ;
@@ -219,7 +219,7 @@ where
219219 db,
220220 zalsa,
221221 active_query,
222- last_provisional_memo . or ( opt_old_memo) ,
222+ last_provisional_memo_opt . or ( opt_old_memo) ,
223223 ) ;
224224
225225 // Take the cycle heads to not-fight-rust's-borrow-checker.
@@ -329,10 +329,7 @@ where
329329
330330 // Get the last provisional value for this query so that we can compare it with the new value
331331 // to test if the cycle converged.
332- let last_provisional_value = if let Some ( last_provisional) = last_provisional_memo {
333- // We have a last provisional value from our previous time around the loop.
334- last_provisional. value . as_ref ( )
335- } else {
332+ let last_provisional_memo = last_provisional_memo_opt. unwrap_or_else ( || {
336333 // This is our first time around the loop; a provisional value must have been
337334 // inserted into the memo table when the cycle was hit, so let's pull our
338335 // initial provisional value from there.
@@ -346,8 +343,10 @@ where
346343 } ) ;
347344
348345 debug_assert ! ( memo. may_be_provisional( ) ) ;
349- memo. value . as_ref ( )
350- } ;
346+ memo
347+ } ) ;
348+
349+ let last_provisional_value = last_provisional_memo. value . as_ref ( ) ;
351350
352351 let last_provisional_value = last_provisional_value. expect (
353352 "`fetch_cold_cycle` should have inserted a provisional memo with Cycle::initial" ,
@@ -393,9 +392,24 @@ where
393392 }
394393 }
395394
396- let this_converged = C :: values_equal ( & new_value, last_provisional_value) ;
397395 let mut completed_query = active_query. pop ( ) ;
398396
397+ let value_converged = C :: values_equal ( & new_value, last_provisional_value) ;
398+
399+ // It's important to force a re-execution of the cycle if `changed_at` or `durability` has changed
400+ // to ensure the reduced durability and changed propagates to all queries depending on this head.
401+ let metadata_converged = last_provisional_memo. revisions . durability
402+ == completed_query. revisions . durability
403+ && last_provisional_memo. revisions . changed_at
404+ == completed_query. revisions . changed_at
405+ && last_provisional_memo
406+ . revisions
407+ . origin
408+ . is_derived_untracked ( )
409+ == completed_query. revisions . origin . is_derived_untracked ( ) ;
410+
411+ let this_converged = value_converged && metadata_converged;
412+
399413 if let Some ( outer_cycle) = outer_cycle {
400414 tracing:: info!(
401415 "Detected nested cycle {database_key_index:?}, iterate it as part of the outer cycle {outer_cycle:?}"
@@ -498,7 +512,7 @@ where
498512 memo_ingredient_index,
499513 ) ;
500514
501- last_provisional_memo = Some ( new_memo) ;
515+ last_provisional_memo_opt = Some ( new_memo) ;
502516
503517 last_stale_tracked_ids = completed_query. stale_tracked_structs ;
504518
0 commit comments