Skip to content

Commit 1339754

Browse files
committed
Track cycle function dependenciees as part of the cyclic query
1 parent cdd0b85 commit 1339754

File tree

4 files changed

+61
-19
lines changed

4 files changed

+61
-19
lines changed

src/active_query.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ impl ActiveQuery {
9191
.mark_all_active(active_tracked_ids.iter().copied());
9292
}
9393

94+
pub(super) fn take_cycle_heads(&mut self) -> CycleHeads {
95+
std::mem::take(&mut self.cycle_heads)
96+
}
97+
9498
pub(super) fn add_read(
9599
&mut self,
96100
input: DatabaseKeyIndex,

src/cycle.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,4 +490,8 @@ impl<'db> ProvisionalStatus<'db> {
490490
_ => empty_cycle_heads(),
491491
}
492492
}
493+
494+
pub(crate) const fn is_provisional(&self) -> bool {
495+
matches!(self, ProvisionalStatus::Provisional { .. }
496+
}
493497
}

src/function/execute.rs

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,25 @@ where
5656
});
5757

5858
let (new_value, mut completed_query) = match C::CYCLE_STRATEGY {
59-
CycleRecoveryStrategy::Panic => Self::execute_query(
60-
db,
61-
zalsa,
62-
zalsa_local.push_query(database_key_index, IterationCount::initial()),
63-
opt_old_memo,
64-
),
59+
CycleRecoveryStrategy::Panic => {
60+
let (new_value, active_query) = Self::execute_query(
61+
db,
62+
zalsa,
63+
zalsa_local.push_query(database_key_index, IterationCount::initial()),
64+
opt_old_memo,
65+
);
66+
(new_value, active_query.pop())
67+
}
6568
CycleRecoveryStrategy::FallbackImmediate => {
66-
let (mut new_value, mut completed_query) = Self::execute_query(
69+
let (mut new_value, active_query) = Self::execute_query(
6770
db,
6871
zalsa,
6972
zalsa_local.push_query(database_key_index, IterationCount::initial()),
7073
opt_old_memo,
7174
);
7275

76+
let mut completed_query = active_query.pop();
77+
7378
if let Some(cycle_heads) = completed_query.revisions.cycle_heads_mut() {
7479
// Did the new result we got depend on our own provisional value, in a cycle?
7580
if cycle_heads.contains(&database_key_index) {
@@ -196,9 +201,10 @@ where
196201

197202
let _poison_guard =
198203
PoisonProvisionalIfPanicking::new(self, zalsa, id, memo_ingredient_index);
199-
let mut active_query = zalsa_local.push_query(database_key_index, iteration_count);
200204

201205
let (new_value, completed_query) = loop {
206+
let active_query = zalsa_local.push_query(database_key_index, iteration_count);
207+
202208
// Tracked struct ids that existed in the previous revision
203209
// but weren't recreated in the last iteration. It's important that we seed the next
204210
// query with these ids because the query might re-create them as part of the next iteration.
@@ -207,21 +213,22 @@ where
207213
// if they aren't recreated when reaching the final iteration.
208214
active_query.seed_tracked_struct_ids(&last_stale_tracked_ids);
209215

210-
let (mut new_value, mut completed_query) = Self::execute_query(
216+
let (mut new_value, mut active_query) = Self::execute_query(
211217
db,
212218
zalsa,
213219
active_query,
214220
last_provisional_memo.or(opt_old_memo),
215221
);
216222

217-
// If there are no cycle heads, break out of the loop (`cycle_heads_mut` returns `None` if the cycle head list is empty)
218-
let Some(cycle_heads) = completed_query.revisions.cycle_heads_mut() else {
223+
// Take the cycle heads to not-fight-rust's-borrow-checker.
224+
let mut cycle_heads = active_query.take_cycle_heads();
225+
226+
// If there are no cycle heads, break out of the loop.
227+
if cycle_heads.is_empty() {
219228
claim_guard.set_release_mode(ReleaseMode::SelfOnly);
220-
break (new_value, completed_query);
221-
};
229+
break (new_value, active_query.pop());
230+
}
222231

223-
// Take the cycle heads to not-fight-rust's-borrow-checker.
224-
let mut cycle_heads = std::mem::take(cycle_heads);
225232
let mut missing_heads: SmallVec<[(DatabaseKeyIndex, IterationCount); 1]> =
226233
SmallVec::new_const();
227234
let mut max_iteration_count = iteration_count;
@@ -252,6 +259,10 @@ where
252259
.provisional_status(zalsa, head.database_key_index.key_index())
253260
.expect("cycle head memo must have been created during the execution");
254261

262+
// A cycle head isn't allowed to be final because that would mean that this query
263+
// dependent on the initial value (or last provisional)
264+
assert!(provisional_status.is_provisional());
265+
255266
for nested_head in provisional_status.cycle_heads() {
256267
let nested_as_tuple = (
257268
nested_head.database_key_index,
@@ -288,6 +299,8 @@ where
288299
claim_guard.set_release_mode(ReleaseMode::SelfOnly);
289300
}
290301

302+
let mut completed_query = active_query.pop();
303+
*completed_query.revisions.verified_final.get_mut() = false;
291304
completed_query.revisions.set_cycle_heads(cycle_heads);
292305
break (new_value, completed_query);
293306
}
@@ -359,8 +372,17 @@ where
359372
this_converged = C::values_equal(&new_value, last_provisional_value);
360373
}
361374
}
375+
376+
let new_cycle_heads = active_query.take_cycle_heads();
377+
for head in new_cycle_heads {
378+
if !cycle_heads.contains(&head.database_key_index) {
379+
panic!("Cycle recovery function for {database_key_index:?} introduced a cycle, depending on {:?}. This is not allowed.", head.database_key_index);
380+
}
381+
}
362382
}
363383

384+
let mut completed_query = active_query.pop();
385+
364386
if let Some(outer_cycle) = outer_cycle {
365387
tracing::info!(
366388
"Detected nested cycle {database_key_index:?}, iterate it as part of the outer cycle {outer_cycle:?}"
@@ -371,6 +393,7 @@ where
371393
completed_query
372394
.revisions
373395
.set_cycle_converged(this_converged);
396+
*completed_query.revisions.verified_final.get_mut() = false;
374397

375398
// Transfer ownership of this query to the outer cycle, so that it can claim it
376399
// and other threads don't compete for the same lock.
@@ -409,9 +432,9 @@ where
409432
}
410433

411434
*completed_query.revisions.verified_final.get_mut() = true;
412-
413435
break (new_value, completed_query);
414436
}
437+
*completed_query.revisions.verified_final.get_mut() = false;
415438

416439
// The fixpoint iteration hasn't converged. Iterate again...
417440
iteration_count = iteration_count.increment().unwrap_or_else(|| {
@@ -465,7 +488,6 @@ where
465488
last_provisional_memo = Some(new_memo);
466489

467490
last_stale_tracked_ids = completed_query.stale_tracked_structs;
468-
active_query = zalsa_local.push_query(database_key_index, iteration_count);
469491

470492
continue;
471493
};
@@ -484,7 +506,7 @@ where
484506
zalsa: &'db Zalsa,
485507
active_query: ActiveQueryGuard<'db>,
486508
opt_old_memo: Option<&Memo<'db, C>>,
487-
) -> (C::Output<'db>, CompletedQuery) {
509+
) -> (C::Output<'db>, ActiveQueryGuard<'db>) {
488510
if let Some(old_memo) = opt_old_memo {
489511
// If we already executed this query once, then use the tracked-struct ids from the
490512
// previous execution as the starting point for the new one.
@@ -509,7 +531,7 @@ where
509531
C::id_to_input(zalsa, active_query.database_key_index.key_index()),
510532
);
511533

512-
(new_value, active_query.pop())
534+
(new_value, active_query)
513535
}
514536
}
515537

src/zalsa_local.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,18 @@ impl ActiveQueryGuard<'_> {
12021202
}
12031203
}
12041204

1205+
pub(crate) fn take_cycle_heads(&mut self) -> CycleHeads {
1206+
// SAFETY: We do not access the query stack reentrantly.
1207+
unsafe {
1208+
self.local_state.with_query_stack_unchecked_mut(|stack| {
1209+
#[cfg(debug_assertions)]
1210+
assert_eq!(stack.len(), self.push_len);
1211+
let frame = stack.last_mut().unwrap();
1212+
frame.take_cycle_heads()
1213+
})
1214+
}
1215+
}
1216+
12051217
/// Invoked when the query has successfully completed execution.
12061218
fn complete(self) -> CompletedQuery {
12071219
// SAFETY: We do not access the query stack reentrantly.

0 commit comments

Comments
 (0)