remove RuntimeId and use ThreadId

This commit is contained in:
Niko Matsakis 2024-07-22 06:11:01 -04:00
parent 7c2bbe811e
commit 3d2b2d3a65
13 changed files with 59 additions and 445 deletions

View file

@ -268,8 +268,8 @@ fn fix_bad_variable_in_function() {
"#]],
expect![[r#"
[
"Event: Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: parse_statements(0) } }",
"Event: Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: type_check_function(0) } }",
"Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: parse_statements(0) } }",
"Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: type_check_function(0) } }",
]
"#]],
)],

View file

@ -174,7 +174,7 @@ impl<A: Accumulator> Ingredient for IngredientImpl<A> {
assert!(stale_output_key.is_none());
if self.map.remove(&executor).is_some() {
db.salsa_event(Event {
runtime_id: db.runtime().id(),
thread_id: std::thread::current().id(),
kind: EventKind::DidDiscardAccumulated {
executor_key: executor,
accumulator: self.dependency_index(),

View file

@ -1,13 +1,14 @@
use crate::{key::DatabaseKeyIndex, key::DependencyIndex, runtime::RuntimeId};
use std::thread::ThreadId;
use crate::{key::DatabaseKeyIndex, key::DependencyIndex};
/// The `Event` struct identifies various notable things that can
/// occur during salsa execution. Instances of this struct are given
/// to `salsa_event`.
#[derive(Debug)]
pub struct Event {
/// The id of the snapshot that triggered the event. Usually
/// 1-to-1 with a thread, as well.
pub runtime_id: RuntimeId,
/// The id of the thread that triggered the event.
pub thread_id: ThreadId,
/// What sort of event was it.
pub kind: EventKind,
@ -26,18 +27,15 @@ pub enum EventKind {
database_key: DatabaseKeyIndex,
},
/// Indicates that another thread (with id `other_runtime_id`) is processing the
/// Indicates that another thread (with id `other_thread_id`) is processing the
/// given query (`database_key`), so we will block until they
/// finish.
///
/// Executes after we have registered with the other thread but
/// before they have answered us.
///
/// (NB: you can find the `id` of the current thread via the
/// `runtime`)
WillBlockOn {
/// The id of the runtime we will block on.
other_runtime_id: RuntimeId,
/// The id of the thread we will block on.
other_thread_id: ThreadId,
/// The database-key for the affected value. Implements `Debug`.
database_key: DatabaseKeyIndex,

View file

@ -269,7 +269,7 @@ where
if let Some(origin) = self.delete_memo(id) {
let key = self.database_key_index(id);
db.salsa_event(Event {
runtime_id: db.runtime().id(),
thread_id: std::thread::current().id(),
kind: EventKind::DidDiscard { key },
});

View file

@ -38,9 +38,8 @@ where
}
fn report_stale_output(db: &C::DbView, key: DatabaseKeyIndex, output: DependencyIndex) {
let runtime_id = db.runtime().id();
db.salsa_event(Event {
runtime_id,
thread_id: std::thread::current().id(),
kind: EventKind::WillDiscardStaleOutput {
execute_key: key,
output_key: output,

View file

@ -33,7 +33,7 @@ where
tracing::info!("{:?}: executing query", database_key_index);
db.salsa_event(Event {
runtime_id: runtime.id(),
thread_id: std::thread::current().id(),
kind: EventKind::WillExecute {
database_key: database_key_index,
},

View file

@ -150,7 +150,7 @@ impl<V> Memo<V> {
database_key_index: DatabaseKeyIndex,
) {
db.salsa_event(Event {
runtime_id: runtime.id(),
thread_id: std::thread::current().id(),
kind: EventKind::DidValidateMemoizedValue {
database_key: database_key_index,
},

View file

@ -1,19 +1,17 @@
use std::sync::atomic::{AtomicBool, Ordering};
use crate::{
hash::FxDashMap,
key::DatabaseKeyIndex,
runtime::{RuntimeId, WaitResult},
Database, Id, Runtime,
use std::{
sync::atomic::{AtomicBool, Ordering},
thread::ThreadId,
};
use crate::{hash::FxDashMap, key::DatabaseKeyIndex, runtime::WaitResult, Database, Id, Runtime};
#[derive(Default)]
pub(super) struct SyncMap {
sync_map: FxDashMap<Id, SyncState>,
}
struct SyncState {
id: RuntimeId,
id: ThreadId,
/// Set to true if any other queries are blocked,
/// waiting for this query to complete.
@ -27,10 +25,11 @@ impl SyncMap {
database_key_index: DatabaseKeyIndex,
) -> Option<ClaimGuard<'me>> {
let runtime = db.runtime();
let thread_id = std::thread::current().id();
match self.sync_map.entry(database_key_index.key_index) {
dashmap::mapref::entry::Entry::Vacant(entry) => {
entry.insert(SyncState {
id: runtime.id(),
id: thread_id,
anyone_waiting: AtomicBool::new(false),
});
Some(ClaimGuard {

View file

@ -1,6 +1,7 @@
use std::{
panic::panic_any,
sync::{atomic::AtomicUsize, Arc},
thread::ThreadId,
};
use crossbeam::atomic::AtomicCell;
@ -24,9 +25,6 @@ use super::tracked_struct::Disambiguator;
mod dependency_graph;
pub struct Runtime {
/// Our unique runtime id.
id: RuntimeId,
/// Local state that is specific to this runtime (thread).
local_state: local_state::LocalState,
@ -64,14 +62,6 @@ pub(crate) enum WaitResult {
Cycle(Cycle),
}
/// A unique identifier for a particular runtime. Each time you create
/// a snapshot, a fresh `RuntimeId` is generated. Once a snapshot is
/// complete, its `RuntimeId` may potentially be re-used.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RuntimeId {
counter: usize,
}
#[derive(Copy, Clone, Debug)]
pub struct StampedValue<V> {
pub value: V,
@ -101,7 +91,6 @@ impl<V> StampedValue<V> {
impl Default for Runtime {
fn default() -> Self {
Runtime {
id: RuntimeId { counter: 0 },
local_state: Default::default(),
revisions: (0..Durability::LEN)
.map(|_| AtomicRevision::start())
@ -117,7 +106,6 @@ impl Default for Runtime {
impl std::fmt::Debug for Runtime {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt.debug_struct("Runtime")
.field("id", &self.id)
.field("revisions", &self.revisions)
.field("next_id", &self.next_id)
.field("revision_canceled", &self.revision_canceled)
@ -127,10 +115,6 @@ impl std::fmt::Debug for Runtime {
}
impl Runtime {
pub(crate) fn id(&self) -> RuntimeId {
self.id
}
pub(crate) fn current_revision(&self) -> Revision {
self.revisions[0].load()
}
@ -234,13 +218,15 @@ impl Runtime {
/// `salsa_event` is emitted when this method is called, so that should be
/// used instead.
pub(crate) fn unwind_if_revision_cancelled<DB: ?Sized + Database>(&self, db: &DB) {
let thread_id = std::thread::current().id();
db.salsa_event(Event {
runtime_id: self.id(),
thread_id,
kind: EventKind::WillCheckCancellation,
});
if self.revision_canceled.load() {
db.salsa_event(Event {
runtime_id: self.id(),
thread_id,
kind: EventKind::WillCheckCancellation,
});
self.unwind_cancelled();
@ -300,23 +286,24 @@ impl Runtime {
&self,
db: &dyn Database,
database_key: DatabaseKeyIndex,
other_id: RuntimeId,
other_id: ThreadId,
query_mutex_guard: QueryMutexGuard,
) {
let mut dg = self.dependency_graph.lock();
let thread_id = std::thread::current().id();
if dg.depends_on(other_id, self.id()) {
if dg.depends_on(other_id, thread_id) {
self.unblock_cycle_and_maybe_throw(db, &mut dg, database_key, other_id);
// If the above fn returns, then (via cycle recovery) it has unblocked the
// cycle, so we can continue.
assert!(!dg.depends_on(other_id, self.id()));
assert!(!dg.depends_on(other_id, thread_id));
}
db.salsa_event(Event {
runtime_id: self.id(),
thread_id,
kind: EventKind::WillBlockOn {
other_runtime_id: other_id,
other_thread_id: other_id,
database_key,
},
});
@ -325,7 +312,7 @@ impl Runtime {
let (stack, result) = DependencyGraph::block_on(
dg,
self.id(),
thread_id,
database_key,
other_id,
stack,
@ -359,7 +346,7 @@ impl Runtime {
db: &dyn Database,
dg: &mut DependencyGraph,
database_key_index: DatabaseKeyIndex,
to_id: RuntimeId,
to_id: ThreadId,
) {
tracing::debug!(
"unblock_cycle_and_maybe_throw(database_key={:?})",
@ -367,7 +354,7 @@ impl Runtime {
);
let mut from_stack = self.local_state.take_query_stack();
let from_id = self.id();
let from_id = std::thread::current().id();
// Make a "dummy stack frame". As we iterate through the cycle, we will collect the
// inputs from each participant. Then, if we are participating in cycle recovery, we

View file

@ -1,8 +1,9 @@
use std::sync::Arc;
use std::thread::ThreadId;
use crate::active_query::ActiveQuery;
use crate::key::DatabaseKeyIndex;
use crate::runtime::{RuntimeId, WaitResult};
use crate::runtime::WaitResult;
use parking_lot::{Condvar, MutexGuard};
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
@ -15,21 +16,21 @@ pub(super) struct DependencyGraph {
/// `K` is blocked on some query executing in the runtime `V`.
/// This encodes a graph that must be acyclic (or else deadlock
/// will result).
edges: FxHashMap<RuntimeId, Edge>,
edges: FxHashMap<ThreadId, Edge>,
/// Encodes the `RuntimeId` that are blocked waiting for the result
/// Encodes the `ThreadId` that are blocked waiting for the result
/// of a given query.
query_dependents: FxHashMap<DatabaseKeyIndex, SmallVec<[RuntimeId; 4]>>,
query_dependents: FxHashMap<DatabaseKeyIndex, SmallVec<[ThreadId; 4]>>,
/// When a key K completes which had dependent queries Qs blocked on it,
/// it stores its `WaitResult` here. As they wake up, each query Q in Qs will
/// come here to fetch their results.
wait_results: FxHashMap<RuntimeId, (QueryStack, WaitResult)>,
wait_results: FxHashMap<ThreadId, (QueryStack, WaitResult)>,
}
#[derive(Debug)]
struct Edge {
blocked_on_id: RuntimeId,
blocked_on_id: ThreadId,
blocked_on_key: DatabaseKeyIndex,
stack: QueryStack,
@ -42,7 +43,7 @@ impl DependencyGraph {
/// True if `from_id` depends on `to_id`.
///
/// (i.e., there is a path from `from_id` to `to_id` in the graph.)
pub(super) fn depends_on(&mut self, from_id: RuntimeId, to_id: RuntimeId) -> bool {
pub(super) fn depends_on(&mut self, from_id: ThreadId, to_id: ThreadId) -> bool {
let mut p = from_id;
while let Some(q) = self.edges.get(&p).map(|edge| edge.blocked_on_id) {
if q == to_id {
@ -62,10 +63,10 @@ impl DependencyGraph {
/// 3. ...and `to_id` is transitively dependent on something which is present on `from_stack`.
pub(super) fn for_each_cycle_participant(
&mut self,
from_id: RuntimeId,
from_id: ThreadId,
from_stack: &mut QueryStack,
database_key: DatabaseKeyIndex,
to_id: RuntimeId,
to_id: ThreadId,
mut closure: impl FnMut(&mut [ActiveQuery]),
) {
debug_assert!(self.depends_on(to_id, from_id));
@ -130,10 +131,10 @@ impl DependencyGraph {
/// * Others is true if other runtimes were unblocked.
pub(super) fn maybe_unblock_runtimes_in_cycle(
&mut self,
from_id: RuntimeId,
from_id: ThreadId,
from_stack: &QueryStack,
database_key: DatabaseKeyIndex,
to_id: RuntimeId,
to_id: ThreadId,
) -> (bool, bool) {
// See diagram in `for_each_cycle_participant`.
let mut id = to_id;
@ -194,9 +195,9 @@ impl DependencyGraph {
/// * `held_mutex` is a read lock (or stronger) on `database_key`
pub(super) fn block_on<QueryMutexGuard>(
mut me: MutexGuard<'_, Self>,
from_id: RuntimeId,
from_id: ThreadId,
database_key: DatabaseKeyIndex,
to_id: RuntimeId,
to_id: ThreadId,
from_stack: QueryStack,
query_mutex_guard: QueryMutexGuard,
) -> (QueryStack, WaitResult) {
@ -220,9 +221,9 @@ impl DependencyGraph {
/// computing `database_key`.
fn add_edge(
&mut self,
from_id: RuntimeId,
from_id: ThreadId,
database_key: DatabaseKeyIndex,
to_id: RuntimeId,
to_id: ThreadId,
from_stack: QueryStack,
) -> Arc<parking_lot::Condvar> {
assert_ne!(from_id, to_id);
@ -266,7 +267,7 @@ impl DependencyGraph {
/// Unblock the runtime with the given id with the given wait-result.
/// This will cause it resume execution (though it will have to grab
/// the lock on this data structure first, to recover the wait result).
fn unblock_runtime(&mut self, id: RuntimeId, wait_result: WaitResult) {
fn unblock_runtime(&mut self, id: ThreadId, wait_result: WaitResult) {
let edge = self.edges.remove(&id).expect("not blocked");
self.wait_results.insert(id, (edge.stack, wait_result));

View file

@ -391,7 +391,7 @@ where
/// discussion and important considerations.
pub(crate) fn delete_entity(&self, db: &dyn crate::Database, id: Id) {
db.salsa_event(Event {
runtime_id: db.runtime().id(),
thread_id: std::thread::current().id(),
kind: crate::EventKind::DidDiscard {
key: self.database_key_index(id),
},

View file

@ -76,8 +76,8 @@ fn test_leaked_inputs_ignored() {
let result_in_rev_1 = function(&db, input);
db.assert_logs(expect![[r#"
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: function(0) } }",
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: function(0) } }",
]"#]]);
assert_eq!(result_in_rev_1, 0);
@ -92,8 +92,8 @@ fn test_leaked_inputs_ignored() {
let result_in_rev_2 = function(&db, input);
db.assert_logs(expect![[r#"
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: function(0) } }",
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: function(0) } }",
]"#]]);
// Because salsa did not see any way for the tracked

View file

@ -1,370 +0,0 @@
//! Test that a `tracked` fn on a `salsa::input`
//! compiles and executes successfully.
use expect_test::expect;
mod common;
use common::{HasLogger, Logger};
use salsa::Setter;
use test_log::test;
#[salsa::db]
trait Db: salsa::Database + HasLogger {}
#[salsa::input]
struct MyInput {
field: u32,
}
#[salsa::tracked]
struct MyTracked<'db> {
input: MyInput,
}
/// If the input is in the range 0..10, this is specified to return 10.
/// Otherwise, the default occurs, and it returns the input.
#[salsa::tracked(specify)]
fn maybe_specified<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> u32 {
db.push_log(format!("maybe_specified({:?})", tracked));
tracked.input(db).field(db)
}
/// Reads maybe-specified and multiplies it by 10.
/// This is here to show whether we can detect when `maybe_specified` has changed
/// and control down-stream work accordingly.
#[salsa::tracked]
fn read_maybe_specified<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> u32 {
db.push_log(format!("read_maybe_specified({:?})", tracked));
maybe_specified(db, tracked) * 10
}
/// Create a tracked value and *maybe* specify a value for
/// `maybe_specified`
#[salsa::tracked]
fn create_tracked(db: &dyn Db, input: MyInput) -> MyTracked<'_> {
db.push_log(format!("create_tracked({:?})", input));
let tracked = MyTracked::new(db, input);
if input.field(db) < 10 {
maybe_specified::specify(db, tracked, 10);
}
tracked
}
#[salsa::tracked]
fn final_result(db: &dyn Db, input: MyInput) -> u32 {
db.push_log(format!("final_result({:?})", input));
let tracked = create_tracked(db, input);
read_maybe_specified(db, tracked)
}
#[salsa::db]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
logger: Logger,
}
#[salsa::db]
impl salsa::Database for Database {
fn salsa_event(&self, event: salsa::Event) {
self.push_log(format!("{event:?}"));
}
}
#[salsa::db]
impl Db for Database {}
impl HasLogger for Database {
fn logger(&self) -> &Logger {
&self.logger
}
}
#[test]
fn test_run_0() {
let mut db = Database::default();
let input = MyInput::new(&db, 0);
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: WillExecute { database_key: final_result(0) } }",
"final_result(MyInput { [salsa id]: Id(0), field: 0 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 0 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 0 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
}
#[test]
fn test_run_5() {
let mut db = Database::default();
let input = MyInput::new(&db, 5);
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: WillExecute { database_key: final_result(0) } }",
"final_result(MyInput { [salsa id]: Id(0), field: 5 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 5 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 5 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
}
#[test]
fn test_run_10() {
let mut db = Database::default();
let input = MyInput::new(&db, 10);
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: WillExecute { database_key: final_result(0) } }",
"final_result(MyInput { [salsa id]: Id(0), field: 10 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 10 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 10 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 10 } })",
]"#]]);
}
#[test]
fn test_run_20() {
let mut db = Database::default();
let input = MyInput::new(&db, 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: WillExecute { database_key: final_result(0) } }",
"final_result(MyInput { [salsa id]: Id(0), field: 20 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 20 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 20 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 20 } })",
]"#]]);
}
#[test]
fn test_run_0_then_5_then_20() {
let mut db = Database::default();
// Set input to 0:
//
// * `create_tracked` specifies `10` for `maybe_specified`
// * final resuilt of `100` is derived by executing `read_maybe_specified`
let input = MyInput::new(&db, 0);
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: WillExecute { database_key: final_result(0) } }",
"final_result(MyInput { [salsa id]: Id(0), field: 0 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 0 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 0 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
// Set input to 5:
//
// * `create_tracked` does re-execute, but specifies same value for `maybe_specified` as before
// * `read_maybe_specified` does not re-execute (its input has not changed)
input.set_field(&mut db).to(5);
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: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 5 })",
"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: read_maybe_specified(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: final_result(0) } }",
]"#]]);
// Set input to 20:
//
// * `create_tracked` re-executes but does not specify any value
// * `read_maybe_specified` is invoked and it calls `maybe_specified`, which now executes
// (its value has not been specified)
input.set_field(&mut db).to(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: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 20 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }",
"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: maybe_specified(0) } }",
"maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 20 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 20 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }",
"final_result(MyInput { [salsa id]: Id(0), field: 20 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
}
#[test]
fn test_run_0_then_5_then_10_then_20() {
let mut db = Database::default();
// Set input to 0:
//
// * `create_tracked` specifies `10` for `maybe_specified`
// * final resuilt of `100` is derived by executing `read_maybe_specified`
let input = MyInput::new(&db, 0);
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: WillExecute { database_key: final_result(0) } }",
"final_result(MyInput { [salsa id]: Id(0), field: 0 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 0 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 0 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
// Set input to 5:
//
// * `create_tracked` does re-execute, but specifies same value for `maybe_specified` as before
// * `read_maybe_specified` does not re-execute (its input has not changed)
input.set_field(&mut db).to(5);
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: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 5 })",
"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: read_maybe_specified(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: final_result(0) } }",
]"#]]);
// Set input to 10:
//
// * `create_tracked` does re-execute and specifies no value for `maybe_specified`
// * `maybe_specified_value` returns 10; this is the same value as was specified.
// * `read_maybe_specified` therefore does NOT need to execute.
input.set_field(&mut db).to(10);
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: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 10 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }",
"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: maybe_specified(0) } }",
"maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 10 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: read_maybe_specified(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: final_result(0) } }",
]"#]]);
// Set input to 20:
//
// * Everything re-executes to get new result (200).
input.set_field(&mut db).to(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: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 20 })",
"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: maybe_specified(0) } }",
"maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 20 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 20 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }",
"final_result(MyInput { [salsa id]: Id(0), field: 20 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
}
#[test]
fn test_run_5_then_20() {
let mut db = Database::default();
let input = MyInput::new(&db, 5);
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: WillExecute { database_key: final_result(0) } }",
"final_result(MyInput { [salsa id]: Id(0), field: 5 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 5 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 5 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
input.set_field(&mut db).to(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: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput { [salsa id]: Id(0), field: 20 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }",
"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: maybe_specified(0) } }",
"maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 20 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked { [salsa id]: Id(0), input: MyInput { [salsa id]: Id(0), field: 20 } })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }",
"final_result(MyInput { [salsa id]: Id(0), field: 20 })",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
}