From 06e0a04cb3ed731a41c98bcefdeb91ca6058f050 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 18 Aug 2022 19:33:12 -0400 Subject: [PATCH] explain more about rev counter, include snippets --- book/src/plumbing.md | 2 +- book/src/plumbing/database_and_runtime.md | 30 ++++++++++++++++++++- book/src/plumbing/jars_and_ingredients.md | 32 +++++++---------------- components/salsa-2022-macros/src/db.rs | 2 ++ components/salsa-2022/src/key.rs | 2 ++ components/salsa-2022/src/storage.rs | 21 +++++++++++++-- 6 files changed, 62 insertions(+), 27 deletions(-) diff --git a/book/src/plumbing.md b/book/src/plumbing.md index 6ae6bdcb..d5ee58c9 100644 --- a/book/src/plumbing.md +++ b/book/src/plumbing.md @@ -10,7 +10,7 @@ We refer to this as the "plumbing". The plumbing section is broken up into chapters: * The [jars and ingredients](./plumbing/jars_and_ingredients.md) covers how each salsa item (like a tracked function) specifies what data it needs and runtime, and how links between items work. -* The [database and runtime](./plumbing/database_and_runtime.md) +* The [database and runtime](./plumbing/database_and_runtime.md) covers the data structures that are used at runtime to coordinate workers, trigger cancellation, track which functions are active and what dependencies they have accrued, and so forth. * The [query operations](./plumbing/query_ops.md) chapter describes how the major operations on function ingredients work. This text was written for an older version of salsa but the logic is the same: * The [maybe changed after](./plumbing/maybe_changed_after.md) operation determines when a memoized value for a tracked function is out of date. * The [fetch](./plumbing/fetch.md) operation computes the most recent value. diff --git a/book/src/plumbing/database_and_runtime.md b/book/src/plumbing/database_and_runtime.md index 48529176..947b67c4 100644 --- a/book/src/plumbing/database_and_runtime.md +++ b/book/src/plumbing/database_and_runtime.md @@ -33,6 +33,32 @@ There are three key bits of data: * The `Routes` struct, which contains the information to find any particular ingredient -- this is also shared across all handles, and its construction is also described in the [jars and ingredients chapter](./jars_and_ingredients.md). The routes are separated out from the `Shared` struct because they are truly immutable at all times, and we want to be able to hold a handle to them while getting `&mut` access to the `Shared` struct. * The `Runtime` struct, which is specific to a particular database instance. It contains the data for a single active thread, along with some links to shraed data of its own. +## Incrementing the revision counter and getting mutable access to the jars + +Salsa's general model is that there is a single "master" copy of the database and, potentially, multiple snapshots. +The snapshots are not directly owned, they are instead enclosed in a `Snapshot` type that permits only `&`-deref, +and so the only database that can be accessed with an `&mut`-ref is the master database. +Each of the snapshots however onlys another handle on the `Arc` in `Storage` that stores the ingredients. + +Whenever the user attempts to do an `&mut`-operation, such as modifying an input field, that needs to +first cancel any parallel snapshots and wait for those parallel threads to finish. +Once the snapshots have completed, we can use `Arc::get_mut` to get an `&mut` reference to the ingredient data. +This allows us to get `&mut` access without any unsafe code and +guarantees that we have successfully managed to cancel the other worker threads +(or gotten ourselves into a deadlock). + +The code to acquire `&mut` access to the database is the `jars_mut` method: + +```rust +{{#include ../../../components/salsa-2022/src/storage.rs:jars_mut}} +``` + +The key initial point is that it invokes `cancel_other_workers` before proceeding: + +```rust +{{#include ../../../components/salsa-2022/src/storage.rs:cancel_other_workers}} +``` + ## The Salsa runtime The salsa runtime offers helper methods that are accessed by the ingredients. @@ -41,4 +67,6 @@ It also tracks the current revision and information about when values with low o Basically, the ingredient structures store the "data at rest" -- like memoized values -- and things that are "per ingredient". -The runtime stores the "active, in-progress" data, such as which queries are on the stack, and/or the dependencies accessed by the currently active query. \ No newline at end of file +The runtime stores the "active, in-progress" data, such as which queries are on the stack, and/or the dependencies accessed by the currently active query. + + diff --git a/book/src/plumbing/jars_and_ingredients.md b/book/src/plumbing/jars_and_ingredients.md index bad26fbc..e7564c04 100644 --- a/book/src/plumbing/jars_and_ingredients.md +++ b/book/src/plumbing/jars_and_ingredients.md @@ -72,7 +72,7 @@ This allows the database to perform generic operations on a numbered ingredient When you declare a salsa jar, you list out each of the salsa items that are included in that jar: -```rust +```rust,ignore #[salsa::jar] struct Jar( foo, @@ -115,21 +115,17 @@ The `HarJars` trait, among other things, defines a `Jars` associated type that m For example, given a database like this... -```rust +```rust,ignore #[salsa::db(Jar1, ..., JarN)] struct MyDatabase { storage: salsa::Storage } ``` -...the `salsa::db` macro would generate a `HasJars` impl that (among other things) contains... +...the `salsa::db` macro would generate a `HasJars` impl that (among other things) contains `type Jars = (Jar1, ..., JarN)`: -```rust -impl HarJars for MyDatabase { - type Jars = (Jar1, ..., JarN); - - /* other stuff elided */ -} +```rust,ignore +{{#include ../../../components/salsa-2022-macros/src/db.rs:HasJars}} ``` In turn, the `salsa::Storage` type ultimately contains a struct `Shared` that embeds `DB::Jars`, thus embedding all the data for each jar. @@ -156,11 +152,8 @@ as described shortly. A `DatabaseKeyIndex` identifies a specific value stored in some specific ingredient. It combines an [`IngredientIndex`] with a `key_index`, which is a `salsa::Id`: -```rust -pub struct DatabaseKeyIndex { - pub(crate) ingredient_index: IngredientIndex, - pub(crate) key_index: Id, -} +```rust,ignore +{{#include ../../../components/salsa-2022/src/key.rs:DatabaseKeyIndex}} ``` A `DependencyIndex` is similar, but the `key_index` is optional. @@ -183,15 +176,8 @@ If we had one function ingredient directly invoke a method on `Ingredient`, We solve this via the `HasJarsDyn` trait. The `HasJarsDyn` trait exports method that combine the "find ingredient, invoking method" steps into one method: -```rust -// Dyn friendly subset of HasJars -pub trait HasJarsDyn { - fn runtime(&self) -> &Runtime; - - fn maybe_changed_after(&self, input: DependencyIndex, revision: Revision) -> bool; - - ... -} +```rust,ignore +{{#include ../../../components/salsa-2022/src/storage.rs:HasJarsDyn}} ``` So, technically, to check if an input has changed, an ingredient: diff --git a/components/salsa-2022-macros/src/db.rs b/components/salsa-2022-macros/src/db.rs index bb9f5a0a..54c22ab8 100644 --- a/components/salsa-2022-macros/src/db.rs +++ b/components/salsa-2022-macros/src/db.rs @@ -86,8 +86,10 @@ fn has_jars_impl(args: &Args, input: &syn::ItemStruct, storage: &syn::Ident) -> let jar_paths: Vec<&syn::Path> = args.jar_paths.iter().collect(); let db = &input.ident; parse_quote! { + // ANCHOR: HasJars impl salsa::storage::HasJars for #db { type Jars = (#(#jar_paths,)*); + // ANCHOR_END: HasJars fn jars(&self) -> (&Self::Jars, &salsa::Runtime) { self.#storage.jars() diff --git a/components/salsa-2022/src/key.rs b/components/salsa-2022/src/key.rs index 9710f0c9..d1369bdb 100644 --- a/components/salsa-2022/src/key.rs +++ b/components/salsa-2022/src/key.rs @@ -43,6 +43,7 @@ where } } +// ANCHOR: DatabaseKeyIndex /// An "active" database key index represents a database key index /// that is actively executing. In that case, the `key_index` cannot be /// None. @@ -51,6 +52,7 @@ pub struct DatabaseKeyIndex { pub(crate) ingredient_index: IngredientIndex, pub(crate) key_index: Id, } +// ANCHOR_END: DatabaseKeyIndex impl DatabaseKeyIndex { pub fn ingredient_index(self) -> IngredientIndex { diff --git a/components/salsa-2022/src/storage.rs b/components/salsa-2022/src/storage.rs index b1b1c637..bf5ba0ea 100644 --- a/components/salsa-2022/src/storage.rs +++ b/components/salsa-2022/src/storage.rs @@ -93,23 +93,35 @@ where &self.runtime } + // ANCHOR: jars_mut /// Gets mutable access to the jars. This will trigger a new revision /// and it will also cancel any ongoing work in the current revision. /// Any actual writes that occur to data in a jar should use /// [`Runtime::report_tracked_write`]. pub fn jars_mut(&mut self) -> (&mut DB::Jars, &mut Runtime) { + // Wait for all snapshots to be dropped. self.cancel_other_workers(); + + // Increment revision counter. self.runtime.new_revision(); - let routes = self.routes.clone(); + // Acquire `&mut` access to `self.shared` -- this is only possible because + // the snapshots have all been dropped, so we hold the only handle to the `Arc`. let shared = Arc::get_mut(&mut self.shared).unwrap(); + + // Inform other ingredients that a new revision has begun. + // This gives them a chance to free resources that were being held until the next revision. + let routes = self.routes.clone(); for route in routes.reset_routes() { route(&mut shared.jars).reset_for_new_revision(); } + // Return mut ref to jars + runtime. (&mut shared.jars, &mut self.runtime) } + // ANCHOR_END: jars_mut + // ANCHOR: cancel_other_workers /// Sets cancellation flag and blocks until all other workers with access /// to this storage have completed. /// @@ -128,11 +140,14 @@ where // We create a mutex here because the cvar api requires it, but we // don't really need one as the data being protected is actually // the jars above. + // + // The cvar `self.shared.cvar` is notified by the `Drop` impl. let mutex = parking_lot::Mutex::new(()); let mut guard = mutex.lock(); self.shared.cvar.wait(&mut guard); } } + // ANCHOR_END: cancel_other_workers pub fn ingredient(&self, ingredient_index: IngredientIndex) -> &dyn Ingredient { let route = self.routes.route(ingredient_index); @@ -179,7 +194,8 @@ pub trait HasJar { fn jar_mut(&mut self) -> (&mut J, &mut Runtime); } -// Dyn friendly subset of HasJars +// ANCHOR: HasJarsDyn +/// Dyn friendly subset of HasJars pub trait HasJarsDyn { fn runtime(&self) -> &Runtime; @@ -205,6 +221,7 @@ pub trait HasJarsDyn { /// [`SalsaStructInDb::register_dependent_fn`](`crate::salsa_struct::SalsaStructInDb::register_dependent_fn`). fn salsa_struct_deleted(&self, ingredient: IngredientIndex, id: Id); } +// ANCHOR_END: HasJarsDyn pub trait HasIngredientsFor where