@@ -5,7 +5,7 @@ use crate::function::{Configuration, IngredientImpl};
55use crate :: sync:: atomic:: { AtomicBool , Ordering } ;
66use crate :: tracked_struct:: Identity ;
77use crate :: zalsa:: { MemoIngredientIndex , Zalsa , ZalsaDatabase } ;
8- use crate :: zalsa_local:: ActiveQueryGuard ;
8+ use crate :: zalsa_local:: { ActiveQueryGuard , QueryRevisions } ;
99use crate :: { Event , EventKind , Id } ;
1010
1111impl < C > IngredientImpl < C >
@@ -141,6 +141,7 @@ where
141141 // only when a cycle is actually encountered.
142142 let mut opt_last_provisional: Option < & Memo < ' db , C > > = None ;
143143 let mut last_stale_tracked_ids: Vec < ( Identity , Id ) > = Vec :: new ( ) ;
144+ let _guard = ClearCycleHeadIfPanicking :: new ( self , zalsa, id, memo_ingredient_index) ;
144145
145146 loop {
146147 let previous_memo = opt_last_provisional. or ( opt_old_memo) ;
@@ -210,6 +211,9 @@ where
210211 // `iteration_count` can't overflow as we check it against `MAX_ITERATIONS`
211212 // which is less than `u32::MAX`.
212213 iteration_count = iteration_count. increment ( ) . unwrap_or_else ( || {
214+ tracing:: warn!(
215+ "{database_key_index:?}: execute: too many cycle iterations"
216+ ) ;
213217 panic ! ( "{database_key_index:?}: execute: too many cycle iterations" )
214218 } ) ;
215219 zalsa. event ( & || {
@@ -222,10 +226,7 @@ where
222226 completed_query
223227 . revisions
224228 . update_iteration_count ( iteration_count) ;
225- crate :: tracing:: debug!(
226- "{database_key_index:?}: execute: iterate again, revisions: {revisions:#?}" ,
227- revisions = & completed_query. revisions
228- ) ;
229+ crate :: tracing:: info!( "{database_key_index:?}: execute: iterate again..." , ) ;
229230 opt_last_provisional = Some ( self . insert_memo (
230231 zalsa,
231232 id,
@@ -297,3 +298,55 @@ where
297298 ( new_value, active_query. pop ( ) )
298299 }
299300}
301+
302+ /// Replaces any inserted memo with a fixpoint initial memo without a value if the current thread panics.
303+ ///
304+ /// A regular query doesn't insert any memo if it panics and the query
305+ /// simply gets re-executed if any later called query depends on the panicked query (and will panic again unless the query isn't deterministic).
306+ ///
307+ /// Unfortunately, this isn't the case for cycle heads because Salsa first inserts the fixpoint initial memo and later inserts
308+ /// provisional memos for every iteration. Detecting whether a query has previously panicked
309+ /// in `fetch` (e.g., `validate_same_iteration`) and requires re-execution is probably possible but not very straightforward
310+ /// and it's easy to get it wrong, which results in infinite loops where `Memo::provisional_retry` keeps retrying to get the latest `Memo`
311+ /// but `fetch` doesn't re-execute the query for reasons.
312+ ///
313+ /// Specifically, a Memo can linger after a panic, which is then incorrectly returned
314+ /// by `fetch_cold_cycle` because it passes the `shallow_verified_memo` check instead of inserting
315+ /// a new fix point initial value if that happens.
316+ ///
317+ /// We could insert a fixpoint initial value here, but it seems unnecessary.
318+ struct ClearCycleHeadIfPanicking < ' a , C : Configuration > {
319+ ingredient : & ' a IngredientImpl < C > ,
320+ zalsa : & ' a Zalsa ,
321+ id : Id ,
322+ memo_ingredient_index : MemoIngredientIndex ,
323+ }
324+
325+ impl < ' a , C : Configuration > ClearCycleHeadIfPanicking < ' a , C > {
326+ fn new (
327+ ingredient : & ' a IngredientImpl < C > ,
328+ zalsa : & ' a Zalsa ,
329+ id : Id ,
330+ memo_ingredient_index : MemoIngredientIndex ,
331+ ) -> Self {
332+ Self {
333+ ingredient,
334+ zalsa,
335+ id,
336+ memo_ingredient_index,
337+ }
338+ }
339+ }
340+
341+ impl < C : Configuration > Drop for ClearCycleHeadIfPanicking < ' _ , C > {
342+ fn drop ( & mut self ) {
343+ if std:: thread:: panicking ( ) {
344+ let revisions =
345+ QueryRevisions :: fixpoint_initial ( self . ingredient . database_key_index ( self . id ) ) ;
346+
347+ let memo = Memo :: new ( None , self . zalsa . current_revision ( ) , revisions) ;
348+ self . ingredient
349+ . insert_memo ( self . zalsa , self . id , memo, self . memo_ingredient_index ) ;
350+ }
351+ }
352+ }
0 commit comments