@@ -160,11 +160,14 @@ where
160
160
let mut iteration_count = IterationCount :: initial ( ) ;
161
161
162
162
if let Some ( old_memo) = opt_old_memo {
163
+ let memo_iteration_count = old_memo. revisions . iteration ( ) ;
164
+
163
165
if old_memo. verified_at . load ( ) == zalsa. current_revision ( )
164
166
&& old_memo. cycle_heads ( ) . contains ( & database_key_index)
167
+ && !memo_iteration_count. is_panicked ( )
165
168
{
166
169
previous_memo = Some ( old_memo) ;
167
- iteration_count = old_memo . revisions . iteration ( ) ;
170
+ iteration_count = memo_iteration_count ;
168
171
}
169
172
}
170
173
@@ -185,44 +188,55 @@ where
185
188
186
189
// If there are no cycle heads, break out of the loop (`cycle_heads_mut` returns `None` if the cycle head list is empty)
187
190
let Some ( cycle_heads) = completed_query. revisions . cycle_heads_mut ( ) else {
191
+ claim_guard. set_release_mode ( ReleaseMode :: SelfOnly ) ;
188
192
break ( new_value, completed_query) ;
189
193
} ;
190
194
191
- // TODO: Remove "removed" cycle heads"
192
195
let mut cycle_heads = std:: mem:: take ( cycle_heads) ;
196
+ let mut missing_heads: SmallVec < [ ( DatabaseKeyIndex , IterationCount ) ; 1 ] > =
197
+ SmallVec :: new_const ( ) ;
198
+ let mut max_iteration_count = iteration_count;
193
199
194
- // Recursively resolve all cycle heads that this head depends on.
195
- // This isn't required in a single-threaded execution but it's not guaranteed that all nested cycles are listed
196
- // in cycle heads in a multi-threaded execution:
200
+ // Ensure that we resolve the latest cycle heads from any provisional value this query depended on during execution .
201
+ // This isn't required in a single-threaded execution, but it's not guaranteed that `cycle_heads` contains all cycles
202
+ // in a multi-threaded execution:
197
203
//
198
204
// t1: a -> b
199
205
// t2: c -> b (blocks on t1)
200
206
// t1: a -> b -> c (cycle, returns fixpoint initial with c(0) in heads)
201
207
// t1: a -> b (completes b, b has c(0) in its cycle heads, releases `b`, which resumes `t2`, and `retry_provisional` blocks on `c` (t2))
202
208
// t2: c -> a (cycle, returns fixpoint initial for a with a(0) in heads)
203
209
// t2: completes c, `provisional_retry` blocks on `a` (t2)
204
- // t1: a (complets `b` with `c` in heads)
210
+ // t1: a (completes `b` with `c` in heads)
205
211
//
206
212
// Note how `a` only depends on `c` but not `a`. This is because `a` only saw the initial value of `c` and wasn't updated when `c` completed.
207
213
// That's why we need to resolve the cycle heads recursively to `cycle_heads` contains all cycle heads at the moment this query completed.
208
- let mut queue: SmallVec < [ DatabaseKeyIndex ; 4 ] > = cycle_heads
209
- . iter ( )
210
- . map ( |head| head. database_key_index )
211
- . filter ( |head| * head != database_key_index)
212
- . collect ( ) ;
213
-
214
- // TODO: Can we also resolve whether the cycles have converged here?
215
- while let Some ( head) = queue. pop ( ) {
216
- let ingredient = zalsa. lookup_ingredient ( head. ingredient_index ( ) ) ;
217
- let nested_heads = ingredient. cycle_heads ( zalsa, head. key_index ( ) ) ;
218
-
219
- for head in nested_heads {
220
- if cycle_heads. insert ( head) && !queue. contains ( & head. database_key_index ) {
221
- queue. push ( head. database_key_index ) ;
214
+ for head in & cycle_heads {
215
+ let ingredient =
216
+ zalsa. lookup_ingredient ( head. database_key_index . ingredient_index ( ) ) ;
217
+ let nested_heads =
218
+ ingredient. cycle_heads ( zalsa, head. database_key_index . key_index ( ) ) ;
219
+
220
+ max_iteration_count = max_iteration_count. max ( head. iteration_count . load ( ) ) ;
221
+
222
+ for nested_head in nested_heads {
223
+ let nested_tuple = (
224
+ nested_head. database_key_index ,
225
+ nested_head. iteration_count . load ( ) ,
226
+ ) ;
227
+ if !cycle_heads. contains ( & nested_head. database_key_index )
228
+ && !missing_heads. contains ( & nested_tuple)
229
+ {
230
+ max_iteration_count = max_iteration_count. max ( nested_tuple. 1 ) ;
231
+ missing_heads. push ( nested_tuple) ;
222
232
}
223
233
}
224
234
}
225
235
236
+ for ( head_key, iteration_count) in missing_heads {
237
+ cycle_heads. insert ( head_key, iteration_count) ;
238
+ }
239
+
226
240
let outer_cycle = outer_cycle ( zalsa, zalsa_local, & cycle_heads, database_key_index) ;
227
241
228
242
// Did the new result we got depend on our own provisional value, in a cycle?
@@ -265,25 +279,14 @@ where
265
279
I am a cycle head, comparing last provisional value with new value"
266
280
) ;
267
281
268
- // determine if it is a nested query.
269
- // This is a nested query if it depends on any other cycle head than itself
270
- // where claiming it results in a cycle. In that case, both queries form a single connected component
271
- // that we can iterate together rather than having separate nested fixpoint iterations.
272
-
273
282
let this_converged = C :: values_equal ( & new_value, last_provisional_value) ;
274
283
275
284
iteration_count = if outer_cycle. is_some ( ) {
276
285
iteration_count
277
286
} else {
278
- cycle_heads
279
- . iter ( )
280
- . map ( |head| head. iteration_count . load ( ) )
281
- . max ( )
282
- . unwrap_or ( iteration_count)
287
+ max_iteration_count
283
288
} ;
284
289
285
- // If the new result is equal to the last provisional result, the cycle has
286
- // converged and we are done.
287
290
if !this_converged {
288
291
// We are in a cycle that hasn't converged; ask the user's
289
292
// cycle-recovery function what to do:
@@ -295,60 +298,53 @@ where
295
298
) {
296
299
crate :: CycleRecoveryAction :: Iterate => { }
297
300
crate :: CycleRecoveryAction :: Fallback ( fallback_value) => {
298
- crate :: tracing:: debug!(
301
+ tracing:: debug!(
299
302
"{database_key_index:?}: execute: user cycle_fn says to fall back"
300
303
) ;
301
304
new_value = fallback_value;
302
305
}
303
306
}
304
307
}
305
308
306
- completed_query
307
- . revisions
308
- . set_cycle_converged ( this_converged) ;
309
-
310
309
if let Some ( outer_cycle) = outer_cycle {
311
310
tracing:: debug!(
312
311
"Detected nested cycle {database_key_index:?}, iterate it as part of the outer cycle {outer_cycle:?}"
313
312
) ;
314
313
315
314
completed_query. revisions . set_cycle_heads ( cycle_heads) ;
315
+ // Store whether this cycle has converged, so that the outer cycle can check it.
316
+ completed_query
317
+ . revisions
318
+ . set_cycle_converged ( this_converged) ;
316
319
claim_guard. set_release_mode ( ReleaseMode :: TransferTo ( outer_cycle) ) ;
317
320
318
321
break ( new_value, completed_query) ;
319
322
}
320
323
321
- // Verify that all cycles have converged, including all inner cycles.
324
+ // Verify that this cycle and all inner cycles have converged .
322
325
let converged = this_converged
323
- && cycle_heads
324
- . iter ( )
325
- . filter ( |head| head. database_key_index != database_key_index)
326
- . all ( |head| {
327
- let ingredient =
328
- zalsa. lookup_ingredient ( head. database_key_index . ingredient_index ( ) ) ;
326
+ && cycle_heads. iter_skip_own ( database_key_index) . all ( |head| {
327
+ let ingredient =
328
+ zalsa. lookup_ingredient ( head. database_key_index . ingredient_index ( ) ) ;
329
329
330
- let converged =
331
- ingredient. cycle_converged ( zalsa, head. database_key_index . key_index ( ) ) ;
330
+ let converged =
331
+ ingredient. cycle_converged ( zalsa, head. database_key_index . key_index ( ) ) ;
332
332
333
- if !converged {
334
- tracing:: debug!( "inner cycle {database_key_index:?} has not converged" ) ;
335
- }
333
+ if !converged {
334
+ tracing:: debug!( "inner cycle {database_key_index:?} has not converged" ) ;
335
+ }
336
336
337
- converged
338
- } ) ;
337
+ converged
338
+ } ) ;
339
339
340
340
if converged {
341
- crate :: tracing:: debug!(
342
- "{database_key_index:?}: execute: fixpoint iteration has a final value after {iteration_count:?} iterations"
343
- ) ;
341
+ tracing:: debug!(
342
+ "{database_key_index:?}: execute: fixpoint iteration has a final value after {iteration_count:?} iterations"
343
+ ) ;
344
344
345
345
// Set the nested cycles as verified. This is necessary because
346
346
// `validate_provisional` doesn't follow cycle heads recursively (and the inner memos now depend on all cycle heads).
347
- for head in cycle_heads {
348
- if head. database_key_index == database_key_index {
349
- continue ;
350
- }
351
-
347
+ for head in cycle_heads. iter_skip_own ( database_key_index) {
352
348
let ingredient =
353
349
zalsa. lookup_ingredient ( head. database_key_index . ingredient_index ( ) ) ;
354
350
ingredient. finalize_cycle_head ( zalsa, head. database_key_index . key_index ( ) ) ;
@@ -359,10 +355,7 @@ where
359
355
break ( new_value, completed_query) ;
360
356
}
361
357
362
- completed_query. revisions . set_cycle_heads ( cycle_heads) ;
363
-
364
- // `iteration_count` can't overflow as we check it against `MAX_ITERATIONS`
365
- // which is less than `u32::MAX`.
358
+ // The fixpoint iteration hasn't converged. Iterate again...
366
359
iteration_count = iteration_count. increment ( ) . unwrap_or_else ( || {
367
360
:: tracing:: warn!( "{database_key_index:?}: execute: too many cycle iterations" ) ;
368
361
panic ! ( "{database_key_index:?}: execute: too many cycle iterations" )
@@ -379,31 +372,29 @@ where
379
372
"{database_key_index:?}: execute: iterate again ({iteration_count:?})..." ,
380
373
) ;
381
374
382
- completed_query
383
- . revisions
384
- . update_iteration_count_mut ( database_key_index, iteration_count) ;
385
-
386
- for head in completed_query. revisions . cycle_heads ( ) {
375
+ // Update the iteration count of nested cycles
376
+ for head in & cycle_heads {
387
377
if head. database_key_index == database_key_index {
388
378
continue ;
389
379
}
390
380
391
381
let ingredient =
392
382
zalsa. lookup_ingredient ( head. database_key_index . ingredient_index ( ) ) ;
393
383
394
- // let iteration_count = if was_initial && !head.iteration_count.load().is_initial() {
395
- // IterationCount::first_after_restart()
396
- // } else {
397
- // iteration_count
398
- // };
399
-
400
384
ingredient. set_cycle_iteration_count (
401
385
zalsa,
402
386
head. database_key_index . key_index ( ) ,
403
387
iteration_count,
404
388
) ;
405
389
}
406
390
391
+ // Update the iteration count of this cycle head, but only after restoring
392
+ // the cycle heads array.
393
+ completed_query. revisions . set_cycle_heads ( cycle_heads) ;
394
+ completed_query
395
+ . revisions
396
+ . update_iteration_count_mut ( database_key_index, iteration_count) ;
397
+
407
398
let new_memo = self . insert_memo (
408
399
zalsa,
409
400
id,
@@ -527,8 +518,7 @@ fn outer_cycle(
527
518
current_key : DatabaseKeyIndex ,
528
519
) -> Option < DatabaseKeyIndex > {
529
520
cycle_heads
530
- . iter ( )
531
- . filter ( |head| head. database_key_index != current_key)
521
+ . iter_skip_own ( current_key)
532
522
. find ( |head| {
533
523
// SAFETY: We don't call into with_query_stack recursively
534
524
let is_on_stack = unsafe {
0 commit comments