explain more about rev counter, include snippets

This commit is contained in:
Niko Matsakis 2022-08-18 19:33:12 -04:00
parent 030caa1d21
commit 06e0a04cb3
6 changed files with 62 additions and 27 deletions

View file

@ -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.

View file

@ -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<DB>` 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.
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.

View file

@ -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<Self>
}
```
...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<DB>` 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<DB>`,
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:

View file

@ -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()

View file

@ -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 {

View file

@ -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<DB> {
let route = self.routes.route(ingredient_index);
@ -179,7 +194,8 @@ pub trait HasJar<J> {
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<I>
where