Skip to content

Commit 5202579

Browse files
committed
fixed all single-thread cycles; multi-thread still not working
1 parent 00acc56 commit 5202579

20 files changed

+317
-196
lines changed

src/accumulator.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ impl<A: Accumulator> Ingredient for IngredientImpl<A> {
107107
panic!("nothing should ever depend on an accumulator directly")
108108
}
109109

110+
fn is_verified_final<'db>(&'db self, _db: &'db dyn Database, _input: Id) -> bool {
111+
false
112+
}
113+
110114
fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy {
111115
CycleRecoveryStrategy::Panic
112116
}

src/active_query.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,15 @@ impl ActiveQuery {
7777
durability: Durability,
7878
revision: Revision,
7979
accumulated: InputAccumulatedValues,
80-
cycle_heads: &FxHashSet<DatabaseKeyIndex>,
80+
cycle_heads: Option<&FxHashSet<DatabaseKeyIndex>>,
8181
) {
8282
self.input_outputs.insert((EdgeKind::Input, input));
8383
self.durability = self.durability.min(durability);
8484
self.changed_at = self.changed_at.max(revision);
8585
self.accumulated.add_input(accumulated);
86-
self.cycle_heads.extend(cycle_heads);
86+
if let Some(cycle_heads) = cycle_heads {
87+
self.cycle_heads.extend(cycle_heads);
88+
}
8789
}
8890

8991
pub(super) fn add_untracked_read(&mut self, changed_at: Revision) {

src/cycle.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/// The maximum number of times we'll fixpoint-iterate before panicking.
2+
///
3+
/// Should only be relevant in case of a badly configured cycle recovery.
4+
pub const MAX_ITERATIONS: u32 = 200;
5+
16
/// Return value from a cycle recovery function.
27
#[derive(Debug)]
38
pub enum CycleRecoveryAction<T> {

src/function.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ where
202202
self.maybe_changed_after(db, key, revision)
203203
}
204204

205+
fn is_verified_final<'db>(&'db self, db: &'db dyn Database, input: Id) -> bool {
206+
self.get_memo_from_table_for(db.zalsa(), input)
207+
.is_some_and(|memo| !memo.may_be_provisional())
208+
}
209+
205210
fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy {
206211
C::CYCLE_STRATEGY
207212
}

src/function/execute.rs

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::sync::Arc;
22

3-
use crate::{zalsa::ZalsaDatabase, Database, DatabaseKeyIndex, Event, EventKind};
3+
use crate::{
4+
cycle::MAX_ITERATIONS, zalsa::ZalsaDatabase, Database, DatabaseKeyIndex, Event, EventKind,
5+
};
46

57
use super::{memo::Memo, Configuration, IngredientImpl};
68

