//! #![allow(clippy::type_complexity)] #![allow(clippy::question_mark)] #![warn(rust_2018_idioms)] #![warn(missing_docs)] //! The salsa crate is a crate for incremental recomputation. It //! permits you to define a "database" of queries with both inputs and //! values derived from those inputs; as you set the inputs, you can //! re-execute the derived queries and it will try to re-use results //! from previous invocations as appropriate. mod derived; mod durability; mod hash; mod input; mod intern_id; mod interned; mod lru; mod revision; mod runtime; mod storage; pub mod debug; /// Items in this module are public for implementation reasons, /// and are exempt from the SemVer guarantees. #[doc(hidden)] pub mod plumbing; use crate::plumbing::CycleRecoveryStrategy; use crate::plumbing::DerivedQueryStorageOps; use crate::plumbing::InputQueryStorageOps; use crate::plumbing::LruQueryStorageOps; use crate::plumbing::QueryStorageMassOps; use crate::plumbing::QueryStorageOps; pub use crate::revision::Revision; use std::fmt::{self, Debug}; use std::hash::Hash; use std::panic::AssertUnwindSafe; use std::panic::{self, UnwindSafe}; pub use crate::durability::Durability; pub use crate::intern_id::InternId; pub use crate::interned::{InternKey, InternValue}; pub use crate::runtime::Runtime; pub use crate::runtime::RuntimeId; pub use crate::storage::Storage; /// The base trait which your "query context" must implement. Gives /// access to the salsa runtime, which you must embed into your query /// context (along with whatever other state you may require). pub trait Database: plumbing::DatabaseOps { /// This function is invoked at key points in the salsa /// runtime. It permits the database to be customized and to /// inject logging or other custom behavior. fn salsa_event(&self, event_fn: Event) { _ = event_fn; } /// Starts unwinding the stack if the current revision is cancelled. /// /// This method can be called by query implementations that perform /// potentially expensive computations, in order to speed up propagation of /// cancellation. /// /// Cancellation will automatically be triggered by salsa on any query /// invocation. /// /// This method should not be overridden by `Database` implementors. A /// `salsa_event` is emitted when this method is called, so that should be /// used instead. #[inline] fn unwind_if_cancelled(&self) { let runtime = self.salsa_runtime(); self.salsa_event(Event { runtime_id: runtime.id(), kind: EventKind::WillCheckCancellation, }); let current_revision = runtime.current_revision(); let pending_revision = runtime.pending_revision(); tracing::debug!( "unwind_if_cancelled: current_revision={:?}, pending_revision={:?}", current_revision, pending_revision ); if pending_revision > current_revision { runtime.unwind_cancelled(); } } /// Gives access to the underlying salsa runtime. /// /// This method should not be overridden by `Database` implementors. fn salsa_runtime(&self) -> &Runtime { self.ops_salsa_runtime() } /// A "synthetic write" causes the system to act *as though* some /// input of durability `durability` has changed. This is mostly /// useful for profiling scenarios. /// /// **WARNING:** Just like an ordinary write, this method triggers /// cancellation. If you invoke it while a snapshot exists, it /// will block until that snapshot is dropped -- if that snapshot /// is owned by the current thread, this could trigger deadlock. fn synthetic_write(&mut self, durability: Durability) { plumbing::DatabaseOps::synthetic_write(self, durability) } } /// The `Event` struct identifies various notable things that can /// occur during salsa execution. Instances of this struct are given /// to `salsa_event`. 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, /// What sort of event was it. pub kind: EventKind, } impl Event { /// Returns a type that gives a user-readable debug output. /// Use like `println!("{:?}", index.debug(db))`. pub fn debug<'me, D: ?Sized>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me where D: plumbing::DatabaseOps, { EventDebug { event: self, db } } } impl fmt::Debug for Event { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("Event") .field("runtime_id", &self.runtime_id) .field("kind", &self.kind) .finish() } } struct EventDebug<'me, D: ?Sized> where D: plumbing::DatabaseOps, { event: &'me Event, db: &'me D, } impl<'me, D: ?Sized> fmt::Debug for EventDebug<'me, D> where D: plumbing::DatabaseOps, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("Event") .field("runtime_id", &self.event.runtime_id) .field("kind", &self.event.kind.debug(self.db)) .finish() } } /// An enum identifying the various kinds of events that can occur. pub enum EventKind { /// Occurs when we found that all inputs to a memoized value are /// up-to-date and hence the value can be re-used without /// executing the closure. /// /// Executes before the "re-used" value is returned. DidValidateMemoizedValue { /// The database-key for the affected value. Implements `Debug`. database_key: DatabaseKeyIndex, }, /// Indicates that another thread (with id `other_runtime_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 /// `salsa_runtime`) WillBlockOn { /// The id of the runtime we will block on. other_runtime_id: RuntimeId, /// The database-key for the affected value. Implements `Debug`. database_key: DatabaseKeyIndex, }, /// Indicates that the function for this query will be executed. /// This is either because it has never executed before or because /// its inputs may be out of date. WillExecute { /// The database-key for the affected value. Implements `Debug`. database_key: DatabaseKeyIndex, }, /// Indicates that `unwind_if_cancelled` was called and salsa will check if /// the current revision has been cancelled. WillCheckCancellation, } impl EventKind { /// Returns a type that gives a user-readable debug output. /// Use like `println!("{:?}", index.debug(db))`. pub fn debug<'me, D: ?Sized>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me where D: plumbing::DatabaseOps, { EventKindDebug { kind: self, db } } } impl fmt::Debug for EventKind { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { EventKind::DidValidateMemoizedValue { database_key } => fmt .debug_struct("DidValidateMemoizedValue") .field("database_key", database_key) .finish(), EventKind::WillBlockOn { other_runtime_id, database_key } => fmt .debug_struct("WillBlockOn") .field("other_runtime_id", other_runtime_id) .field("database_key", database_key) .finish(), EventKind::WillExecute { database_key } => { fmt.debug_struct("WillExecute").field("database_key", database_key).finish() } EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(), } } } struct EventKindDebug<'me, D: ?Sized> where D: plumbing::DatabaseOps, { kind: &'me EventKind, db: &'me D, } impl<'me, D: ?Sized> fmt::Debug for EventKindDebug<'me, D> where D: plumbing::DatabaseOps, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self.kind { EventKind::DidValidateMemoizedValue { database_key } => fmt .debug_struct("DidValidateMemoizedValue") .field("database_key", &database_key.debug(self.db)) .finish(), EventKind::WillBlockOn { other_runtime_id, database_key } => fmt .debug_struct("WillBlockOn") .field("other_runtime_id", &other_runtime_id) .field("database_key", &database_key.debug(self.db)) .finish(), EventKind::WillExecute { database_key } => fmt .debug_struct("WillExecute") .field("database_key", &database_key.debug(self.db)) .finish(), EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(), } } } /// Indicates a database that also supports parallel query /// evaluation. All of Salsa's base query support is capable of /// parallel execution, but for it to work, your query key/value types /// must also be `Send`, as must any additional data in your database. pub trait ParallelDatabase: Database + Send { /// Creates a second handle to the database that holds the /// database fixed at a particular revision. So long as this /// "frozen" handle exists, any attempt to [`set`] an input will /// block. /// /// [`set`]: struct.QueryTable.html#method.set /// /// This is the method you are meant to use most of the time in a /// parallel setting where modifications may arise asynchronously /// (e.g., a language server). In this context, it is common to /// wish to "fork off" a snapshot of the database performing some /// series of queries in parallel and arranging the results. Using /// this method for that purpose ensures that those queries will /// see a consistent view of the database (it is also advisable /// for those queries to use the [`Runtime::unwind_if_cancelled`] /// method to check for cancellation). /// /// # Panics /// /// It is not permitted to create a snapshot from inside of a /// query. Attepting to do so will panic. /// /// # Deadlock warning /// /// The intended pattern for snapshots is that, once created, they /// are sent to another thread and used from there. As such, the /// `snapshot` acquires a "read lock" on the database -- /// therefore, so long as the `snapshot` is not dropped, any /// attempt to `set` a value in the database will block. If the /// `snapshot` is owned by the same thread that is attempting to /// `set`, this will cause a problem. /// /// # How to implement this /// /// Typically, this method will create a second copy of your /// database type (`MyDatabaseType`, in the example below), /// cloning over each of the fields from `self` into this new /// copy. For the field that stores the salsa runtime, you should /// use [the `Runtime::snapshot` method][rfm] to create a snapshot of the /// runtime. Finally, package up the result using `Snapshot::new`, /// which is a simple wrapper type that only gives `&self` access /// to the database within (thus preventing the use of methods /// that may mutate the inputs): /// /// [rfm]: struct.Runtime.html#method.snapshot /// /// ```rust,ignore /// impl ParallelDatabase for MyDatabaseType { /// fn snapshot(&self) -> Snapshot { /// Snapshot::new( /// MyDatabaseType { /// runtime: self.runtime.snapshot(self), /// other_field: self.other_field.clone(), /// } /// ) /// } /// } /// ``` fn snapshot(&self) -> Snapshot; } /// Simple wrapper struct that takes ownership of a database `DB` and /// only gives `&self` access to it. See [the `snapshot` method][fm] /// for more details. /// /// [fm]: trait.ParallelDatabase.html#method.snapshot #[derive(Debug)] pub struct Snapshot where DB: ParallelDatabase, { db: DB, } impl Snapshot where DB: ParallelDatabase, { /// Creates a `Snapshot` that wraps the given database handle /// `db`. From this point forward, only shared references to `db` /// will be possible. pub fn new(db: DB) -> Self { Snapshot { db } } } impl std::ops::Deref for Snapshot where DB: ParallelDatabase, { type Target = DB; fn deref(&self) -> &DB { &self.db } } /// An integer that uniquely identifies a particular query instance within the /// database. Used to track dependencies between queries. Fully ordered and /// equatable but those orderings are arbitrary, and meant to be used only for /// inserting into maps and the like. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct DatabaseKeyIndex { group_index: u16, query_index: u16, key_index: u32, } impl DatabaseKeyIndex { /// Returns the index of the query group containing this key. #[inline] pub fn group_index(self) -> u16 { self.group_index } /// Returns the index of the query within its query group. #[inline] pub fn query_index(self) -> u16 { self.query_index } /// Returns the index of this particular query key within the query. #[inline] pub fn key_index(self) -> u32 { self.key_index } /// Returns a type that gives a user-readable debug output. /// Use like `println!("{:?}", index.debug(db))`. pub fn debug(self, db: &D) -> impl std::fmt::Debug + '_ where D: plumbing::DatabaseOps, { DatabaseKeyIndexDebug { index: self, db } } } /// Helper type for `DatabaseKeyIndex::debug` struct DatabaseKeyIndexDebug<'me, D: ?Sized> where D: plumbing::DatabaseOps, { index: DatabaseKeyIndex, db: &'me D, } impl std::fmt::Debug for DatabaseKeyIndexDebug<'_, D> where D: plumbing::DatabaseOps, { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.db.fmt_index(self.index, fmt) } } /// Trait implements by all of the "special types" associated with /// each of your queries. /// /// Base trait of `Query` that has a lifetime parameter to allow the `DynDb` to be non-'static. pub trait QueryDb<'d>: Sized { /// Dyn version of the associated trait for this query group. type DynDb: ?Sized + Database + HasQueryGroup + 'd; /// Associate query group struct. type Group: plumbing::QueryGroup; /// Generated struct that contains storage for all queries in a group. type GroupStorage; } /// Trait implements by all of the "special types" associated with /// each of your queries. pub trait Query: Debug + Default + Sized + for<'d> QueryDb<'d> { /// Type that you you give as a parameter -- for queries with zero /// or more than one input, this will be a tuple. type Key: Clone + Debug + Hash + Eq; /// What value does the query return? type Value: Clone + Debug; /// Internal struct storing the values for the query. // type Storage: plumbing::QueryStorageOps; type Storage; /// A unique index identifying this query within the group. const QUERY_INDEX: u16; /// Name of the query method (e.g., `foo`) const QUERY_NAME: &'static str; /// Extract storage for this query from the storage for its group. fn query_storage<'a>( group_storage: &'a >::GroupStorage, ) -> &'a std::sync::Arc; /// Extract storage for this query from the storage for its group. fn query_storage_mut<'a>( group_storage: &'a >::GroupStorage, ) -> &'a std::sync::Arc; } /// Return value from [the `query` method] on `Database`. /// Gives access to various less common operations on queries. /// /// [the `query` method]: trait.Database.html#method.query pub struct QueryTable<'me, Q> where Q: Query, { db: &'me >::DynDb, storage: &'me Q::Storage, } impl<'me, Q> QueryTable<'me, Q> where Q: Query, Q::Storage: QueryStorageOps, { /// Constructs a new `QueryTable`. pub fn new(db: &'me >::DynDb, storage: &'me Q::Storage) -> Self { Self { db, storage } } /// Execute the query on a given input. Usually it's easier to /// invoke the trait method directly. Note that for variadic /// queries (those with no inputs, or those with more than one /// input) the key will be a tuple. pub fn get(&self, key: Q::Key) -> Q::Value { self.storage.fetch(self.db, &key) } /// Completely clears the storage for this query. /// /// This method breaks internal invariants of salsa, so any further queries /// might return nonsense results. It is useful only in very specific /// circumstances -- for example, when one wants to observe which values /// dropped together with the table pub fn purge(&self) where Q::Storage: plumbing::QueryStorageMassOps, { self.storage.purge(); } } /// Return value from [the `query_mut` method] on `Database`. /// Gives access to the `set` method, notably, that is used to /// set the value of an input query. /// /// [the `query_mut` method]: trait.Database.html#method.query_mut pub struct QueryTableMut<'me, Q> where Q: Query + 'me, { runtime: &'me mut Runtime, storage: &'me Q::Storage, } impl<'me, Q> QueryTableMut<'me, Q> where Q: Query, { /// Constructs a new `QueryTableMut`. pub fn new(runtime: &'me mut Runtime, storage: &'me Q::Storage) -> Self { Self { runtime, storage } } /// Assign a value to an "input query". Must be used outside of /// an active query computation. /// /// If you are using `snapshot`, see the notes on blocking /// and cancellation on [the `query_mut` method]. /// /// [the `query_mut` method]: trait.Database.html#method.query_mut pub fn set(&mut self, key: Q::Key, value: Q::Value) where Q::Storage: plumbing::InputQueryStorageOps, { self.set_with_durability(key, value, Durability::LOW); } /// Assign a value to an "input query", with the additional /// promise that this value will **never change**. Must be used /// outside of an active query computation. /// /// If you are using `snapshot`, see the notes on blocking /// and cancellation on [the `query_mut` method]. /// /// [the `query_mut` method]: trait.Database.html#method.query_mut pub fn set_with_durability(&mut self, key: Q::Key, value: Q::Value, durability: Durability) where Q::Storage: plumbing::InputQueryStorageOps, { self.storage.set(self.runtime, &key, value, durability); } /// Sets the size of LRU cache of values for this query table. /// /// That is, at most `cap` values will be preset in the table at the same /// time. This helps with keeping maximum memory usage under control, at the /// cost of potential extra recalculations of evicted values. /// /// If `cap` is zero, all values are preserved, this is the default. pub fn set_lru_capacity(&self, cap: usize) where Q::Storage: plumbing::LruQueryStorageOps, { self.storage.set_lru_capacity(cap); } /// Marks the computed value as outdated. /// /// This causes salsa to re-execute the query function on the next access to /// the query, even if all dependencies are up to date. /// /// This is most commonly used as part of the [on-demand input /// pattern](https://salsa-rs.github.io/salsa/common_patterns/on_demand_inputs.html). pub fn invalidate(&mut self, key: &Q::Key) where Q::Storage: plumbing::DerivedQueryStorageOps, { self.storage.invalidate(self.runtime, key) } } /// A panic payload indicating that execution of a salsa query was cancelled. /// /// This can occur for a few reasons: /// * /// * /// * #[derive(Debug)] #[non_exhaustive] pub enum Cancelled { /// The query was operating on revision R, but there is a pending write to move to revision R+1. #[non_exhaustive] PendingWrite, /// The query was blocked on another thread, and that thread panicked. #[non_exhaustive] PropagatedPanic, } impl Cancelled { fn throw(self) -> ! { // We use resume and not panic here to avoid running the panic // hook (that is, to avoid collecting and printing backtrace). std::panic::resume_unwind(Box::new(self)); } /// Runs `f`, and catches any salsa cancellation. pub fn catch(f: F) -> Result where F: FnOnce() -> T + UnwindSafe, { match panic::catch_unwind(f) { Ok(t) => Ok(t), Err(payload) => match payload.downcast() { Ok(cancelled) => Err(*cancelled), Err(payload) => panic::resume_unwind(payload), }, } } } impl std::fmt::Display for Cancelled { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let why = match self { Cancelled::PendingWrite => "pending write", Cancelled::PropagatedPanic => "propagated panic", }; f.write_str("cancelled because of ")?; f.write_str(why) } } impl std::error::Error for Cancelled {} /// Captures the participants of a cycle that occurred when executing a query. /// /// This type is meant to be used to help give meaningful error messages to the /// user or to help salsa developers figure out why their program is resulting /// in a computation cycle. /// /// It is used in a few ways: /// /// * During [cycle recovery](https://https://salsa-rs.github.io/salsa/cycles/fallback.html), /// where it is given to the fallback function. /// * As the panic value when an unexpected cycle (i.e., a cycle where one or more participants /// lacks cycle recovery information) occurs. /// /// You can read more about cycle handling in /// the [salsa book](https://https://salsa-rs.github.io/salsa/cycles.html). #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Cycle { participants: plumbing::CycleParticipants, } impl Cycle { pub(crate) fn new(participants: plumbing::CycleParticipants) -> Self { Self { participants } } /// True if two `Cycle` values represent the same cycle. pub(crate) fn is(&self, cycle: &Cycle) -> bool { triomphe::Arc::ptr_eq(&self.participants, &cycle.participants) } pub(crate) fn throw(self) -> ! { tracing::debug!("throwing cycle {:?}", self); std::panic::resume_unwind(Box::new(self)) } pub(crate) fn catch(execute: impl FnOnce() -> T) -> Result { match std::panic::catch_unwind(AssertUnwindSafe(execute)) { Ok(v) => Ok(v), Err(err) => match err.downcast::() { Ok(cycle) => Err(*cycle), Err(other) => std::panic::resume_unwind(other), }, } } /// Iterate over the [`DatabaseKeyIndex`] for each query participating /// in the cycle. The start point of this iteration within the cycle /// is arbitrary but deterministic, but the ordering is otherwise determined /// by the execution. pub fn participant_keys(&self) -> impl Iterator + '_ { self.participants.iter().copied() } /// Returns a vector with the debug information for /// all the participants in the cycle. pub fn all_participants(&self, db: &DB) -> Vec { self.participant_keys().map(|d| format!("{:?}", d.debug(db))).collect() } /// Returns a vector with the debug information for /// those participants in the cycle that lacked recovery /// information. pub fn unexpected_participants(&self, db: &DB) -> Vec { self.participant_keys() .filter(|&d| db.cycle_recovery_strategy(d) == CycleRecoveryStrategy::Panic) .map(|d| format!("{:?}", d.debug(db))) .collect() } /// Returns a "debug" view onto this strict that can be used to print out information. pub fn debug<'me, DB: ?Sized + Database>(&'me self, db: &'me DB) -> impl std::fmt::Debug + 'me { struct UnexpectedCycleDebug<'me> { c: &'me Cycle, db: &'me dyn Database, } impl<'me> std::fmt::Debug for UnexpectedCycleDebug<'me> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fmt.debug_struct("UnexpectedCycle") .field("all_participants", &self.c.all_participants(self.db)) .field("unexpected_participants", &self.c.unexpected_participants(self.db)) .finish() } } UnexpectedCycleDebug { c: self, db: db.ops_database() } } } // Re-export the procedural macros. #[allow(unused_imports)] #[macro_use] extern crate salsa_macros; use plumbing::HasQueryGroup; pub use salsa_macros::*;