Skip to content

Commit 4047309

Browse files
committed
Force invalidation on cache eviction
1 parent 00dbf01 commit 4047309

File tree

11 files changed

+106
-2
lines changed

11 files changed

+106
-2
lines changed

components/salsa-macro-rules/src/setup_tracked_fn.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ macro_rules! setup_tracked_fn {
6060

6161
assert_return_type_is_update: {$($assert_return_type_is_update:tt)*},
6262

63+
force_invalidation_on_cache_eviction: $force_invalidation_on_cache_eviction:literal,
64+
6365
// Annoyingly macro-rules hygiene does not extend to items defined in the macro.
6466
// We have the procedural macro generate names for those items that are
6567
// not used elsewhere in the user's code.
@@ -192,6 +194,7 @@ macro_rules! setup_tracked_fn {
192194
line: line!(),
193195
};
194196
const DEBUG_NAME: &'static str = stringify!($fn_name);
197+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = $force_invalidation_on_cache_eviction;
195198

196199
type DbView = dyn $Db;
197200

components/salsa-macros/src/accumulator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ impl AllowedOptions for Accumulator {
4444
const LRU: bool = false;
4545
const CONSTRUCTOR_NAME: bool = false;
4646
const ID: bool = false;
47+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = false;
4748
}
4849

4950
struct StructMacro {

components/salsa-macros/src/input.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ impl crate::options::AllowedOptions for InputStruct {
6262
const CONSTRUCTOR_NAME: bool = true;
6363

6464
const ID: bool = false;
65+
66+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = false;
6567
}
6668

6769
impl SalsaStructAllowedOptions for InputStruct {

components/salsa-macros/src/interned.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ impl crate::options::AllowedOptions for InternedStruct {
6262
const CONSTRUCTOR_NAME: bool = true;
6363

6464
const ID: bool = true;
65+
66+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = false;
6567
}
6668

6769
impl SalsaStructAllowedOptions for InternedStruct {

components/salsa-macros/src/options.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ pub(crate) struct Options<A: AllowedOptions> {
8989
/// If this is `Some`, the value is the `<ident>`.
9090
pub id: Option<syn::Path>,
9191

92+
pub force_invalidation_on_cache_eviction: Option<syn::Ident>,
93+
9294
/// Remember the `A` parameter, which plays no role after parsing.
9395
phantom: PhantomData<A>,
9496
}
@@ -112,6 +114,7 @@ impl<A: AllowedOptions> Default for Options<A> {
112114
lru: Default::default(),
113115
singleton: Default::default(),
114116
id: Default::default(),
117+
force_invalidation_on_cache_eviction: Default::default(),
115118
}
116119
}
117120
}
@@ -133,6 +136,7 @@ pub(crate) trait AllowedOptions {
133136
const LRU: bool;
134137
const CONSTRUCTOR_NAME: bool;
135138
const ID: bool;
139+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool;
136140
}
137141

138142
type Equals = syn::Token![=];
@@ -351,6 +355,20 @@ impl<A: AllowedOptions> syn::parse::Parse for Options<A> {
351355
"`id` option not allowed here",
352356
));
353357
}
358+
} else if ident == "force_invalidation_on_cache_eviction" {
359+
if A::FORCE_INVALIDATION_ON_CACHE_EVICTION {
360+
if let Some(old) = options.force_invalidation_on_cache_eviction.replace(ident) {
361+
return Err(syn::Error::new(
362+
old.span(),
363+
"option `force_invalidation_on_cache_eviction` provided twice",
364+
));
365+
}
366+
} else {
367+
return Err(syn::Error::new(
368+
ident.span(),
369+
"`force_invalidation_on_cache_eviction` option not allowed here",
370+
));
371+
}
354372
} else {
355373
return Err(syn::Error::new(
356374
ident.span(),

components/salsa-macros/src/tracked_fn.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ impl crate::options::AllowedOptions for TrackedFn {
5555
const CONSTRUCTOR_NAME: bool = false;
5656

5757
const ID: bool = false;
58+
59+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = true;
5860
}
5961

6062
struct Macro {
@@ -147,6 +149,8 @@ impl Macro {
147149
let lru = Literal::usize_unsuffixed(self.args.lru.unwrap_or(0));
148150

149151
let return_ref: bool = self.args.return_ref.is_some();
152+
let force_invalidation_on_cache_eviction: bool =
153+
self.args.force_invalidation_on_cache_eviction.is_some();
150154

151155
// The path expression is responsible for emitting the primary span in the diagnostic we
152156
// want, so by uniformly using `output_ty.span()` we ensure that the diagnostic is emitted
@@ -185,6 +189,7 @@ impl Macro {
185189
lru: #lru,
186190
return_ref: #return_ref,
187191
assert_return_type_is_update: { #assert_return_type_is_update },
192+
force_invalidation_on_cache_eviction: #force_invalidation_on_cache_eviction,
188193
unused_names: [
189194
#zalsa,
190195
#Configuration,

components/salsa-macros/src/tracked_struct.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ impl crate::options::AllowedOptions for TrackedStruct {
5757
const CONSTRUCTOR_NAME: bool = true;
5858

5959
const ID: bool = false;
60+
61+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = false;
6062
}
6163

6264
impl SalsaStructAllowedOptions for TrackedStruct {

src/function.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub type Memo<C> = memo::Memo<<C as Configuration>::Output<'static>>;
3838
pub trait Configuration: Any {
3939
const DEBUG_NAME: &'static str;
4040
const LOCATION: crate::ingredient::Location;
41+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool;
4142

4243
/// The database that this function is associated with.
4344
type DbView: ?Sized + crate::Database;

src/function/execute.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::function::{Configuration, IngredientImpl};
44
use crate::loom::sync::atomic::{AtomicBool, Ordering};
55
use crate::zalsa::{MemoIngredientIndex, Zalsa, ZalsaDatabase};
66
use crate::zalsa_local::{ActiveQueryGuard, QueryRevisions};
7-
use crate::{Event, EventKind, Id, Revision};
7+
use crate::{Durability, Event, EventKind, Id, Revision};
88

99
impl<C> IngredientImpl<C>
1010
where
@@ -269,7 +269,12 @@ where
269269
// Query was not previously executed, or value is potentially
270270
// stale, or value is absent. Let's execute!
271271
let new_value = C::execute(db, C::id_to_input(db, id));
272+
let mut revisions = active_query.pop();
272273

273-
(new_value, active_query.pop())
274+
if C::FORCE_INVALIDATION_ON_CACHE_EVICTION {
275+
revisions.durability = Durability::MIN;
276+
}
277+
278+
(new_value, revisions)
274279
}
275280
}

src/function/maybe_changed_after.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ where
7070
return VerifyResult::Changed;
7171
};
7272

73+
if C::FORCE_INVALIDATION_ON_CACHE_EVICTION && memo.value.is_none() {
74+
return VerifyResult::Changed;
75+
}
76+
7377
let can_shallow_update = self.shallow_verify_memo(zalsa, database_key_index, memo);
7478
if can_shallow_update.yes() && !memo.may_be_provisional() {
7579
self.update_shallow(zalsa, database_key_index, memo, can_shallow_update);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//! Tests that `force_invalidation_on_cache_eviction` causes dependent queries
2+
//! to be recomputed if our memo is missing, even if our result is unchanged.
3+
4+
mod common;
5+
use common::LogDatabase;
6+
use expect_test::expect;
7+
use salsa::{Durability, Setter};
8+
use test_log::test;
9+
10+
#[salsa::input]
11+
struct MyInput {
12+
field: (),
13+
}
14+
15+
#[salsa::tracked(force_invalidation_on_cache_eviction, lru = 1)]
16+
fn intermediate_result(db: &dyn LogDatabase, input: MyInput) {
17+
db.push_log(format!("intermediate_result({})", input.0.as_u32()));
18+
input.field(db);
19+
}
20+
21+
#[salsa::tracked]
22+
fn final_result(db: &dyn LogDatabase, input: MyInput) {
23+
db.push_log("final_result".to_string());
24+
intermediate_result(db, input);
25+
}
26+
27+
#[test]
28+
fn execute() {
29+
let mut db = common::LoggerDatabase::default();
30+
31+
let high = MyInput::builder(()).durability(Durability::HIGH).new(&db);
32+
let low = MyInput::new(&db, ());
33+
34+
final_result(&db, high);
35+
// on first run, both intermediate and final results were computed
36+
db.assert_logs(expect![[r#"
37+
[
38+
"final_result",
39+
"intermediate_result(0)",
40+
]"#]]);
41+
42+
// an intermediate result for a different input will evict the original memo
43+
// from the cache (because query's lru = 1) when revision is bumped
44+
intermediate_result(&db, low);
45+
db.assert_logs(expect![[r#"
46+
[
47+
"intermediate_result(1)",
48+
]"#]]);
49+
50+
// bump the revision, causing cache eviction
51+
low.set_field(&mut db).to(());
52+
53+
final_result(&db, high);
54+
// now, despite unchanged intermediate result, final result was recomputed
55+
// (because intermediate query is `force_invalidation_on_cache_eviction`)
56+
db.assert_logs(expect![[r#"
57+
[
58+
"final_result",
59+
"intermediate_result(0)",
60+
]"#]]);
61+
}

0 commit comments

Comments
 (0)