@@ -103,51 +105,36 @@ where
103105
) {
104106
crate::CycleRecoveryAction::Iterate => {
105107
tracing::debug!("{database_key_index:?}: execute: iterate again");
106-
iteration_count = iteration_count.checked_add(1).expect(
107-
"fixpoint iteration of {database_key_index:#?} should \
108-
converge before u32::MAX iterations",
109-
);
110-
opt_last_provisional = Some(self.insert_memo(
111-
zalsa,
112-
id,
113-
Memo::new(Some(new_value), revision_now, revisions),
114-
));
115-
continue;
116108
}
117109
crate::CycleRecoveryAction::Fallback(fallback_value) => {
118110
tracing::debug!(
119111
"{database_key_index:?}: execute: user cycle_fn says to fall back"
120112
);
121113
new_value = fallback_value;
114+
// We have to insert the fallback value for this query and then iterate
115+
// one more time to fill in correct values for everything else in the
116+
// cycle based on it; then we'll re-insert it as final value.
122117
}
123118
}
124-
}
125-
iteration_count = iteration_count.checked_add(1).expect(
126-
"fixpoint iteration of {database_key_index:#?} should \
119+
iteration_count = iteration_count.checked_add(1).expect(
120+
"fixpoint iteration of {database_key_index:#?} should \
127121
converge before u32::MAX iterations",
128-
);
129-
if iteration_count > 10 {
130-
panic!("too much iteration");
122+
);
123+
if iteration_count > MAX_ITERATIONS {
124+
panic!("{database_key_index:?}: execute: too many cycle iterations");
125+
}
126+
opt_last_provisional = Some(self.insert_memo(
127+
zalsa,
128+
id,
129+
Memo::new(Some(new_value), revision_now, revisions),
130+
));
131+
continue;
131132
}
132-
// This is no longer a provisional result, it's our final result, so remove ourself
133-
// from the cycle heads, and iterate one last time to remove ourself from all other
134-
// results in the cycle as well and turn them into usable cached results.
135-
// TODO Can we avoid doing this? the extra iteration is quite expensive if there is
136-
// a nested cycle. Maybe track the relevant memos and replace them all with the
137-
// cycle head removed? Or just let them keep the cycle head and allow cycle memos
138-
// to be used when we are not actually iterating the cycle for that head?
139133
tracing::debug!(
140-
"{database_key_index:?}: execute: fixpoint iteration has a final value, \
141-
one more iteration to remove cycle heads from memos"
134+
"{database_key_index:?}: execute: fixpoint iteration has a final value"
142135
);
143136
revisions.cycle_heads.remove(&database_key_index);
144137
dbg!(&revisions.cycle_heads);
145-
self.insert_memo(
146-
zalsa,
147-
id,
148-
Memo::new(Some(new_value), revision_now, revisions),
149-
);
150-
continue;
151138
}
152139

153140
tracing::debug!("{database_key_index:?}: execute: result.revisions = {revisions:#?}");

src/function/fetch.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ where
2929
durability,
3030
changed_at,
3131
InputAccumulatedValues::from_map(&memo.revisions.accumulated),
32-
&memo.revisions.cycle_heads,
32+
memo.cycle_heads(),
3333
);
3434

