diff --git a/components/salsa-2022-macros/src/accumulator.rs b/components/salsa-2022-macros/src/accumulator.rs index 8b1eddae..cfc32c9e 100644 --- a/components/salsa-2022-macros/src/accumulator.rs +++ b/components/salsa-2022-macros/src/accumulator.rs @@ -116,7 +116,7 @@ fn ingredients_for_impl(args: &Args, struct_ty: &syn::Type, data_ty: &syn::Type) where DB: salsa::DbWithJar + salsa::storage::JarFromJars, { - let index = routes.push_mut( + let index = routes.push( |jars| { let jar = >::jar_from_jars(jars); <_ as salsa::storage::HasIngredientsFor>::ingredient(jar) diff --git a/components/salsa-2022-macros/src/db.rs b/components/salsa-2022-macros/src/db.rs index c56ae9cb..e2b82a9b 100644 --- a/components/salsa-2022-macros/src/db.rs +++ b/components/salsa-2022-macros/src/db.rs @@ -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); + } } } } diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index fa8d4ec7..a09794c9 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -159,6 +159,11 @@ impl InputStruct { let ingredients = <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); &ingredients.#all_field_indices }, + |jars| { + let jar = >::jar_from_jars_mut(jars); + let ingredients = <_ as salsa::storage::HasIngredientsFor>::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>::ingredient(jar); &ingredients.#input_index }, + |jars| { + let jar = >::jar_from_jars_mut(jars); + let ingredients = <_ as salsa::storage::HasIngredientsFor>::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. + } } } } diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 9d8667ab..2d5dc0ff 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -136,6 +136,10 @@ impl InternedStruct { let jar = >::jar_from_jars(jars); <_ as salsa::storage::HasIngredientsFor>::ingredient(jar) }, + |jars| { + let jar = >::jar_from_jars_mut(jars); + <_ as salsa::storage::HasIngredientsFor>::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. + } } } } diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index fb031cb2..8c0884e1 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -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 = >::jar_from_jars(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); - &ingredients.intern_map - }); + let index = routes.push( + |jars| { + let jar = >::jar_from_jars(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); + &ingredients.intern_map + }, + |jars| { + let jar = >::jar_from_jars_mut(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::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 = >::jar_from_jars(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); - &ingredients.function - }); + let index = routes.push( + |jars| { + let jar = >::jar_from_jars(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); + &ingredients.function + }, + |jars| { + let jar = >::jar_from_jars_mut(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); + &mut ingredients.function + }); salsa::function::FunctionIngredient::new(index) }, } diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index b2dcc11f..b689ff6d 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -182,12 +182,17 @@ impl TrackedStruct { let ingredients = <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); &ingredients.#value_field_indices }, + |jars| { + let jar = >::jar_from_jars_mut(jars); + let ingredients = <_ as salsa::storage::HasIngredientsFor>::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 = >::jar_from_jars(jars); let ingredients = <_ as salsa::storage::HasIngredientsFor>::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 salsa::salsa_struct::SalsaStructInDb 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) + } } } } diff --git a/components/salsa-2022/src/accumulator.rs b/components/salsa-2022/src/accumulator.rs index 6b326e1c..c34e663a 100644 --- a/components/salsa-2022/src/accumulator.rs +++ b/components/salsa-2022/src/accumulator.rs @@ -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 MutIngredient for AccumulatorIngredient +impl IngredientRequiresReset for AccumulatorIngredient 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; } diff --git a/components/salsa-2022/src/event.rs b/components/salsa-2022/src/event.rs index 8eccb280..a2909b17 100644 --- a/components/salsa-2022/src/event.rs +++ b/components/salsa-2022/src/event.rs @@ -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(), } } } diff --git a/components/salsa-2022/src/function.rs b/components/salsa-2022/src/function.rs index 914d98d3..0f70641c 100644 --- a/components/salsa-2022/src/function.rs +++ b/components/salsa-2022/src/function.rs @@ -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 { /// we don't know that we can trust the database to give us the same runtime /// everytime and so forth. deleted_entries: SegQueue>>, + + /// Set to true once we invoke `register_dependent_fn` for `C::SalsaStruct`. + /// Prevents us from registering more than once. + registered: AtomicCell, } 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) -> Option<&C::Value> { + fn insert_memo( + &self, + db: &DynDb<'_, C>, + key: C::Key, + memo: memo::Memo, + ) -> 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) { + >::register_dependent_fn(db, self.index) + } + } } impl Ingredient for FunctionIngredient @@ -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 MutIngredient for FunctionIngredient -where - DB: ?Sized + DbWithJar, - 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 IngredientRequiresReset for FunctionIngredient +where + C: Configuration, +{ + const RESET_ON_NEW_REVISION: bool = true; } diff --git a/components/salsa-2022/src/function/delete.rs b/components/salsa-2022/src/function/delete.rs index a62e61f4..54796bc5 100644 --- a/components/salsa-2022/src/function/delete.rs +++ b/components/salsa-2022/src/function/delete.rs @@ -1,3 +1,5 @@ +use crate::runtime::local_state::QueryOrigin; + use super::{Configuration, FunctionIngredient}; impl FunctionIngredient @@ -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 { 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 } } } diff --git a/components/salsa-2022/src/function/diff_outputs.rs b/components/salsa-2022/src/function/diff_outputs.rs index db456636..617371ba 100644 --- a/components/salsa-2022/src/function/diff_outputs.rs +++ b/components/salsa-2022/src/function/diff_outputs.rs @@ -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}; diff --git a/components/salsa-2022/src/function/execute.rs b/components/salsa-2022/src/function/execute.rs index 51e3867e..a70b7d65 100644 --- a/components/salsa-2022/src/function/execute.rs +++ b/components/salsa-2022/src/function/execute.rs @@ -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); diff --git a/components/salsa-2022/src/function/specify.rs b/components/salsa-2022/src/function/specify.rs index 25a4d45a..e511eadd 100644 --- a/components/salsa-2022/src/function/specify.rs +++ b/components/salsa-2022/src/function/specify.rs @@ -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. diff --git a/components/salsa-2022/src/ingredient.rs b/components/salsa-2022/src/ingredient.rs index 04f376da..76347d71 100644 --- a/components/salsa-2022/src/ingredient.rs +++ b/components/salsa-2022/src/ingredient.rs @@ -33,13 +33,28 @@ pub trait Ingredient { /// /// 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: Ingredient { - /// 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; +} diff --git a/components/salsa-2022/src/ingredient_list.rs b/components/salsa-2022/src/ingredient_list.rs new file mode 100644 index 00000000..f80c41ad --- /dev/null +++ b/components/salsa-2022/src/ingredient_list.rs @@ -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>, +} + +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 { + 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> = 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; + } + } + } +} diff --git a/components/salsa-2022/src/input.rs b/components/salsa-2022/src/input.rs index f4529fbc..d0ce79f0 100644 --- a/components/salsa-2022/src/input.rs +++ b/components/salsa-2022/src/input.rs @@ -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 IngredientRequiresReset for InputIngredient +where + Id: InputId, +{ + const RESET_ON_NEW_REVISION: bool = false; } diff --git a/components/salsa-2022/src/input_field.rs b/components/salsa-2022/src/input_field.rs index 470cf7fd..4db87951 100644 --- a/components/salsa-2022/src/input_field.rs +++ b/components/salsa-2022/src/input_field.rs @@ -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 IngredientRequiresReset for InputFieldIngredient +where + K: AsId, +{ + const RESET_ON_NEW_REVISION: bool = false; } diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index 13a6f7d8..9810c5ea 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -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 IngredientRequiresReset for InternedIngredient +where + Id: InternedId, + Data: InternedData, +{ + const RESET_ON_NEW_REVISION: bool = false; } pub struct IdentityInterner { diff --git a/components/salsa-2022/src/lib.rs b/components/salsa-2022/src/lib.rs index 1558cbbb..6a0e4ad8 100644 --- a/components/salsa-2022/src/lib.rs +++ b/components/salsa-2022/src/lib.rs @@ -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; diff --git a/components/salsa-2022/src/routes.rs b/components/salsa-2022/src/routes.rs index 308f7217..02699f13 100644 --- a/components/salsa-2022/src/routes.rs +++ b/components/salsa-2022/src/routes.rs @@ -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 = dyn Fn(&DB::Jars) -> (&dyn Ingredient) + Se #[allow(type_alias_bounds)] #[allow(unused_parens)] pub type DynMutRoute = - dyn Fn(&mut DB::Jars) -> (&mut dyn MutIngredient) + Send + Sync; + dyn Fn(&mut DB::Jars) -> (&mut dyn Ingredient) + 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 { /// 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>>, + routes: Vec<(Box>, Box>)>, - /// 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>>, + /// Indices of routes which need a 'reset' call. + needs_reset: Vec, } impl Routes { @@ -66,7 +62,7 @@ impl Routes { pub(super) fn new() -> Self { Routes { routes: vec![], - mut_routes: vec![], + needs_reset: vec![], } } @@ -77,52 +73,53 @@ impl Routes { /// /// # 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( &mut self, - route: impl (Fn(&DB::Jars) -> &dyn Ingredient) + 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 + 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) + Send + Sync + 'static, - mut_route: impl (Fn(&mut DB::Jars) -> &mut dyn MutIngredient) + 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 { - &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 &mut dyn MutIngredient> + '_ { - self.mut_routes - .iter() - .map(|b| &**b as &dyn Fn(&mut DB::Jars) -> &mut dyn MutIngredient) + index: IngredientIndex, + ) -> &dyn Fn(&mut DB::Jars) -> &mut dyn Ingredient { + &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 &mut dyn Ingredient> + '_ { + self.needs_reset.iter().map(|&index| self.route_mut(index)) } } diff --git a/components/salsa-2022/src/salsa_struct.rs b/components/salsa-2022/src/salsa_struct.rs index 62a7a84b..febdae4a 100644 --- a/components/salsa-2022/src/salsa_struct.rs +++ b/components/salsa-2022/src/salsa_struct.rs @@ -1,3 +1,5 @@ -use crate::Database; +use crate::{Database, IngredientIndex}; -pub trait SalsaStructInDb {} +pub trait SalsaStructInDb { + fn register_dependent_fn(db: &DB, index: IngredientIndex); +} diff --git a/components/salsa-2022/src/storage.rs b/components/salsa-2022/src/storage.rs index dff46b4b..1737b11d 100644 --- a/components/salsa-2022/src/storage.rs +++ b/components/salsa-2022/src/storage.rs @@ -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 diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 7d7741b4..f03f96a2 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -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>, + + /// 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) { - 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 Ingredient for TrackedStructIngredient 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 MutIngredient for TrackedStructIngredient +impl IngredientRequiresReset for TrackedStructIngredient where Id: TrackedStructId, Data: TrackedStructData, { - fn reset_for_new_revision(&mut self) { - self.interned.clear_deleted_indices(); - } + const RESET_ON_NEW_REVISION: bool = true; } diff --git a/salsa-2022-tests/tests/deletion-cascade.rs b/salsa-2022-tests/tests/deletion-cascade.rs new file mode 100644 index 00000000..d52f698b --- /dev/null +++ b/salsa-2022-tests/tests/deletion-cascade.rs @@ -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 + 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 { + 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, + 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 }))", + ]"#]]); +} diff --git a/salsa-2022-tests/tests/deletion.rs b/salsa-2022-tests/tests/deletion.rs new file mode 100644 index 00000000..b5c756b4 --- /dev/null +++ b/salsa-2022-tests/tests/deletion.rs @@ -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 + 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 { + 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, + 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 }))", + ]"#]]); +} diff --git a/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs b/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs index 60c3d2f5..c1ab5003 100644 --- a/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs +++ b/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs @@ -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 }",