mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-01-23 13:10:19 +00:00
fad97eeb6a
This had two unexpected consequences, one unfortunate, one "medium": * All `salsa::Database` must be `'static`. This falls out from `Q::DynDb` not having access to any lifetimes, but also the defaulting rules for `dyn QueryGroup` that make it `dyn QueryGroup + 'static`. We don't really support generic databases anyway yet so this isn't a big deal, and we can add workarounds later (ideally via GATs). * It is now statically impossible to invoke `snapshot` from a query, and so we don't need to test that it panics. This is because the signature of `snapshot` returns a `Snapshot<Self>` and that is not accessible to a `dyn QueryGroup` type. Similarly, invoking `Runtime::snapshot` directly is not possible becaues it is crate-private. So I removed the test. This seems ok, but eventually I would like to expose ways for queries to do parallel execution (matklad and I had talked about a "speculation" primitive for enabling that). * This commit is 99% boilerplate I did with search-and-replace. I also rolled in a few other changes I might have preferred to factor out, most notably removing the `GetQueryTable` plumbing trait in favor of free-methods, but it was awkward to factor them out and get all the generics right (so much simpler in this version).
194 lines
6.2 KiB
Rust
194 lines
6.2 KiB
Rust
use crate::db;
|
|
use salsa::debug::DebugQueryTable;
|
|
use salsa::{Database, Durability, InternId, SweepStrategy};
|
|
|
|
/// Query group for tests for how interned keys interact with GC.
|
|
#[salsa::query_group(Intern)]
|
|
pub(crate) trait InternDatabase {
|
|
/// A dummy input that can be used to trigger a new revision.
|
|
#[salsa::input]
|
|
fn dummy(&self) -> ();
|
|
|
|
/// Underlying interning query.
|
|
#[salsa::interned]
|
|
fn intern_str(&self, x: &'static str) -> InternId;
|
|
|
|
/// This just executes the intern query and returns the result.
|
|
fn repeat_intern1(&self, x: &'static str) -> InternId;
|
|
|
|
/// Same as `repeat_intern1`. =)
|
|
fn repeat_intern2(&self, x: &'static str) -> InternId;
|
|
}
|
|
|
|
fn repeat_intern1(db: &dyn InternDatabase, x: &'static str) -> InternId {
|
|
db.intern_str(x)
|
|
}
|
|
|
|
fn repeat_intern2(db: &dyn InternDatabase, x: &'static str) -> InternId {
|
|
db.intern_str(x)
|
|
}
|
|
|
|
/// This test highlights the difference between *interned queries* and
|
|
/// other non-input queries -- in particular, their results are not
|
|
/// *deterministic*. Therefore, we cannot GC values that were created
|
|
/// in the current revision; that might cause us to re-execute the
|
|
/// query twice on the same key during the same revision, which could
|
|
/// yield different results each time, wreaking havoc. This test
|
|
/// exercises precisely that scenario.
|
|
#[test]
|
|
fn discard_during_same_revision() {
|
|
let db = db::DatabaseImpl::default();
|
|
|
|
// This will assign index 0 for "foo".
|
|
let foo1a = db.repeat_intern1("foo");
|
|
|
|
// If we are not careful, this would remove the interned key for
|
|
// "foo".
|
|
InternStrQuery.in_db(&db).sweep(
|
|
SweepStrategy::default()
|
|
.discard_everything()
|
|
.sweep_all_revisions(),
|
|
);
|
|
|
|
// This would then reuse index 0 for "bar".
|
|
let bar1 = db.intern_str("bar");
|
|
|
|
// And here we would assign index *1* to "foo".
|
|
let foo2 = db.repeat_intern2("foo");
|
|
|
|
// But we would still have a cached result, *from the same
|
|
// revision*, with the value 0. So that's inconsistent.
|
|
let foo1b = db.repeat_intern1("foo");
|
|
|
|
assert_ne!(foo2, bar1);
|
|
assert_eq!(foo1a, foo1b);
|
|
assert_eq!(foo1b, foo2);
|
|
}
|
|
|
|
/// This test highlights the difference between *interned queries* and
|
|
/// other non-input queries -- in particular, their results are not
|
|
/// *deterministic*. Therefore, we cannot GC values that were created
|
|
/// in the current revision; that might cause us to re-execute the
|
|
/// query twice on the same key during the same revision, which could
|
|
/// yield different results each time, wreaking havoc. This test
|
|
/// exercises precisely that scenario.
|
|
#[test]
|
|
fn discard_outdated() {
|
|
let mut db = db::DatabaseImpl::default();
|
|
|
|
let foo_from_rev0 = db.repeat_intern1("foo");
|
|
let bar_from_rev0 = db.repeat_intern1("bar");
|
|
|
|
// Trigger a new revision.
|
|
db.salsa_runtime_mut().synthetic_write(Durability::HIGH);
|
|
|
|
// In this revision, we use "bar".
|
|
let bar_from_rev1 = db.repeat_intern1("bar");
|
|
|
|
// This should collect "foo".
|
|
db.sweep_all(SweepStrategy::discard_outdated());
|
|
|
|
// This should be the same as before the GC, as bar
|
|
// is not outdated.
|
|
let bar2_from_rev1 = db.repeat_intern1("bar");
|
|
|
|
// This should re-use the index of "foo".
|
|
let baz_from_rev1 = db.repeat_intern1("baz");
|
|
|
|
// This should assign the next index to "foo".
|
|
let foo_from_rev1 = db.repeat_intern1("foo");
|
|
|
|
assert_eq!(bar_from_rev0, bar_from_rev1);
|
|
assert_eq!(bar_from_rev0, bar2_from_rev1);
|
|
|
|
assert_eq!(foo_from_rev0, baz_from_rev1);
|
|
|
|
assert_ne!(foo_from_rev0, foo_from_rev1);
|
|
assert_ne!(foo_from_rev1, bar_from_rev1);
|
|
assert_ne!(foo_from_rev1, baz_from_rev1);
|
|
|
|
assert_eq!(db.lookup_intern_str(foo_from_rev1), "foo");
|
|
assert_eq!(db.lookup_intern_str(bar_from_rev1), "bar");
|
|
assert_eq!(db.lookup_intern_str(baz_from_rev1), "baz");
|
|
}
|
|
|
|
/// Variation on `discard_during_same_revision` --- here we show that
|
|
/// a synthetic write of level LOW isn't enough to collect interned
|
|
/// keys (which are considered durability HIGH).
|
|
#[test]
|
|
fn discard_durability_after_synthetic_write_low() {
|
|
let mut db = db::DatabaseImpl::default();
|
|
|
|
// This will assign index 0 for "foo".
|
|
let foo1a = db.repeat_intern1("foo");
|
|
assert_eq!(
|
|
Durability::HIGH,
|
|
RepeatIntern1Query.in_db(&db).durability("foo")
|
|
);
|
|
|
|
// Trigger a new revision.
|
|
db.salsa_runtime_mut().synthetic_write(Durability::LOW);
|
|
|
|
// If we are not careful, this would remove the interned key for
|
|
// "foo".
|
|
InternStrQuery.in_db(&db).sweep(
|
|
SweepStrategy::default()
|
|
.discard_everything()
|
|
.sweep_all_revisions(),
|
|
);
|
|
|
|
// This would then reuse index 0 for "bar".
|
|
let bar1 = db.intern_str("bar");
|
|
|
|
// And here we would assign index *1* to "foo".
|
|
let foo2 = db.repeat_intern2("foo");
|
|
|
|
// But we would still have a cached result with the value 0 and
|
|
// with high durability, so we can reuse it. That gives an
|
|
// inconsistent result.
|
|
let foo1b = db.repeat_intern1("foo");
|
|
|
|
assert_ne!(foo2, bar1);
|
|
assert_eq!(foo1a, foo1b);
|
|
assert_eq!(foo1b, foo2);
|
|
}
|
|
|
|
/// Variation on previous test in which we do a synthetic write to
|
|
/// `Durability::HIGH`.
|
|
#[test]
|
|
fn discard_durability_after_synthetic_write_high() {
|
|
let mut db = db::DatabaseImpl::default();
|
|
|
|
// This will assign index 0 for "foo".
|
|
let foo1a = db.repeat_intern1("foo");
|
|
assert_eq!(
|
|
Durability::HIGH,
|
|
RepeatIntern1Query.in_db(&db).durability("foo")
|
|
);
|
|
|
|
// Trigger a new revision -- marking even high things as having changed.
|
|
db.salsa_runtime_mut().synthetic_write(Durability::HIGH);
|
|
|
|
// We are now able to collect "collect".
|
|
InternStrQuery.in_db(&db).sweep(
|
|
SweepStrategy::default()
|
|
.discard_everything()
|
|
.sweep_all_revisions(),
|
|
);
|
|
|
|
// So we can reuse index 0 for "bar".
|
|
let bar1 = db.intern_str("bar");
|
|
|
|
// And here we assign index *1* to "foo".
|
|
let foo2 = db.repeat_intern2("foo");
|
|
let foo1b = db.repeat_intern1("foo");
|
|
|
|
// Thus foo1a (from before the synthetic write) and foo1b (from
|
|
// after) are different.
|
|
assert_ne!(foo1a, foo1b);
|
|
|
|
// But the things that come after the synthetic write are
|
|
// consistent.
|
|
assert_ne!(foo2, bar1);
|
|
assert_eq!(foo1b, foo2);
|
|
}
|