3535
value
@@ -54,8 +54,7 @@ where
5454
let memo_guard = self.get_memo_from_table_for(zalsa, id);
5555
if let Some(memo) = &memo_guard {
5656
if memo.value.is_some()
57-
&& !memo.is_provisional()
58-
&& self.shallow_verify_memo(db, zalsa, self.database_key_index(id), memo)
57+
&& self.shallow_verify_memo(db, zalsa, self.database_key_index(id), memo, false)
5958
{
6059
// Unsafety invariant: memo is present in memo_map
6160
unsafe {
@@ -84,9 +83,10 @@ where
8483
let memo_guard = self.get_memo_from_table_for(zalsa, id);
8584
if let Some(memo) = &memo_guard {
8685
dbg!("found provisional value, shallow verifying it");
86+
dbg!(memo.tracing_debug());
8787
if memo.value.is_some()
8888
&& memo.revisions.cycle_heads.contains(&database_key_index)
89-
&& self.shallow_verify_memo(db, zalsa, database_key_index, memo)
89+
&& self.shallow_verify_memo(db, zalsa, database_key_index, memo, true)
9090
{
9191
dbg!("verified provisional value, returning it");
9292
dbg!(&memo.value);
@@ -146,6 +146,8 @@ where
146146
}
147147
}
148148

149-
Some(self.execute(db, database_key_index, opt_old_memo))
149+
let memo = self.execute(db, database_key_index, opt_old_memo);
150+
151+
Some(memo)
150152
}
151153
}

src/function/maybe_changed_after.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@ where
5757
// Check if we have a verified version: this is the hot path.
5858
let memo_guard = self.get_memo_from_table_for(zalsa, id);
5959
if let Some(memo) = &memo_guard {
60-
if !memo.is_provisional()
61-
&& self.shallow_verify_memo(db, zalsa, database_key_index, memo)
62-
{
60+
if self.shallow_verify_memo(db, zalsa, database_key_index, memo, false) {
6361
return VerifyResult::changed_if(memo.revisions.changed_at > revision);
6462
}
6563
drop(memo_guard); // release the arc-swap guard before cold path
@@ -150,14 +148,25 @@ where
150148
zalsa: &Zalsa,
151149
database_key_index: DatabaseKeyIndex,
152150
memo: &Memo<C::Output<'_>>,
151+
allow_provisional: bool,
153152
) -> bool {
154-
let verified_at = memo.verified_at.load();
155-
let revision_now = zalsa.current_revision();
156-
157153
tracing::debug!(
158154
"{database_key_index:?}: shallow_verify_memo(memo = {memo:#?})",
159155
memo = memo.tracing_debug()
160156
);
157+
if !allow_provisional {
158+
if memo.may_be_provisional() {
159+
tracing::debug!(
160+
"{database_key_index:?}: validate_provisional(memo = {memo:#?})",
161+
memo = memo.tracing_debug()
162+
);
163+
if !self.validate_provisional(db, zalsa, memo) {
164+
return false;
165+
}
166+
}
167+
}
168+
let verified_at = memo.verified_at.load();
169+
let revision_now = zalsa.current_revision();
161170

162171
if verified_at == revision_now {
163172
// Already verified.
@@ -175,6 +184,26 @@ where
175184
false
176185
}
177186

187+
/// Check if this memo's cycle heads have all been finalized. If so, mark it verified final and
188+
/// return true, if not return false.
189+
fn validate_provisional(
190+
&self,
191+
db: &C::DbView,
192+
zalsa: &Zalsa,
193+
memo: &Memo<C::Output<'_>>,
194+
) -> bool {
195+
for cycle_head in &memo.revisions.cycle_heads {
196+
if !zalsa
197+
.lookup_ingredient(cycle_head.ingredient_index)
198+
.is_verified_final(db.as_dyn_database(), cycle_head.key_index)
199+
{
200+
return false;
201+
}
202+
}
203+
memo.verified_final.store(true);
204+
true
205+
}
206+
178207
/// VerifyResult::Unchanged if the memo's value and `changed_at` time is up to date in the
179208
/// current revision. When this returns Unchanged with no cycle heads, it also updates the
180209
/// memo's `verified_at` field if needed to make future calls cheaper.
@@ -188,9 +217,6 @@ where
188217
old_memo: &Memo<C::Output<'_>>,
189218
active_query: &ActiveQueryGuard<'_>,
190219
) -> VerifyResult {
191-
if old_memo.is_provisional() {
192-
return VerifyResult::Changed;
193-
}
194220
let zalsa = db.zalsa();
195221
let database_key_index = active_query.database_key_index;
196222

@@ -199,9 +225,12 @@ where
199225
old_memo = old_memo.tracing_debug()
200226
);
201227

202-
if self.shallow_verify_memo(db, zalsa, database_key_index, old_memo) {
228+
if self.shallow_verify_memo(db, zalsa, database_key_index, old_memo, false) {
203229
return VerifyResult::Unchanged(Default::default());
204230
}
231+
if old_memo.may_be_provisional() {
232+
return VerifyResult::Changed;
233+
}
205234

206235
loop {
207236
let mut cycle_heads = FxHashSet::default();

src/function/memo.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use rustc_hash::FxHashSet;
12
use std::any::Any;
23
use std::fmt::Debug;
34
use std::fmt::Formatter;
@@ -109,6 +110,9 @@ pub(super) struct Memo<V> {
109110
/// as the current revision.
110111
pub(super) verified_at: AtomicCell<Revision>,
111112

113+
/// Is this memo verified to not be a provisional cycle result?
114+
pub(super) verified_final: AtomicCell<bool>,
115+
112116
/// Revision information
113117
pub(super) revisions: QueryRevisions,
114118
}
@@ -118,13 +122,23 @@ impl<V> Memo<V> {
118122
Memo {
119123
value,
120124
verified_at: AtomicCell::new(revision_now),
125+
verified_final: AtomicCell::new(revisions.cycle_heads.is_empty()),
121126
revisions,
122127
}
123128
}
124129

125-
/// True if this is a provisional cycle-iteration result.
126-
pub(super) fn is_provisional(&self) -> bool {
127-
!self.revisions.cycle_heads.is_empty()
130+
/// True if this is may be a provisional cycle-iteration result.
131+
pub(super) fn may_be_provisional(&self) -> bool {
132+
!self.verified_final.load()
133+
}
134+
135+
/// Cycle heads that should be propagated to dependent queries.
136+
pub(super) fn cycle_heads(&self) -> Option<&FxHashSet<DatabaseKeyIndex>> {
137+
if self.may_be_provisional() {
138+
Some(&self.revisions.cycle_heads)
139+
} else {
140+
None
141+
}
128142
}
129143

130144
/// True if this memo is known not to have changed based on its durability.
@@ -185,6 +199,7 @@ impl<V> Memo<V> {
185199
},
186200
)
187201
.field("verified_at", &self.memo.verified_at)
202+
.field("verified_final", &self.memo.verified_final)
188203
.field("revisions", &self.memo.revisions)
189204
.finish()
190205
}

src/function/specify.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ where
8181
let memo = Memo {
8282
value: Some(value),
8383
verified_at: AtomicCell::new(revision),
84+
verified_final: AtomicCell::new(true),
8485
revisions,
8586
};
8687

src/ingredient.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
4141
revision: Revision,
4242
) -> VerifyResult;
4343

44+
/// Is the value for `input` in this ingredient marked as possibly a provisional cycle value?
45+
fn is_verified_final<'db>(&'db self, db: &'db dyn Database, input: Id) -> bool;
46+
4447
/// What were the inputs (if any) that were used to create the value at `key_index`.
4548
fn origin(&self, db: &dyn Database, key_index: Id) -> Option<QueryOrigin>;
4649

src/input.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ impl<C: Configuration> IngredientImpl<C> {
191191
stamp.durability,
192192
stamp.changed_at,
193193
InputAccumulatedValues::Empty,
194-
&Default::default(),
194+
None,
195195
);
196196
&value.fields
197197
}
@@ -222,6 +222,10 @@ impl<C: Configuration> Ingredient for IngredientImpl<C> {
222222
VerifyResult::unchanged()
223223
}
224224

225+
fn is_verified_final<'db>(&'db self, _db: &'db dyn Database, _input: Id) -> bool {
226+
false
227+
}
228+
225229
fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy {
226230
CycleRecoveryStrategy::Panic
227231
}

