Skip to content

Commit 8c5d831

Browse files
committed
Fix infinite loop in `provisional_retry
1 parent 97b4535 commit 8c5d831

File tree

8 files changed

+86
-158
lines changed

8 files changed

+86
-158
lines changed

src/active_query.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,6 @@ impl ActiveQuery {
158158
}
159159
}
160160

161-
pub(super) fn iteration_count(&self) -> IterationCount {
162-
self.iteration_count
163-
}
164-
165161
pub(crate) fn tracked_struct_ids(&self) -> &IdentityMap {
166162
&self.tracked_struct_ids
167163
}

src/function/execute.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@ where
2828
/// * `db`, the database.
2929
/// * `active_query`, the active stack frame for the query to execute.
3030
/// * `opt_old_memo`, the older memo, if any existed. Used for backdating.
31+
///
32+
/// # Returns
33+
/// The newly computed memo or `None` if this query is part of a larger cycle
34+
/// and `execute` blocked on a cycle head running on another thread. In this case,
35+
/// the memo is potentially outdated and needs to be refetched.
3136
#[inline(never)]
3237
pub(super) fn execute<'db>(
3338
&'db self,
3439
db: &'db C::DbView,
3540
mut claim_guard: ClaimGuard<'db>,
3641
zalsa_local: &'db ZalsaLocal,
3742
opt_old_memo: Option<&Memo<'db, C>>,
38-
) -> &'db Memo<'db, C> {
43+
) -> Option<&'db Memo<'db, C>> {
3944
let database_key_index = claim_guard.database_key_index();
4045
let zalsa = claim_guard.zalsa();
4146

@@ -80,7 +85,7 @@ where
8085
// We need to mark the memo as finalized so other cycle participants that have fallbacks
8186
// will be verified (participants that don't have fallbacks will not be verified).
8287
memo.revisions.verified_final.store(true, Ordering::Release);
83-
return memo;
88+
return Some(memo);
8489
}
8590

8691
// If we're in the middle of a cycle and we have a fallback, use it instead.
@@ -125,7 +130,7 @@ where
125130
self.diff_outputs(zalsa, database_key_index, old_memo, &completed_query);
126131
}
127132

128-
self.insert_memo(
133+
let memo = self.insert_memo(
129134
zalsa,
130135
id,
131136
Memo::new(
@@ -134,7 +139,13 @@ where
134139
completed_query.revisions,
135140
),
136141
memo_ingredient_index,
137-
)
142+
);
143+
144+
if claim_guard.drop() {
145+
None
146+
} else {
147+
Some(memo)
148+
}
138149
}
139150

140151
fn execute_maybe_iterate<'db>(

src/function/fetch.rs

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,11 @@ where
5858
id: Id,
5959
) -> &'db Memo<'db, C> {
6060
let memo_ingredient_index = self.memo_ingredient_index(zalsa, id);
61-
let mut retry_count = 0;
61+
6262
loop {
6363
if let Some(memo) = self
6464
.fetch_hot(zalsa, id, memo_ingredient_index)
65-
.or_else(|| {
66-
self.fetch_cold_with_retry(
67-
zalsa,
68-
zalsa_local,
69-
db,
70-
id,
71-
memo_ingredient_index,
72-
&mut retry_count,
73-
)
74-
})
65+
.or_else(|| self.fetch_cold(zalsa, zalsa_local, db, id, memo_ingredient_index))
7566
{
7667
return memo;
7768
}
@@ -104,33 +95,6 @@ where
10495
}
10596
}
10697

107-
fn fetch_cold_with_retry<'db>(
108-
&'db self,
109-
zalsa: &'db Zalsa,
110-
zalsa_local: &'db ZalsaLocal,
111-
db: &'db C::DbView,
112-
id: Id,
113-
memo_ingredient_index: MemoIngredientIndex,
114-
retry_count: &mut u32,
115-
) -> Option<&'db Memo<'db, C>> {
116-
let memo = self.fetch_cold(zalsa, zalsa_local, db, id, memo_ingredient_index)?;
117-
118-
// If we get back a provisional cycle memo, and it's provisional on any cycle heads
119-
// that are claimed by a different thread, we can't propagate the provisional memo
120-
// any further (it could escape outside the cycle); we need to block on the other
121-
// thread completing fixpoint iteration of the cycle, and then we can re-query for
122-
// our no-longer-provisional memo.
123-
// That is only correct for fixpoint cycles, though: `FallbackImmediate` cycles
124-
// never have provisional entries.
125-
if C::CYCLE_STRATEGY == CycleRecoveryStrategy::FallbackImmediate
126-
|| !memo.provisional_retry(zalsa, zalsa_local, self.database_key_index(id), retry_count)
127-
{
128-
Some(memo)
129-
} else {
130-
None
131-
}
132-
}
133-
13498
fn fetch_cold<'db>(
13599
&'db self,
136100
zalsa: &'db Zalsa,
@@ -151,7 +115,7 @@ where
151115

152116
if let Some(memo) = memo {
153117
if memo.value.is_some() {
154-
memo.block_on_heads(zalsa, zalsa_local);
118+
memo.block_on_heads(zalsa);
155119
}
156120
}
157121
}
@@ -212,9 +176,7 @@ where
212176
}
213177
}
214178

215-
let memo = self.execute(db, claim_guard, zalsa_local, opt_old_memo);
216-
217-
Some(memo)
179+
self.execute(db, claim_guard, zalsa_local, opt_old_memo)
218180
}
219181

220182
#[cold]

src/function/maybe_changed_after.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ where
228228
// `in_cycle` tracks if the enclosing query is in a cycle. `deep_verify.cycle_heads` tracks
229229
// if **this query** encountered a cycle (which means there's some provisional value somewhere floating around).
230230
if old_memo.value.is_some() && !cycle_heads.has_any() {
231-
let memo = self.execute(db, claim_guard, zalsa_local, Some(old_memo));
231+
let memo = self.execute(db, claim_guard, zalsa_local, Some(old_memo))?;
232232
let changed_at = memo.revisions.changed_at;
233233

234234
// Always assume that a provisional value has changed.
@@ -500,7 +500,7 @@ where
500500
return on_stack;
501501
}
502502

503-
let cycle_heads_iter = TryClaimCycleHeadsIter::new(zalsa, zalsa_local, cycle_heads);
503+
let cycle_heads_iter = TryClaimCycleHeadsIter::new(zalsa, cycle_heads);
504504

505505
for cycle_head in cycle_heads_iter {
506506
match cycle_head {

src/function/memo.rs

Lines changed: 10 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::runtime::Running;
1414
use crate::sync::atomic::Ordering;
1515
use crate::table::memo::MemoTableWithTypesMut;
1616
use crate::zalsa::{MemoIngredientIndex, Zalsa};
17-
use crate::zalsa_local::{QueryOriginRef, QueryRevisions, ZalsaLocal};
17+
use crate::zalsa_local::{QueryOriginRef, QueryRevisions};
1818
use crate::{Event, EventKind, Id, Revision};
1919

2020
impl<C: Configuration> IngredientImpl<C> {
@@ -132,50 +132,12 @@ impl<'db, C: Configuration> Memo<'db, C> {
132132
!self.revisions.verified_final.load(Ordering::Relaxed)
133133
}
134134

135-
/// Invoked when `refresh_memo` is about to return a memo to the caller; if that memo is
136-
/// provisional, and its cycle head is claimed by another thread, we need to wait for that
137-
/// other thread to complete the fixpoint iteration, and then retry fetching our own memo.
138-
///
139-
/// Return `true` if the caller should retry, `false` if the caller should go ahead and return
140-
/// this memo to the caller.
141-
#[inline(always)]
142-
pub(super) fn provisional_retry(
143-
&self,
144-
zalsa: &Zalsa,
145-
zalsa_local: &ZalsaLocal,
146-
database_key_index: DatabaseKeyIndex,
147-
retry_count: &mut u32,
148-
) -> bool {
149-
if self.block_on_heads(zalsa, zalsa_local) {
150-
// If we get here, we are a provisional value of
151-
// the cycle head (either initial value, or from a later iteration) and should be
152-
// returned to caller to allow fixpoint iteration to proceed.
153-
false
154-
} else {
155-
assert!(
156-
*retry_count <= 20000,
157-
"Provisional memo retry limit exceeded for {database_key_index:?}; \
158-
this usually indicates a bug in salsa's cycle caching/locking. \
159-
(retried {retry_count} times)",
160-
);
161-
162-
*retry_count += 1;
163-
164-
// all our cycle heads are complete; re-fetch
165-
// and we should get a non-provisional memo.
166-
crate::tracing::debug!(
167-
"Retrying provisional memo {database_key_index:?} after awaiting cycle heads."
168-
);
169-
true
170-
}
171-
}
172-
173135
/// Blocks on all cycle heads (recursively) that this memo depends on.
174136
///
175137
/// Returns `true` if awaiting all cycle heads results in a cycle. This means, they're all waiting
176138
/// for us to make progress.
177139
#[inline(always)]
178-
pub(super) fn block_on_heads(&self, zalsa: &Zalsa, zalsa_local: &ZalsaLocal) -> bool {
140+
pub(super) fn block_on_heads(&self, zalsa: &Zalsa) -> bool {
179141
// IMPORTANT: If you make changes to this function, make sure to run `cycle_nested_deep` with
180142
// shuttle with at least 10k iterations.
181143

@@ -184,16 +146,12 @@ impl<'db, C: Configuration> Memo<'db, C> {
184146
return true;
185147
}
186148

187-
return block_on_heads_cold(zalsa, zalsa_local, cycle_heads);
149+
return block_on_heads_cold(zalsa, cycle_heads);
188150

189151
#[inline(never)]
190-
fn block_on_heads_cold(
191-
zalsa: &Zalsa,
192-
zalsa_local: &ZalsaLocal,
193-
heads: &CycleHeads,
194-
) -> bool {
152+
fn block_on_heads_cold(zalsa: &Zalsa, heads: &CycleHeads) -> bool {
195153
let _entered = crate::tracing::debug_span!("block_on_heads").entered();
196-
let cycle_heads = TryClaimCycleHeadsIter::new(zalsa, zalsa_local, heads);
154+
let cycle_heads = TryClaimCycleHeadsIter::new(zalsa, heads);
197155
let mut all_cycles = true;
198156

199157
for claim_result in cycle_heads {
@@ -447,6 +405,7 @@ mod persistence {
447405
}
448406
}
449407

408+
#[derive(Debug)]
450409
pub(super) enum TryClaimHeadsResult<'me> {
451410
/// Claiming the cycle head results in a cycle.
452411
Cycle {
@@ -465,19 +424,15 @@ pub(super) enum TryClaimHeadsResult<'me> {
465424
/// Iterator to try claiming the transitive cycle heads of a memo.
466425
pub(super) struct TryClaimCycleHeadsIter<'a> {
467426
zalsa: &'a Zalsa,
468-
zalsa_local: &'a ZalsaLocal,
427+
469428
cycle_heads: CycleHeadsIterator<'a>,
470429
}
471430

472431
impl<'a> TryClaimCycleHeadsIter<'a> {
473-
pub(super) fn new(
474-
zalsa: &'a Zalsa,
475-
zalsa_local: &'a ZalsaLocal,
476-
cycle_heads: &'a CycleHeads,
477-
) -> Self {
432+
pub(super) fn new(zalsa: &'a Zalsa, cycle_heads: &'a CycleHeads) -> Self {
478433
Self {
479434
zalsa,
480-
zalsa_local,
435+
481436
cycle_heads: cycle_heads.iter(),
482437
}
483438
}
@@ -488,31 +443,7 @@ impl<'me> Iterator for TryClaimCycleHeadsIter<'me> {
488443

489444
fn next(&mut self) -> Option<Self::Item> {
490445
let head = self.cycle_heads.next()?;
491-
492446
let head_database_key = head.database_key_index;
493-
let head_iteration_count = head.iteration_count.load();
494-
495-
// The most common case is that the head is already in the query stack. So let's check that first.
496-
// SAFETY: We do not access the query stack reentrantly.
497-
if let Some(current_iteration_count) = unsafe {
498-
self.zalsa_local.with_query_stack_unchecked(|stack| {
499-
stack
500-
.iter()
501-
.rev()
502-
.find(|query| query.database_key_index == head_database_key)
503-
.map(|query| query.iteration_count())
504-
})
505-
} {
506-
crate::tracing::trace!(
507-
"Waiting for {head_database_key:?} results in a cycle (because it is already in the query stack)"
508-
);
509-
return Some(TryClaimHeadsResult::Cycle {
510-
head_iteration_count,
511-
memo_iteration_count: current_iteration_count,
512-
verified_at: self.zalsa.current_revision(),
513-
});
514-
}
515-
516447
let head_key_index = head_database_key.key_index();
517448
let ingredient = self
518449
.zalsa
@@ -543,7 +474,7 @@ impl<'me> Iterator for TryClaimCycleHeadsIter<'me> {
543474

544475
Some(TryClaimHeadsResult::Cycle {
545476
memo_iteration_count: current_iteration_count,
546-
head_iteration_count,
477+
head_iteration_count: head.iteration_count.load(),
547478
verified_at,
548479
})
549480
}

0 commit comments

Comments
 (0)