mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-01-22 21:05:11 +00:00
Merge #349
349: GC tracked structs r=nikomatsakis a=nikomatsakis Extends the system to track when a tracked struct is no longer created in a new revision and eagerly delete data associated with it. It's not a *complete* answer for GC but it seems pretty useful. Fix #315 Co-authored-by: Niko Matsakis <niko@alum.mit.edu>
This commit is contained in:
commit
c24ab8ffc1
26 changed files with 676 additions and 132 deletions
|
@ -116,7 +116,7 @@ fn ingredients_for_impl(args: &Args, struct_ty: &syn::Type, data_ty: &syn::Type)
|
|||
where
|
||||
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
|
||||
{
|
||||
let index = routes.push_mut(
|
||||
let index = routes.push(
|
||||
|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
|
||||
<_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar)
|
||||
|
|
|
@ -150,6 +150,11 @@ fn has_jars_dyn_impl(input: &syn::ItemStruct, storage: &syn::Ident) -> syn::Item
|
|||
let ingredient = self.#storage.ingredient(stale_output.ingredient_index());
|
||||
ingredient.remove_stale_output(self, executor, stale_output.key_index());
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, ingredient: salsa::IngredientIndex, id: salsa::Id) {
|
||||
let ingredient = self.#storage.ingredient(ingredient);
|
||||
ingredient.salsa_struct_deleted(self, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,6 +159,11 @@ impl InputStruct {
|
|||
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
|
||||
&ingredients.#all_field_indices
|
||||
},
|
||||
|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
|
||||
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
|
||||
&mut ingredients.#all_field_indices
|
||||
},
|
||||
);
|
||||
salsa::input_field::InputFieldIngredient::new(index)
|
||||
},
|
||||
|
@ -170,6 +175,11 @@ impl InputStruct {
|
|||
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
|
||||
&ingredients.#input_index
|
||||
},
|
||||
|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
|
||||
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
|
||||
&mut ingredients.#input_index
|
||||
},
|
||||
);
|
||||
salsa::input::InputIngredient::new(index)
|
||||
},
|
||||
|
@ -205,6 +215,10 @@ impl InputStruct {
|
|||
where
|
||||
DB: ?Sized + salsa::DbWithJar<#jar_ty>,
|
||||
{
|
||||
fn register_dependent_fn(_db: &DB, _index: salsa::routes::IngredientIndex) {
|
||||
// Do nothing here, at least for now.
|
||||
// If/when we add ability to delete inputs, this would become relevant.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,6 +136,10 @@ impl InternedStruct {
|
|||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
|
||||
<_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar)
|
||||
},
|
||||
|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
|
||||
<_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar)
|
||||
},
|
||||
);
|
||||
salsa::interned::InternedIngredient::new(index)
|
||||
}
|
||||
|
@ -152,6 +156,10 @@ impl InternedStruct {
|
|||
where
|
||||
DB: ?Sized + salsa::DbWithJar<#jar_ty>,
|
||||
{
|
||||
fn register_dependent_fn(_db: &DB, _index: salsa::routes::IngredientIndex) {
|
||||
// Do nothing here, at least for now.
|
||||
// If/when we add ability to delete inputs, this would become relevant.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,12 +205,20 @@ fn ingredients_for_impl(
|
|||
let intern_map: syn::Expr = if requires_interning(item_fn) {
|
||||
parse_quote! {
|
||||
{
|
||||
let index = routes.push(|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
|
||||
let ingredients =
|
||||
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient(jar);
|
||||
&ingredients.intern_map
|
||||
});
|
||||
let index = routes.push(
|
||||
|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
|
||||
let ingredients =
|
||||
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient(jar);
|
||||
&ingredients.intern_map
|
||||
},
|
||||
|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
|
||||
let ingredients =
|
||||
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient_mut(jar);
|
||||
&mut ingredients.intern_map
|
||||
}
|
||||
);
|
||||
salsa::interned::InternedIngredient::new(index)
|
||||
}
|
||||
}
|
||||
|
@ -233,12 +241,19 @@ fn ingredients_for_impl(
|
|||
intern_map: #intern_map,
|
||||
|
||||
function: {
|
||||
let index = routes.push(|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
|
||||
let ingredients =
|
||||
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient(jar);
|
||||
&ingredients.function
|
||||
});
|
||||
let index = routes.push(
|
||||
|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
|
||||
let ingredients =
|
||||
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient(jar);
|
||||
&ingredients.function
|
||||
},
|
||||
|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
|
||||
let ingredients =
|
||||
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient_mut(jar);
|
||||
&mut ingredients.function
|
||||
});
|
||||
salsa::function::FunctionIngredient::new(index)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -182,12 +182,17 @@ impl TrackedStruct {
|
|||
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
|
||||
&ingredients.#value_field_indices
|
||||
},
|
||||
|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
|
||||
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
|
||||
&mut ingredients.#value_field_indices
|
||||
},
|
||||
);
|
||||
salsa::function::FunctionIngredient::new(index)
|
||||
},
|
||||
)*
|
||||
{
|
||||
let index = routes.push_mut(
|
||||
let index = routes.push(
|
||||
|jars| {
|
||||
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
|
||||
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
|
||||
|
@ -211,11 +216,17 @@ impl TrackedStruct {
|
|||
fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl {
|
||||
let ident = self.id_ident();
|
||||
let jar_ty = self.jar_ty();
|
||||
let tracked_struct_index: Literal = self.tracked_struct_index();
|
||||
parse_quote! {
|
||||
impl<DB> salsa::salsa_struct::SalsaStructInDb<DB> for #ident
|
||||
where
|
||||
DB: ?Sized + salsa::DbWithJar<#jar_ty>,
|
||||
{
|
||||
fn register_dependent_fn(db: &DB, index: salsa::routes::IngredientIndex) {
|
||||
let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
|
||||
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar);
|
||||
ingredients.#tracked_struct_index.register_dependent_fn(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
hash::FxDashMap,
|
||||
ingredient::{Ingredient, MutIngredient},
|
||||
ingredient::{Ingredient, IngredientRequiresReset},
|
||||
key::DependencyIndex,
|
||||
runtime::{local_state::QueryOrigin, StampedValue},
|
||||
storage::HasJar,
|
||||
|
@ -96,14 +96,19 @@ where
|
|||
// FIXME
|
||||
drop((executor, stale_output_key));
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
panic!("unexpected reset on accumulator")
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) {
|
||||
panic!("unexpected call: accumulator is not registered as a dependent fn");
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB: ?Sized, Data> MutIngredient<DB> for AccumulatorIngredient<Data>
|
||||
impl<Data> IngredientRequiresReset for AccumulatorIngredient<Data>
|
||||
where
|
||||
Data: Clone,
|
||||
{
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
// FIXME: We could certainly drop things here if we knew which ones
|
||||
// to drop. There's a fixed point algorithm we could be doing.
|
||||
}
|
||||
const RESET_ON_NEW_REVISION: bool = false;
|
||||
}
|
||||
|
|
|
@ -83,6 +83,12 @@ pub enum EventKind {
|
|||
/// Key for the query that is no longer output
|
||||
output_key: DatabaseKeyIndex,
|
||||
},
|
||||
|
||||
/// Tracked structs or memoized data were discarded (freed).
|
||||
DidDiscard {
|
||||
/// Value being discarded.
|
||||
key: DatabaseKeyIndex,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Debug for EventKind {
|
||||
|
@ -113,6 +119,9 @@ impl fmt::Debug for EventKind {
|
|||
.field("execute_key", &execute_key)
|
||||
.field("output_key", &output_key)
|
||||
.finish(),
|
||||
EventKind::DidDiscard { key } => {
|
||||
fmt.debug_struct("DidDiscard").field("key", &key).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +157,10 @@ where
|
|||
.field("execute_key", &execute_key.debug(db))
|
||||
.field("output_key", &output_key.debug(db))
|
||||
.finish(),
|
||||
EventKind::DidDiscard { key } => fmt
|
||||
.debug_struct("DidDiscard")
|
||||
.field("key", &key.debug(db))
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use crossbeam::queue::SegQueue;
|
||||
use crossbeam::{atomic::AtomicCell, queue::SegQueue};
|
||||
|
||||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
ingredient::MutIngredient,
|
||||
ingredient::IngredientRequiresReset,
|
||||
jar::Jar,
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
runtime::local_state::QueryOrigin,
|
||||
salsa_struct::SalsaStructInDb,
|
||||
Cycle, DbWithJar, Id, Revision,
|
||||
Cycle, DbWithJar, Event, EventKind, Id, Revision,
|
||||
};
|
||||
|
||||
use super::{ingredient::Ingredient, routes::IngredientIndex, AsId};
|
||||
|
@ -67,6 +67,10 @@ pub struct FunctionIngredient<C: Configuration> {
|
|||
/// we don't know that we can trust the database to give us the same runtime
|
||||
/// everytime and so forth.
|
||||
deleted_entries: SegQueue<ArcSwap<memo::Memo<C::Value>>>,
|
||||
|
||||
/// Set to true once we invoke `register_dependent_fn` for `C::SalsaStruct`.
|
||||
/// Prevents us from registering more than once.
|
||||
registered: AtomicCell<bool>,
|
||||
}
|
||||
|
||||
pub trait Configuration {
|
||||
|
@ -140,6 +144,7 @@ where
|
|||
lru: Default::default(),
|
||||
sync_map: Default::default(),
|
||||
deleted_entries: Default::default(),
|
||||
registered: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,7 +174,13 @@ where
|
|||
std::mem::transmute(memo_value)
|
||||
}
|
||||
|
||||
fn insert_memo(&self, key: C::Key, memo: memo::Memo<C::Value>) -> Option<&C::Value> {
|
||||
fn insert_memo(
|
||||
&self,
|
||||
db: &DynDb<'_, C>,
|
||||
key: C::Key,
|
||||
memo: memo::Memo<C::Value>,
|
||||
) -> Option<&C::Value> {
|
||||
self.register(db);
|
||||
let memo = Arc::new(memo);
|
||||
let value = unsafe {
|
||||
// Unsafety conditions: memo must be in the map (it's not yet, but it will be by the time this
|
||||
|
@ -183,6 +194,15 @@ where
|
|||
}
|
||||
value
|
||||
}
|
||||
|
||||
/// Register this function as a dependent fn of the given salsa struct.
|
||||
/// When instances of that salsa struct are deleted, we'll get a callback
|
||||
/// so we can remove any data keyed by them.
|
||||
fn register(&self, db: &DynDb<'_, C>) {
|
||||
if !self.registered.fetch_or(true) {
|
||||
<C::SalsaStruct as SalsaStructInDb<_>>::register_dependent_fn(db, self.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB, C> Ingredient<DB> for FunctionIngredient<C>
|
||||
|
@ -220,14 +240,35 @@ where
|
|||
// but not in rev 2. We don't do anything in this case, we just leave the (now stale) memo.
|
||||
// Since its `verified_at` field has not changed, it will be considered dirty if it is invoked.
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB, C> MutIngredient<DB> for FunctionIngredient<C>
|
||||
where
|
||||
DB: ?Sized + DbWithJar<C::Jar>,
|
||||
C: Configuration,
|
||||
{
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
std::mem::take(&mut self.deleted_entries);
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, db: &DB, id: crate::Id) {
|
||||
// Remove any data keyed by `id`, since `id` no longer
|
||||
// exists in this revision.
|
||||
|
||||
let id: C::Key = C::key_from_id(id);
|
||||
if let Some(origin) = self.delete_memo(id) {
|
||||
let key = self.database_key_index(id);
|
||||
db.salsa_event(Event {
|
||||
runtime_id: db.salsa_runtime().id(),
|
||||
kind: EventKind::DidDiscard { key },
|
||||
});
|
||||
|
||||
// Anything that was output by this memoized execution
|
||||
// is now itself stale.
|
||||
for stale_output in origin.outputs() {
|
||||
db.remove_stale_output(key, stale_output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> IngredientRequiresReset for FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
const RESET_ON_NEW_REVISION: bool = true;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use crate::runtime::local_state::QueryOrigin;
|
||||
|
||||
use super::{Configuration, FunctionIngredient};
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
|
@ -6,9 +8,13 @@ where
|
|||
{
|
||||
/// Removes the memoized value for `key` from the memo-map.
|
||||
/// Pushes the memo onto `deleted_entries` to ensure that any references into that memo which were handed out remain valid.
|
||||
pub(super) fn delete_memo(&self, key: C::Key) {
|
||||
pub(super) fn delete_memo(&self, key: C::Key) -> Option<QueryOrigin> {
|
||||
if let Some(memo) = self.memo_map.remove(key) {
|
||||
let origin = memo.load().revisions.origin.clone();
|
||||
self.deleted_entries.push(memo);
|
||||
Some(origin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
key::DependencyIndex, runtime::local_state::QueryRevisions, storage::HasJarsDyn, Database,
|
||||
DatabaseKeyIndex, Event, EventKind,
|
||||
runtime::local_state::QueryRevisions, storage::HasJarsDyn, Database, DatabaseKeyIndex, Event,
|
||||
EventKind,
|
||||
};
|
||||
|
||||
use super::{memo::Memo, Configuration, DynDb, FunctionIngredient};
|
||||
|
|
|
@ -91,7 +91,11 @@ where
|
|||
}
|
||||
|
||||
let value = self
|
||||
.insert_memo(key, Memo::new(Some(value), revision_now, revisions.clone()))
|
||||
.insert_memo(
|
||||
db,
|
||||
key,
|
||||
Memo::new(Some(value), revision_now, revisions.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let stamped_value = revisions.stamped_value(value);
|
||||
|
|
|
@ -75,7 +75,7 @@ where
|
|||
};
|
||||
|
||||
log::debug!("specify: about to add memo {:#?} for key {:?}", memo, key);
|
||||
self.insert_memo(key, memo);
|
||||
self.insert_memo(db, key, memo);
|
||||
}
|
||||
|
||||
/// Specify the value for `key` but do not record it is an output.
|
||||
|
|
|
@ -33,13 +33,28 @@ pub trait Ingredient<DB: ?Sized> {
|
|||
///
|
||||
/// This hook is used to clear out the stale value so others cannot read it.
|
||||
fn remove_stale_output(&self, db: &DB, executor: DatabaseKeyIndex, stale_output_key: Id);
|
||||
}
|
||||
|
||||
/// Optional trait for ingredients that wish to be notified when new revisions are
|
||||
/// about to occur. If ingredients wish to receive these method calls,
|
||||
/// they need to indicate that by invoking [`Routes::push_mut`] during initialization.
|
||||
pub trait MutIngredient<DB: ?Sized>: Ingredient<DB> {
|
||||
/// Invoked when a new revision is about to start. This gives ingredients
|
||||
/// a chance to flush data and so forth.
|
||||
/// Informs the ingredient `self` that the salsa struct with id `id` has been deleted.
|
||||
/// This gives `self` a chance to remove any memoized data dependent on `id`.
|
||||
/// To receive this callback, `self` must register itself as a dependent function using
|
||||
/// [`SalsaStructInDb::register_dependent_fn`](`crate::salsa_struct::SalsaStructInDb::register_dependent_fn`).
|
||||
fn salsa_struct_deleted(&self, db: &DB, id: Id);
|
||||
|
||||
/// Invoked when a new revision is about to start.
|
||||
/// This moment is important because it means that we have an `&mut`-reference to the database,
|
||||
/// and hence any pre-existing `&`-references must have expired.
|
||||
/// Many ingredients, given an `&'db`-reference to the database,
|
||||
/// use unsafe code to return `&'db`-references to internal values.
|
||||
/// The backing memory for those values can only be freed once an `&mut`-reference to the database is created.
|
||||
///
|
||||
/// **Important:** to actually receive resets, the ingredient must set
|
||||
/// [`IngredientRequiresReset::RESET_ON_NEW_REVISION`] to true.
|
||||
fn reset_for_new_revision(&mut self);
|
||||
}
|
||||
|
||||
/// Defines a const indicating if an ingredient needs to be reset each round.
|
||||
/// This const probably *should* be a member of `Ingredient` trait but then `Ingredient` would not be dyn-safe.
|
||||
pub trait IngredientRequiresReset {
|
||||
/// If this is true, then `reset_for_new_revision` will be called every new revision.
|
||||
const RESET_ON_NEW_REVISION: bool;
|
||||
}
|
||||
|
|
83
components/salsa-2022/src/ingredient_list.rs
Normal file
83
components/salsa-2022/src/ingredient_list.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use arc_swap::{ArcSwapOption, AsRaw};
|
||||
|
||||
use crate::IngredientIndex;
|
||||
|
||||
/// A list of ingredients that can be added to in parallel.
|
||||
pub(crate) struct IngredientList {
|
||||
/// A list of each tracked functions.
|
||||
/// tracked struct.
|
||||
///
|
||||
/// Whenever an instance `i` of this struct is deleted,
|
||||
/// each of these functions will be notified
|
||||
/// so they can remove any data tied to that instance.
|
||||
list: ArcSwapOption<Vec<IngredientIndex>>,
|
||||
}
|
||||
|
||||
impl IngredientList {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
list: ArcSwapOption::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the items in the list.
|
||||
/// This is a snapshot of the list as it was when this function is called.
|
||||
/// Items could still be added in parallel via `add_ingredient`
|
||||
/// that will not be returned by this iterator.
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = IngredientIndex> {
|
||||
let guard = self.list.load();
|
||||
let mut index = 0;
|
||||
std::iter::from_fn(move || match &*guard {
|
||||
Some(list) if index < list.len() => {
|
||||
let r = list[index];
|
||||
index += 1;
|
||||
Some(r)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Adds an ingredient to the list (if not already present).
|
||||
pub(crate) fn push(&self, index: IngredientIndex) {
|
||||
// This function is called whenever a value is stored,
|
||||
// so other tracked functions and things may be executing,
|
||||
// and there could even be two calls to this function in parallel.
|
||||
//
|
||||
// We use a "compare-and-swap" strategy of reading the old vector, creating a new vector,
|
||||
// and then installing it, hoping that nobody has conflicted with us.
|
||||
// If that fails, we start over.
|
||||
|
||||
loop {
|
||||
let guard = self.list.load();
|
||||
let empty_vec = vec![];
|
||||
let old_vec = match &*guard {
|
||||
Some(v) => v,
|
||||
None => &empty_vec,
|
||||
};
|
||||
|
||||
// First check whether the index is already present.
|
||||
if old_vec.contains(&index) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If not, construct a new vector that has all the old values, followed by `index`.
|
||||
let vec: Arc<Vec<IngredientIndex>> = Arc::new(
|
||||
old_vec
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(std::iter::once(index))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
// Try to replace the old vector with the new one. If we fail, loop around again.
|
||||
assert_eq!(vec.len(), vec.capacity());
|
||||
let previous = self.list.compare_and_swap(&guard, Some(vec));
|
||||
if guard.as_raw() == previous.as_raw() {
|
||||
// swap was successful
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
ingredient::Ingredient,
|
||||
ingredient::{Ingredient, IngredientRequiresReset},
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
runtime::{local_state::QueryOrigin, Runtime},
|
||||
AsId, IngredientIndex, Revision,
|
||||
|
@ -80,4 +80,21 @@ where
|
|||
executor, stale_output_key
|
||||
);
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
panic!("unexpected call to `reset_for_new_revision`")
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) {
|
||||
panic!(
|
||||
"unexpected call: input ingredients do not register for salsa struct deletion events"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Id> IngredientRequiresReset for InputIngredient<Id>
|
||||
where
|
||||
Id: InputId,
|
||||
{
|
||||
const RESET_ON_NEW_REVISION: bool = false;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::cycle::CycleRecoveryStrategy;
|
||||
use crate::ingredient::Ingredient;
|
||||
use crate::ingredient::{Ingredient, IngredientRequiresReset};
|
||||
use crate::key::DependencyIndex;
|
||||
use crate::runtime::local_state::QueryOrigin;
|
||||
use crate::runtime::StampedValue;
|
||||
|
@ -93,4 +93,19 @@ where
|
|||
fn mark_validated_output(&self, _db: &DB, _executor: DatabaseKeyIndex, _output_key: Id) {}
|
||||
|
||||
fn remove_stale_output(&self, _db: &DB, _executor: DatabaseKeyIndex, _stale_output_key: Id) {}
|
||||
|
||||
fn salsa_struct_deleted(&self, _db: &DB, _id: Id) {
|
||||
panic!("unexpected call: input fields are never deleted");
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
panic!("unexpected call: input fields don't register for resets");
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, F> IngredientRequiresReset for InputFieldIngredient<K, F>
|
||||
where
|
||||
K: AsId,
|
||||
{
|
||||
const RESET_ON_NEW_REVISION: bool = false;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::marker::PhantomData;
|
|||
|
||||
use crate::durability::Durability;
|
||||
use crate::id::AsId;
|
||||
use crate::ingredient::IngredientRequiresReset;
|
||||
use crate::key::DependencyIndex;
|
||||
use crate::runtime::local_state::QueryOrigin;
|
||||
use crate::runtime::Runtime;
|
||||
|
@ -217,6 +218,25 @@ where
|
|||
executor, stale_output_key
|
||||
);
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
// Interned ingredients do not, normally, get deleted except when they are "reset" en masse.
|
||||
// There ARE methods (e.g., `clear_deleted_entries` and `remove`) for deleting individual
|
||||
// items, but those are only used for tracked struct ingredients.
|
||||
panic!("unexpected call to `reset_for_new_revision`")
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) {
|
||||
panic!("unexpected call: interned ingredients do not register for salsa struct deletion events");
|
||||
}
|
||||
}
|
||||
|
||||
impl<Id, Data> IngredientRequiresReset for InternedIngredient<Id, Data>
|
||||
where
|
||||
Id: InternedId,
|
||||
Data: InternedData,
|
||||
{
|
||||
const RESET_ON_NEW_REVISION: bool = false;
|
||||
}
|
||||
|
||||
pub struct IdentityInterner<Id: AsId> {
|
||||
|
|
|
@ -9,6 +9,7 @@ pub mod function;
|
|||
pub mod hash;
|
||||
pub mod id;
|
||||
pub mod ingredient;
|
||||
pub mod ingredient_list;
|
||||
pub mod input;
|
||||
pub mod input_field;
|
||||
pub mod interned;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use super::{
|
||||
ingredient::{Ingredient, MutIngredient},
|
||||
storage::HasJars,
|
||||
};
|
||||
use crate::ingredient::IngredientRequiresReset;
|
||||
|
||||
use super::{ingredient::Ingredient, storage::HasJars};
|
||||
|
||||
/// An ingredient index identifies a particular [`Ingredient`] in the database.
|
||||
/// The database contains a number of jars, and each jar contains a number of ingredients.
|
||||
|
@ -39,7 +38,7 @@ pub type DynRoute<DB: HasJars> = dyn Fn(&DB::Jars) -> (&dyn Ingredient<DB>) + Se
|
|||
#[allow(type_alias_bounds)]
|
||||
#[allow(unused_parens)]
|
||||
pub type DynMutRoute<DB: HasJars> =
|
||||
dyn Fn(&mut DB::Jars) -> (&mut dyn MutIngredient<DB>) + Send + Sync;
|
||||
dyn Fn(&mut DB::Jars) -> (&mut dyn Ingredient<DB>) + Send + Sync;
|
||||
|
||||
/// The "routes" structure is used to navigate the database.
|
||||
/// The database contains a number of jars, and each jar contains a number of ingredients.
|
||||
|
@ -52,13 +51,10 @@ pub struct Routes<DB: HasJars> {
|
|||
/// Vector indexed by ingredient index. Yields the `DynRoute`,
|
||||
/// a function which can be applied to the `DB::Jars` to yield
|
||||
/// the `dyn Ingredient.
|
||||
routes: Vec<Box<DynRoute<DB>>>,
|
||||
routes: Vec<(Box<DynRoute<DB>>, Box<DynMutRoute<DB>>)>,
|
||||
|
||||
/// Vector if "mut routes". This vector is used to give callbacks
|
||||
/// when new revisions are trigged. It is not indexed by ingredient
|
||||
/// index as not every ingredient needs a callback; instead, you
|
||||
/// just iterate over it and invoke each fn to get a `MutIngredient`.
|
||||
mut_routes: Vec<Box<DynMutRoute<DB>>>,
|
||||
/// Indices of routes which need a 'reset' call.
|
||||
needs_reset: Vec<IngredientIndex>,
|
||||
}
|
||||
|
||||
impl<DB: HasJars> Routes<DB> {
|
||||
|
@ -66,7 +62,7 @@ impl<DB: HasJars> Routes<DB> {
|
|||
pub(super) fn new() -> Self {
|
||||
Routes {
|
||||
routes: vec![],
|
||||
mut_routes: vec![],
|
||||
needs_reset: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,52 +73,53 @@ impl<DB: HasJars> Routes<DB> {
|
|||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `requires_reset` -- if true, the [`Ingredient::reset_for_new_revision`] method will be called on this ingredient
|
||||
/// at each new revision. See that method for more information.
|
||||
/// * `route` -- a closure which, given a database, will identify the ingredient.
|
||||
/// This closure will be invoked to dispatch calls to `maybe_changed_after`.
|
||||
/// * `mut_route` -- an optional closure which identifies the ingredient in a mut
|
||||
/// * `mut_route` -- a closure which identifies the ingredient in a mut
|
||||
/// database.
|
||||
pub fn push(
|
||||
pub fn push<I>(
|
||||
&mut self,
|
||||
route: impl (Fn(&DB::Jars) -> &dyn Ingredient<DB>) + Send + Sync + 'static,
|
||||
) -> IngredientIndex {
|
||||
route: impl (Fn(&DB::Jars) -> &I) + Send + Sync + 'static,
|
||||
mut_route: impl (Fn(&mut DB::Jars) -> &mut I) + Send + Sync + 'static,
|
||||
) -> IngredientIndex
|
||||
where
|
||||
I: Ingredient<DB> + IngredientRequiresReset + 'static,
|
||||
{
|
||||
let len = self.routes.len();
|
||||
self.routes.push(Box::new(route));
|
||||
self.routes.push((
|
||||
Box::new(move |jars| route(jars)),
|
||||
Box::new(move |jars| mut_route(jars)),
|
||||
));
|
||||
let index = IngredientIndex::from(len);
|
||||
index
|
||||
}
|
||||
|
||||
/// As [`Self::push`] but for an ingredient that wants a callback whenever
|
||||
/// a new revision is published.
|
||||
/// Many ingredients will, given an `&'db dyn Db` reference,
|
||||
/// return a `&'db V` reference to one of their values.
|
||||
/// This forces those values to live as long as the `&dyn Db` is valid.
|
||||
/// This callback is invoked when a new revision begins.
|
||||
/// It allows those values that were accessed to be freed.
|
||||
/// We know it is safe to free them because, when a new revision starts,
|
||||
/// we have an `&mut dyn Db`, and hence the `&dyn Db` lifetime must have ended.
|
||||
pub fn push_mut(
|
||||
&mut self,
|
||||
route: impl (Fn(&DB::Jars) -> &dyn Ingredient<DB>) + Send + Sync + 'static,
|
||||
mut_route: impl (Fn(&mut DB::Jars) -> &mut dyn MutIngredient<DB>) + Send + Sync + 'static,
|
||||
) -> IngredientIndex {
|
||||
let index = self.push(route);
|
||||
self.mut_routes.push(Box::new(mut_route));
|
||||
if I::RESET_ON_NEW_REVISION {
|
||||
self.needs_reset.push(index);
|
||||
}
|
||||
|
||||
index
|
||||
}
|
||||
|
||||
/// Given an ingredient index, return the "route"
|
||||
/// (a function that, given a `Jars`, returns the ingredient).
|
||||
/// (a function that, given a `&Jars`, returns the ingredient).
|
||||
pub fn route(&self, index: IngredientIndex) -> &dyn Fn(&DB::Jars) -> &dyn Ingredient<DB> {
|
||||
&self.routes[index.as_usize()]
|
||||
&self.routes[index.as_usize()].0
|
||||
}
|
||||
|
||||
/// Returns the "mutable routes" for the purposes of giving callbacks when a new revision is published.
|
||||
/// Not all ingredients need callbacks, so this returns an iterator of just those that do.
|
||||
pub fn mut_routes(
|
||||
/// Given an ingredient index, return the "mut route"
|
||||
/// (a function that, given an `&mut Jars`, returns the ingredient).
|
||||
pub fn route_mut(
|
||||
&self,
|
||||
) -> impl Iterator<Item = &dyn Fn(&mut DB::Jars) -> &mut dyn MutIngredient<DB>> + '_ {
|
||||
self.mut_routes
|
||||
.iter()
|
||||
.map(|b| &**b as &dyn Fn(&mut DB::Jars) -> &mut dyn MutIngredient<DB>)
|
||||
index: IngredientIndex,
|
||||
) -> &dyn Fn(&mut DB::Jars) -> &mut dyn Ingredient<DB> {
|
||||
&self.routes[index.as_usize()].1
|
||||
}
|
||||
|
||||
/// Returns the mut routes for ingredients that need to be reset at the start of each revision.
|
||||
pub fn reset_routes(
|
||||
&self,
|
||||
) -> impl Iterator<Item = &dyn Fn(&mut DB::Jars) -> &mut dyn Ingredient<DB>> + '_ {
|
||||
self.needs_reset.iter().map(|&index| self.route_mut(index))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use crate::Database;
|
||||
use crate::{Database, IngredientIndex};
|
||||
|
||||
pub trait SalsaStructInDb<DB: ?Sized + Database> {}
|
||||
pub trait SalsaStructInDb<DB: ?Sized + Database> {
|
||||
fn register_dependent_fn(db: &DB, index: IngredientIndex);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::jar::Jar;
|
|||
use crate::key::DependencyIndex;
|
||||
use crate::runtime::local_state::QueryOrigin;
|
||||
use crate::runtime::Runtime;
|
||||
use crate::{Database, DatabaseKeyIndex, IngredientIndex};
|
||||
use crate::{Database, DatabaseKeyIndex, Id, IngredientIndex};
|
||||
|
||||
use super::routes::Routes;
|
||||
use super::{ParallelDatabase, Revision};
|
||||
|
@ -94,7 +94,7 @@ where
|
|||
|
||||
let routes = self.routes.clone();
|
||||
let shared = Arc::get_mut(&mut self.shared).unwrap();
|
||||
for route in routes.mut_routes() {
|
||||
for route in routes.reset_routes() {
|
||||
route(&mut shared.jars).reset_for_new_revision();
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,19 @@ pub trait HasJarsDyn {
|
|||
|
||||
fn mark_validated_output(&self, executor: DatabaseKeyIndex, output: DatabaseKeyIndex);
|
||||
|
||||
/// Invoked when `executor` used to output `stale_output` but no longer does.
|
||||
/// This method routes that into a call to the [`remove_stale_output`](`crate::ingredient::Ingredient::remove_stale_output`)
|
||||
/// method on the ingredient for `stale_output`.
|
||||
fn remove_stale_output(&self, executor: DatabaseKeyIndex, stale_output: DatabaseKeyIndex);
|
||||
|
||||
/// Informs `ingredient` that the salsa struct with id `id` has been deleted.
|
||||
/// This means that `id` will not be used in this revision and hence
|
||||
/// any memoized values keyed by that struct can be discarded.
|
||||
///
|
||||
/// In order to receive this callback, `ingredient` must have registered itself
|
||||
/// as a dependent function using
|
||||
/// [`SalsaStructInDb::register_dependent_fn`](`crate::salsa_struct::SalsaStructInDb::register_dependent_fn`).
|
||||
fn salsa_struct_deleted(&self, ingredient: IngredientIndex, id: Id);
|
||||
}
|
||||
|
||||
pub trait HasIngredientsFor<I>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
ingredient::{Ingredient, MutIngredient},
|
||||
ingredient::{Ingredient, IngredientRequiresReset},
|
||||
ingredient_list::IngredientList,
|
||||
interned::{InternedData, InternedId, InternedIngredient},
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
runtime::{local_state::QueryOrigin, Runtime},
|
||||
salsa_struct::SalsaStructInDb,
|
||||
Database, IngredientIndex, Revision,
|
||||
Database, Event, IngredientIndex, Revision,
|
||||
};
|
||||
|
||||
pub trait TrackedStructId: InternedId {}
|
||||
|
@ -33,6 +34,14 @@ where
|
|||
Data: TrackedStructData,
|
||||
{
|
||||
interned: InternedIngredient<Id, TrackedStructKey<Data>>,
|
||||
|
||||
/// A list of each tracked function `f` whose key is this
|
||||
/// tracked struct.
|
||||
///
|
||||
/// Whenever an instance `i` of this struct is deleted,
|
||||
/// each of these functions will be notified
|
||||
/// so they can remove any data tied to that instance.
|
||||
dependent_fns: IngredientList,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
|
||||
|
@ -53,6 +62,7 @@ where
|
|||
pub fn new(index: IngredientIndex) -> Self {
|
||||
Self {
|
||||
interned: InternedIngredient::new(index),
|
||||
dependent_fns: IngredientList::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,17 +104,33 @@ where
|
|||
/// Using this method on an entity id that MAY be used in the current revision will lead to
|
||||
/// unspecified results (but not UB). See [`InternedIngredient::delete_index`] for more
|
||||
/// discussion and important considerations.
|
||||
pub(crate) fn delete_entities(&self, _runtime: &Runtime, ids: impl Iterator<Item = Id>) {
|
||||
for id in ids {
|
||||
self.interned.delete_index(id);
|
||||
pub(crate) fn delete_entity(&self, db: &dyn crate::Database, id: Id) {
|
||||
db.salsa_event(Event {
|
||||
runtime_id: db.salsa_runtime().id(),
|
||||
kind: crate::EventKind::DidDiscard {
|
||||
key: self.database_key_index(id),
|
||||
},
|
||||
});
|
||||
|
||||
self.interned.delete_index(id);
|
||||
for dependent_fn in self.dependent_fns.iter() {
|
||||
db.salsa_struct_deleted(dependent_fn, id.as_id());
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a dependent function (one keyed by this tracked struct) to our list.
|
||||
/// When instances of this struct are deleted, these dependent functions
|
||||
/// will be notified.
|
||||
pub fn register_dependent_fn(&self, index: IngredientIndex) {
|
||||
self.dependent_fns.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB: ?Sized, Id, Data> Ingredient<DB> for TrackedStructIngredient<Id, Data>
|
||||
where
|
||||
Id: TrackedStructId,
|
||||
Data: TrackedStructData,
|
||||
DB: crate::Database,
|
||||
{
|
||||
fn maybe_changed_after(&self, db: &DB, input: DependencyIndex, revision: Revision) -> bool {
|
||||
self.interned.maybe_changed_after(db, input, revision)
|
||||
|
@ -125,22 +151,31 @@ where
|
|||
|
||||
fn remove_stale_output(
|
||||
&self,
|
||||
_db: &DB,
|
||||
executor: DatabaseKeyIndex,
|
||||
db: &DB,
|
||||
_executor: DatabaseKeyIndex,
|
||||
stale_output_key: crate::Id,
|
||||
) {
|
||||
let key: Id = Id::from_id(stale_output_key);
|
||||
// FIXME -- we can delete this entity
|
||||
drop((executor, key));
|
||||
// This method is called when, in prior revisions,
|
||||
// `executor` creates a tracked struct `salsa_output_key`,
|
||||
// but it did not in the current revision.
|
||||
// In that case, we can delete `stale_output_key` and any data associated with it.
|
||||
let stale_output_key: Id = Id::from_id(stale_output_key);
|
||||
self.delete_entity(db.as_salsa_database(), stale_output_key);
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
self.interned.clear_deleted_indices();
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) {
|
||||
panic!("unexpected call: interned ingredients do not register for salsa struct deletion events");
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB: ?Sized, Id, Data> MutIngredient<DB> for TrackedStructIngredient<Id, Data>
|
||||
impl<Id, Data> IngredientRequiresReset for TrackedStructIngredient<Id, Data>
|
||||
where
|
||||
Id: TrackedStructId,
|
||||
Data: TrackedStructData,
|
||||
{
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
self.interned.clear_deleted_indices();
|
||||
}
|
||||
const RESET_ON_NEW_REVISION: bool = true;
|
||||
}
|
||||
|
|
134
salsa-2022-tests/tests/deletion-cascade.rs
Normal file
134
salsa-2022-tests/tests/deletion-cascade.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
//! Delete cascade:
|
||||
//!
|
||||
//! * when we delete memoized data, also delete outputs from that data
|
||||
|
||||
use salsa::DebugWithDb;
|
||||
use salsa_2022_tests::{HasLogger, Logger};
|
||||
|
||||
use expect_test::expect;
|
||||
use test_log::test;
|
||||
|
||||
#[salsa::jar(db = Db)]
|
||||
struct Jar(
|
||||
MyInput,
|
||||
MyTracked,
|
||||
final_result,
|
||||
create_tracked_structs,
|
||||
contribution_from_struct,
|
||||
copy_field,
|
||||
);
|
||||
|
||||
trait Db: salsa::DbWithJar<Jar> + HasLogger {}
|
||||
|
||||
#[salsa::input]
|
||||
struct MyInput {
|
||||
field: u32,
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
fn final_result(db: &dyn Db, input: MyInput) -> u32 {
|
||||
db.push_log(format!("final_result({:?})", input));
|
||||
let mut sum = 0;
|
||||
for tracked_struct in create_tracked_structs(db, input) {
|
||||
sum += contribution_from_struct(db, tracked_struct);
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
struct MyTracked {
|
||||
field: u32,
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
fn create_tracked_structs(db: &dyn Db, input: MyInput) -> Vec<MyTracked> {
|
||||
db.push_log(format!("intermediate_result({:?})", input));
|
||||
(0..input.field(db))
|
||||
.map(|i| MyTracked::new(db, i))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
fn contribution_from_struct(db: &dyn Db, tracked: MyTracked) -> u32 {
|
||||
let m = MyTracked::new(db, tracked.field(db));
|
||||
copy_field(db, m) * 2
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
fn copy_field(db: &dyn Db, tracked: MyTracked) -> u32 {
|
||||
tracked.field(db)
|
||||
}
|
||||
|
||||
#[salsa::db(Jar)]
|
||||
#[derive(Default)]
|
||||
struct Database {
|
||||
storage: salsa::Storage<Self>,
|
||||
logger: Logger,
|
||||
}
|
||||
|
||||
impl salsa::Database for Database {
|
||||
fn salsa_event(&self, event: salsa::Event) {
|
||||
match event.kind {
|
||||
salsa::EventKind::WillDiscardStaleOutput { .. }
|
||||
| salsa::EventKind::DidDiscard { .. } => {
|
||||
self.push_log(format!("salsa_event({:?})", event.kind.debug(self)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn salsa_runtime(&self) -> &salsa::Runtime {
|
||||
self.storage.runtime()
|
||||
}
|
||||
}
|
||||
|
||||
impl Db for Database {}
|
||||
|
||||
impl HasLogger for Database {
|
||||
fn logger(&self) -> &Logger {
|
||||
&self.logger
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let mut db = Database::default();
|
||||
|
||||
// Creates 3 tracked structs
|
||||
let input = MyInput::new(&mut db, 3);
|
||||
assert_eq!(final_result(&db, input), 2 * 2 + 1 * 2 + 0 * 2);
|
||||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"final_result(MyInput(Id { value: 1 }))",
|
||||
"intermediate_result(MyInput(Id { value: 1 }))",
|
||||
]"#]]);
|
||||
|
||||
// Creates only 2 tracked structs in this revision, should delete 1
|
||||
//
|
||||
// Expect to see 6 DidDiscard events. Three from the primary struct:
|
||||
//
|
||||
// * the struct itself
|
||||
// * the struct's field
|
||||
// * the `contribution_from_struct` result
|
||||
//
|
||||
// and then 3 more from the struct created by `contribution_from_struct`:
|
||||
//
|
||||
// * the struct itself
|
||||
// * the struct's field
|
||||
// * the `copy_field` result
|
||||
|
||||
input.set_field(&mut db, 2);
|
||||
assert_eq!(final_result(&db, input), 1 * 2 + 0 * 2);
|
||||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"intermediate_result(MyInput(Id { value: 1 }))",
|
||||
"salsa_event(WillDiscardStaleOutput { execute_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) }, output_key: DependencyIndex { ingredient_index: IngredientIndex(3), key_index: Some(Id { value: 3 }) } })",
|
||||
"salsa_event(DidDiscard { key: DependencyIndex { ingredient_index: IngredientIndex(3), key_index: Some(Id { value: 3 }) } })",
|
||||
"salsa_event(DidDiscard { key: DependencyIndex { ingredient_index: IngredientIndex(2), key_index: Some(Id { value: 3 }) } })",
|
||||
"salsa_event(DidDiscard { key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 3 }) } })",
|
||||
"salsa_event(DidDiscard { key: DependencyIndex { ingredient_index: IngredientIndex(3), key_index: Some(Id { value: 6 }) } })",
|
||||
"salsa_event(DidDiscard { key: DependencyIndex { ingredient_index: IngredientIndex(2), key_index: Some(Id { value: 6 }) } })",
|
||||
"salsa_event(DidDiscard { key: DependencyIndex { ingredient_index: IngredientIndex(7), key_index: Some(Id { value: 6 }) } })",
|
||||
"final_result(MyInput(Id { value: 1 }))",
|
||||
]"#]]);
|
||||
}
|
117
salsa-2022-tests/tests/deletion.rs
Normal file
117
salsa-2022-tests/tests/deletion.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
//! Basic deletion test:
|
||||
//!
|
||||
//! * entities not created in a revision are deleted, as is any memoized data keyed on them.
|
||||
|
||||
use salsa::DebugWithDb;
|
||||
use salsa_2022_tests::{HasLogger, Logger};
|
||||
|
||||
use expect_test::expect;
|
||||
use test_log::test;
|
||||
|
||||
#[salsa::jar(db = Db)]
|
||||
struct Jar(
|
||||
MyInput,
|
||||
MyTracked,
|
||||
final_result,
|
||||
create_tracked_structs,
|
||||
contribution_from_struct,
|
||||
);
|
||||
|
||||
trait Db: salsa::DbWithJar<Jar> + HasLogger {}
|
||||
|
||||
#[salsa::input]
|
||||
struct MyInput {
|
||||
field: u32,
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
fn final_result(db: &dyn Db, input: MyInput) -> u32 {
|
||||
db.push_log(format!("final_result({:?})", input));
|
||||
let mut sum = 0;
|
||||
for tracked_struct in create_tracked_structs(db, input) {
|
||||
sum += contribution_from_struct(db, tracked_struct);
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
struct MyTracked {
|
||||
field: u32,
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
fn create_tracked_structs(db: &dyn Db, input: MyInput) -> Vec<MyTracked> {
|
||||
db.push_log(format!("intermediate_result({:?})", input));
|
||||
(0..input.field(db))
|
||||
.map(|i| MyTracked::new(db, i))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
fn contribution_from_struct(db: &dyn Db, tracked: MyTracked) -> u32 {
|
||||
tracked.field(db) * 2
|
||||
}
|
||||
|
||||
#[salsa::db(Jar)]
|
||||
#[derive(Default)]
|
||||
struct Database {
|
||||
storage: salsa::Storage<Self>,
|
||||
logger: Logger,
|
||||
}
|
||||
|
||||
impl salsa::Database for Database {
|
||||
fn salsa_event(&self, event: salsa::Event) {
|
||||
match event.kind {
|
||||
salsa::EventKind::WillDiscardStaleOutput { .. }
|
||||
| salsa::EventKind::DidDiscard { .. } => {
|
||||
self.push_log(format!("salsa_event({:?})", event.kind.debug(self)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn salsa_runtime(&self) -> &salsa::Runtime {
|
||||
self.storage.runtime()
|
||||
}
|
||||
}
|
||||
|
||||
impl Db for Database {}
|
||||
|
||||
impl HasLogger for Database {
|
||||
fn logger(&self) -> &Logger {
|
||||
&self.logger
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let mut db = Database::default();
|
||||
|
||||
// Creates 3 tracked structs
|
||||
let input = MyInput::new(&mut db, 3);
|
||||
assert_eq!(final_result(&db, input), 2 * 2 + 1 * 2 + 0 * 2);
|
||||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"final_result(MyInput(Id { value: 1 }))",
|
||||
"intermediate_result(MyInput(Id { value: 1 }))",
|
||||
]"#]]);
|
||||
|
||||
// Creates only 2 tracked structs in this revision, should delete 1
|
||||
//
|
||||
// Expect to see 3 DidDiscard events--
|
||||
//
|
||||
// * the struct itself
|
||||
// * the struct's field
|
||||
// * the `contribution_from_struct` result
|
||||
input.set_field(&mut db, 2);
|
||||
assert_eq!(final_result(&db, input), 1 * 2 + 0 * 2);
|
||||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"intermediate_result(MyInput(Id { value: 1 }))",
|
||||
"salsa_event(WillDiscardStaleOutput { execute_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) }, output_key: DependencyIndex { ingredient_index: IngredientIndex(3), key_index: Some(Id { value: 3 }) } })",
|
||||
"salsa_event(DidDiscard { key: DependencyIndex { ingredient_index: IngredientIndex(3), key_index: Some(Id { value: 3 }) } })",
|
||||
"salsa_event(DidDiscard { key: DependencyIndex { ingredient_index: IngredientIndex(2), key_index: Some(Id { value: 3 }) } })",
|
||||
"salsa_event(DidDiscard { key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 3 }) } })",
|
||||
"final_result(MyInput(Id { value: 1 }))",
|
||||
]"#]]);
|
||||
}
|
|
@ -104,7 +104,6 @@ fn test_run_0() {
|
|||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"read_maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
|
@ -126,7 +125,6 @@ fn test_run_5() {
|
|||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"read_maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
|
@ -148,14 +146,12 @@ fn test_run_10() {
|
|||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"read_maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(4), key_index: Some(Id { value: 1 }) } } }",
|
||||
"maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
]"#]]);
|
||||
}
|
||||
|
||||
|
@ -174,14 +170,12 @@ fn test_run_20() {
|
|||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"read_maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(4), key_index: Some(Id { value: 1 }) } } }",
|
||||
"maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
]"#]]);
|
||||
}
|
||||
|
||||
|
@ -204,7 +198,6 @@ fn test_run_0_then_5_then_20() {
|
|||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"read_maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
|
@ -218,14 +211,12 @@ fn test_run_0_then_5_then_20() {
|
|||
assert_eq!(final_result(&db, input), 100);
|
||||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: DependencyIndex { ingredient_index: IngredientIndex(7), key_index: Some(Id { value: 1 }) } } }",
|
||||
]"#]]);
|
||||
|
@ -239,19 +230,16 @@ fn test_run_0_then_5_then_20() {
|
|||
assert_eq!(final_result(&db, input), 200);
|
||||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) }, output_key: DependencyIndex { ingredient_index: IngredientIndex(4), key_index: Some(Id { value: 1 }) } } }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(4), key_index: Some(Id { value: 1 }) } } }",
|
||||
"maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"read_maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
|
@ -281,7 +269,6 @@ fn test_run_0_then_5_then_10_then_20() {
|
|||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"read_maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
|
@ -295,14 +282,12 @@ fn test_run_0_then_5_then_10_then_20() {
|
|||
assert_eq!(final_result(&db, input), 100);
|
||||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: DependencyIndex { ingredient_index: IngredientIndex(7), key_index: Some(Id { value: 1 }) } } }",
|
||||
]"#]]);
|
||||
|
@ -316,19 +301,16 @@ fn test_run_0_then_5_then_10_then_20() {
|
|||
assert_eq!(final_result(&db, input), 100);
|
||||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) }, output_key: DependencyIndex { ingredient_index: IngredientIndex(4), key_index: Some(Id { value: 1 }) } } }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(4), key_index: Some(Id { value: 1 }) } } }",
|
||||
"maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: DependencyIndex { ingredient_index: IngredientIndex(7), key_index: Some(Id { value: 1 }) } } }",
|
||||
]"#]]);
|
||||
|
@ -340,7 +322,6 @@ fn test_run_0_then_5_then_10_then_20() {
|
|||
assert_eq!(final_result(&db, input), 200);
|
||||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
|
@ -348,12 +329,9 @@ fn test_run_0_then_5_then_10_then_20() {
|
|||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(4), key_index: Some(Id { value: 1 }) } } }",
|
||||
"maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"read_maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
|
@ -379,7 +357,6 @@ fn test_run_5_then_20() {
|
|||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"read_maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
|
@ -389,19 +366,16 @@ fn test_run_5_then_20() {
|
|||
assert_eq!(final_result(&db, input), 200);
|
||||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) } } }",
|
||||
"create_tracked(MyInput(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: DependencyIndex { ingredient_index: IngredientIndex(6), key_index: Some(Id { value: 1 }) }, output_key: DependencyIndex { ingredient_index: IngredientIndex(4), key_index: Some(Id { value: 1 }) } } }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(4), key_index: Some(Id { value: 1 }) } } }",
|
||||
"maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: DependencyIndex { ingredient_index: IngredientIndex(5), key_index: Some(Id { value: 1 }) } } }",
|
||||
"read_maybe_specified(MyTracked(Id { value: 1 }))",
|
||||
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
|
||||
|
|
Loading…
Reference in a new issue