From 9d474363fc61008671ddede2cc1758dc030e0c88 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 25 Jun 2019 18:02:56 -0400 Subject: [PATCH] s/next_revision/synthetic_write/ Also write some docs explaining its side-effects. --- src/runtime.rs | 41 ++++++++++++++++------ tests/gc/derived_tests.rs | 48 +++++++++++++++++++++++--- tests/gc/discard_values.rs | 4 +-- tests/gc/shallow_constant_tests.rs | 8 ++--- tests/incremental/memoized_volatile.rs | 6 ++-- tests/storage_varieties/tests.rs | 5 +-- 6 files changed, 85 insertions(+), 27 deletions(-) diff --git a/src/runtime.rs b/src/runtime.rs index dbe64211..b7406cee 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -116,18 +116,37 @@ where } } - /// Indicates that some input to the system has changed and hence - /// that memoized values **may** be invalidated. This cannot be - /// invoked while query computation is in progress. + /// A "synthetic write" causes the system to act *as though* some + /// input of durability `durability` has changed. This is mostly + /// useful for profiling scenarios, but it also has interactions + /// with garbage collection. In general, a synthetic write to + /// durability level D will cause the system to fully trace all + /// queries of durability level D and below. When running a GC, then: /// - /// As a user of the system, you would not normally invoke this - /// method directly. Instead, you would use "input" queries and - /// invoke their `set` method. But it can be useful if you have a - /// "volatile" input that you must poll from time to time; in that - /// case, you can wrap the input with a "no-storage" query and - /// invoke this method from time to time. - pub fn next_revision(&self) { - self.with_incremented_revision(|_| ()); + /// - Synthetic writes will cause more derived values to be + /// *retained*. This is because derived values are only + /// retained if they are traced, and a synthetic write can cause + /// more things to be traced. + /// - Synthetic writes can cause more interned values to be + /// *collected*. This is because interned values can only be + /// collected if they were not yet traced in the current + /// revision. Therefore, if you issue a synthetic write, execute + /// some query Q, and then start collecting interned values, you + /// will be able to recycle interned values not used in Q. + /// + /// In general, then, one can do a "full GC" that retains only + /// those things that are used by some query Q by (a) doing a + /// synthetic write at `Durability::HIGH`, (b) executing the query + /// Q and then (c) doing a sweep. + /// + /// **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. + pub fn synthetic_write(&self, durability: Durability) { + self.with_incremented_revision(|guard| { + guard.mark_durability_as_changed(durability); + }); } /// Default implementation for `Database::sweep_all`. diff --git a/tests/gc/derived_tests.rs b/tests/gc/derived_tests.rs index 6faeab13..628c8948 100644 --- a/tests/gc/derived_tests.rs +++ b/tests/gc/derived_tests.rs @@ -1,17 +1,17 @@ use crate::db; use crate::group::*; use salsa::debug::DebugQueryTable; -use salsa::{Database, SweepStrategy}; +use salsa::{Database, Durability, SweepStrategy}; #[test] -fn compute_one() { +fn compute_one_write_low() { let mut db = db::DatabaseImpl::default(); // Will compute fibonacci(5) db.set_use_triangular(5, false); db.compute(5); - db.salsa_runtime().next_revision(); + db.salsa_runtime().synthetic_write(Durability::LOW); assert_keys! { db, @@ -38,6 +38,44 @@ fn compute_one() { } } +#[test] +fn compute_one_write_high() { + let mut db = db::DatabaseImpl::default(); + + // Will compute fibonacci(5) + db.set_use_triangular(5, false); + db.compute(5); + + // Doing a synthetic write with durability *high* means that we + // will revalidate the things `compute(5)` uses, and hence they + // are not discarded. + db.salsa_runtime().synthetic_write(Durability::HIGH); + + assert_keys! { + db, + TriangularQuery => (), + FibonacciQuery => (0, 1, 2, 3, 4, 5), + ComputeQuery => (5), + UseTriangularQuery => (5), + MinQuery => (), + MaxQuery => (), + } + + // Memoized, but will compute fibonacci(5) again + db.compute(5); + db.sweep_all(SweepStrategy::discard_outdated()); + + assert_keys! { + db, + TriangularQuery => (), + FibonacciQuery => (0, 1, 2, 3, 4, 5), + ComputeQuery => (5), + UseTriangularQuery => (5), + MinQuery => (), + MaxQuery => (), + } +} + #[test] fn compute_switch() { let mut db = db::DatabaseImpl::default(); @@ -78,7 +116,7 @@ fn compute_switch() { } // Now run `compute` *again* in next revision. - db.salsa_runtime().next_revision(); + db.salsa_runtime().synthetic_write(Durability::LOW); assert_eq!(db.compute(5), 15); db.sweep_all(SweepStrategy::discard_outdated()); @@ -107,7 +145,7 @@ fn compute_all() { db.set_max(6); db.compute_all(); - db.salsa_runtime().next_revision(); + db.salsa_runtime().synthetic_write(Durability::LOW); db.compute_all(); db.sweep_all(SweepStrategy::discard_outdated()); diff --git a/tests/gc/discard_values.rs b/tests/gc/discard_values.rs index 2db2b60f..9c38c313 100644 --- a/tests/gc/discard_values.rs +++ b/tests/gc/discard_values.rs @@ -1,7 +1,7 @@ use crate::db; use crate::group::{FibonacciQuery, GcDatabase}; use salsa::debug::DebugQueryTable; -use salsa::{Database, SweepStrategy}; +use salsa::{Database, Durability, SweepStrategy}; #[test] fn sweep_default() { @@ -12,7 +12,7 @@ fn sweep_default() { let k: Vec<_> = db.query(FibonacciQuery).entries(); assert_eq!(k.len(), 6); - db.salsa_runtime().next_revision(); + db.salsa_runtime().synthetic_write(Durability::LOW); db.fibonacci(5); db.fibonacci(3); diff --git a/tests/gc/shallow_constant_tests.rs b/tests/gc/shallow_constant_tests.rs index a5eef000..1dd39be9 100644 --- a/tests/gc/shallow_constant_tests.rs +++ b/tests/gc/shallow_constant_tests.rs @@ -1,7 +1,7 @@ use crate::db; use crate::group::{FibonacciQuery, GcDatabase}; use salsa::debug::DebugQueryTable; -use salsa::{Database, SweepStrategy}; +use salsa::{Database, Durability, SweepStrategy}; // For constant values (like `fibonacci`), we only keep the values // that were used in the latest revision, not the sub-values that @@ -31,7 +31,7 @@ fn two_rev_nothing() { let k: Vec<_> = db.query(FibonacciQuery).entries(); assert_eq!(k.len(), 6); - db.salsa_runtime().next_revision(); + db.salsa_runtime().synthetic_write(Durability::LOW); // Nothing was used in this revision, so // everything gets collected. @@ -50,7 +50,7 @@ fn two_rev_one_use() { let k: Vec<_> = db.query(FibonacciQuery).entries(); assert_eq!(k.len(), 6); - db.salsa_runtime().next_revision(); + db.salsa_runtime().synthetic_write(Durability::LOW); db.fibonacci(5); @@ -73,7 +73,7 @@ fn two_rev_two_uses() { let k: Vec<_> = db.query(FibonacciQuery).entries(); assert_eq!(k.len(), 6); - db.salsa_runtime().next_revision(); + db.salsa_runtime().synthetic_write(Durability::LOW); db.fibonacci(5); db.fibonacci(3); diff --git a/tests/incremental/memoized_volatile.rs b/tests/incremental/memoized_volatile.rs index b48b1219..e2ffcbff 100644 --- a/tests/incremental/memoized_volatile.rs +++ b/tests/incremental/memoized_volatile.rs @@ -1,5 +1,5 @@ use crate::implementation::{TestContext, TestContextImpl}; -use salsa::Database; +use salsa::{Database, Durability}; #[salsa::query_group(MemoizedVolatile)] pub(crate) trait MemoizedVolatileContext: TestContext { @@ -58,7 +58,7 @@ fn revalidate() { // Second generation: volatile will change (to 1) but memoized1 // will not (still 0, as 1/2 = 0) - query.salsa_runtime().next_revision(); + query.salsa_runtime().synthetic_write(Durability::LOW); query.memoized2(); query.assert_log(&["Memoized1 invoked", "Volatile invoked"]); query.memoized2(); @@ -67,7 +67,7 @@ fn revalidate() { // Third generation: volatile will change (to 2) and memoized1 // will too (to 1). Therefore, after validating that Memoized1 // changed, we now invoke Memoized2. - query.salsa_runtime().next_revision(); + query.salsa_runtime().synthetic_write(Durability::LOW); query.memoized2(); query.assert_log(&["Memoized1 invoked", "Volatile invoked", "Memoized2 invoked"]); diff --git a/tests/storage_varieties/tests.rs b/tests/storage_varieties/tests.rs index ce38ba8e..d765080c 100644 --- a/tests/storage_varieties/tests.rs +++ b/tests/storage_varieties/tests.rs @@ -3,6 +3,7 @@ use crate::implementation::DatabaseImpl; use crate::queries::Database; use salsa::Database as _Database; +use salsa::Durability; #[test] fn memoized_twice() { @@ -19,7 +20,7 @@ fn volatile_twice() { let v2 = db.volatile(); // volatiles are cached, so 2nd read returns the same assert_eq!(v1, v2); - db.salsa_runtime().next_revision(); // clears volatile caches + db.salsa_runtime().synthetic_write(Durability::LOW); // clears volatile caches let v3 = db.volatile(); // will re-increment the counter let v4 = db.volatile(); // second call will be cached @@ -39,7 +40,7 @@ fn intermingled() { assert_eq!(v1, v3); assert_eq!(v2, v4); - db.salsa_runtime().next_revision(); // clears volatile caches + db.salsa_runtime().synthetic_write(Durability::LOW); // clears volatile caches let v5 = db.memoized(); // re-executes volatile, caches new result let v6 = db.memoized(); // re-use cached result