@@ -5,7 +5,7 @@ use crate::function::{Configuration, IngredientImpl};
5
5
use crate :: sync:: atomic:: { AtomicBool , Ordering } ;
6
6
use crate :: tracked_struct:: Identity ;
7
7
use crate :: zalsa:: { MemoIngredientIndex , Zalsa , ZalsaDatabase } ;
8
- use crate :: zalsa_local:: ActiveQueryGuard ;
8
+ use crate :: zalsa_local:: { ActiveQueryGuard , QueryRevisions } ;
9
9
use crate :: { Event , EventKind , Id } ;
10
10
11
11
impl < C > IngredientImpl < C >
@@ -141,6 +141,7 @@ where
141
141
// only when a cycle is actually encountered.
142
142
let mut opt_last_provisional: Option < & Memo < ' db , C > > = None ;
143
143
let mut last_stale_tracked_ids: Vec < ( Identity , Id ) > = Vec :: new ( ) ;
144
+ let _guard = ClearCycleHeadIfPanicking :: new ( self , zalsa, id, memo_ingredient_index) ;
144
145
145
146
loop {
146
147
let previous_memo = opt_last_provisional. or ( opt_old_memo) ;
@@ -210,6 +211,9 @@ where
210
211
// `iteration_count` can't overflow as we check it against `MAX_ITERATIONS`
211
212
// which is less than `u32::MAX`.
212
213
iteration_count = iteration_count. increment ( ) . unwrap_or_else ( || {
214
+ tracing:: warn!(
215
+ "{database_key_index:?}: execute: too many cycle iterations"
216
+ ) ;
213
217
panic ! ( "{database_key_index:?}: execute: too many cycle iterations" )
214
218
} ) ;
215
219
zalsa. event ( & || {
@@ -222,10 +226,7 @@ where
222
226
completed_query
223
227
. revisions
224
228
. 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..." , ) ;
229
230
opt_last_provisional = Some ( self . insert_memo (
230
231
zalsa,
231
232
id,
@@ -297,3 +298,55 @@ where
297
298
( new_value, active_query. pop ( ) )
298
299
}
299
300
}
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