Skip to content

Commit baf2d65

Browse files
committed
remove high-durability values from interned LRU
1 parent 8d3455f commit baf2d65

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

src/interned.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,19 @@ where
350350
}
351351
}
352352

353-
// Record the maximum durability across all queries that intern this value.
354353
if let Some((_, stamp)) = zalsa_local.active_query() {
354+
let was_reusable = value_shared.is_reusable();
355+
356+
// Record the maximum durability across all queries that intern this value.
355357
value_shared.durability = std::cmp::max(value_shared.durability, stamp.durability);
358+
359+
// If the value is no longer reusable, i.e. the durability increased, remove it
360+
// from the LRU.
361+
if was_reusable && !value_shared.is_reusable() {
362+
// SAFETY: We hold the lock for the shard containing the value, and `value`
363+
// was previously reusable, so is in the list.
364+
unsafe { shard.lru.cursor_mut_from_ptr(value).remove() };
365+
}
356366
}
357367

358368
// Record a dependency on the value.
@@ -415,7 +425,8 @@ where
415425
//
416426
// If the ID is at its maximum generation, we are forced to leak the slot.
417427
let Some(new_id) = value_shared.id.next_generation() else {
418-
// Remove the value from the LRU list.
428+
// Remove the value from the LRU list as we will never be able to
429+
// collect it.
419430
cursor.remove().unwrap();
420431

421432
// Retry with the previous element.

tests/interned-revisions.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
mod common;
55
use common::LogDatabase;
66
use expect_test::expect;
7-
use salsa::{Database, Setter};
7+
use salsa::{Database, Durability, Setter};
88
use test_log::test;
99

1010
#[salsa::input]
@@ -342,3 +342,47 @@ fn test_reuse_multiple_interned_input() {
342342
let result = use_nested_interned(&db, nested_interned);
343343
assert_eq!(result, 2);
344344
}
345+
346+
#[test]
347+
fn test_durability_increase() {
348+
#[salsa::tracked]
349+
fn intern<'db>(db: &'db dyn Database, input: Input, value: usize) -> Interned<'db> {
350+
let _f = input.field1(db);
351+
Interned::new(db, BadHash(value))
352+
}
353+
354+
let mut db = common::EventLoggerDatabase::default();
355+
356+
let high_durability = Input::builder(0).durability(Durability::HIGH).new(&db);
357+
let low_durability = Input::builder(1).durability(Durability::LOW).new(&db);
358+
359+
// Intern `i0`.
360+
let _i0 = intern(&db, low_durability, 0);
361+
// Re-intern `i0`, this time using a high-durability.
362+
let _i0 = intern(&db, high_durability, 0);
363+
364+
// Get the garbage collector to consider `i0` stale.
365+
for _ in 0..100 {
366+
let _dummy = intern(&db, low_durability, 1000).field1(&db);
367+
db.synthetic_write(Durability::LOW);
368+
}
369+
370+
// Intern `i1`.
371+
//
372+
// The slot of `i0` should not be reused as it is high-durability, and there
373+
// were no high-durability writes.
374+
let _i1 = intern(&db, low_durability, 1);
375+
376+
// Re-intern and read `i0`.
377+
//
378+
// If the slot was reused, the memo would be shallow-verified and we would
379+
// read `i1` incorrectly.
380+
let value = intern(&db, high_durability, 0);
381+
assert_eq!(value.field1(&db).0, 0);
382+
383+
db.synthetic_write(Durability::LOW);
384+
385+
// We should have the same issue even after a low-durability write.
386+
let value = intern(&db, high_durability, 0);
387+
assert_eq!(value.field1(&db).0, 0);
388+
}

0 commit comments

Comments
 (0)