src/input/input_field.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ where
6262
VerifyResult::changed_if(value.stamps[self.field_index].changed_at > revision)
6363
}
6464

65+
fn is_verified_final<'db>(&'db self, _db: &'db dyn Database, _input: Id) -> bool {
66+
false
67+
}
68+
6569
fn origin(&self, _db: &dyn Database, _key_index: Id) -> Option<QueryOrigin> {
6670
None
6771
}

src/interned.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ where
136136
Durability::MAX,
137137
self.reset_at,
138138
InputAccumulatedValues::Empty,
139-
&Default::default(),
139+
None,
140140
);
141141

142142
// Optimisation to only get read lock on the map if the data has already
@@ -226,6 +226,10 @@ where
226226
VerifyResult::changed_if(revision < self.reset_at)
227227
}
228228

229+
fn is_verified_final<'db>(&'db self, _db: &'db dyn Database, _input: Id) -> bool {
230+
false
231+
}
232+
229233
fn cycle_recovery_strategy(&self) -> crate::cycle::CycleRecoveryStrategy {
230234
crate::cycle::CycleRecoveryStrategy::Panic
231235
}

src/runtime.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,10 @@ impl Runtime {
172172
let mut dg = self.dependency_graph.lock();
173173
let thread_id = std::thread::current().id();
174174

175+
eprintln!("Runtime::block_on {database_key:?}, I am {thread_id:?}, other id {other_id:?}");
176+
175177
if dg.depends_on(other_id, thread_id) {
178+
eprintln!("thread dependency cycle");
176179
return BlockResult::Cycle;
177180
}
178181

0 commit comments

Comments
 (0)