mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-01-15 01:39:25 +00:00
fad97eeb6a
This had two unexpected consequences, one unfortunate, one "medium": * All `salsa::Database` must be `'static`. This falls out from `Q::DynDb` not having access to any lifetimes, but also the defaulting rules for `dyn QueryGroup` that make it `dyn QueryGroup + 'static`. We don't really support generic databases anyway yet so this isn't a big deal, and we can add workarounds later (ideally via GATs). * It is now statically impossible to invoke `snapshot` from a query, and so we don't need to test that it panics. This is because the signature of `snapshot` returns a `Snapshot<Self>` and that is not accessible to a `dyn QueryGroup` type. Similarly, invoking `Runtime::snapshot` directly is not possible becaues it is crate-private. So I removed the test. This seems ok, but eventually I would like to expose ways for queries to do parallel execution (matklad and I had talked about a "speculation" primitive for enabling that). * This commit is 99% boilerplate I did with search-and-replace. I also rolled in a few other changes I might have preferred to factor out, most notably removing the `GetQueryTable` plumbing trait in favor of free-methods, but it was awkward to factor them out and get all the generics right (so much simpler in this version).
224 lines
5.9 KiB
Rust
224 lines
5.9 KiB
Rust
use crate::signal::Signal;
|
|
use salsa::Database;
|
|
use salsa::ParallelDatabase;
|
|
use salsa::Snapshot;
|
|
use std::cell::Cell;
|
|
use std::sync::Arc;
|
|
|
|
#[salsa::query_group(Par)]
|
|
pub(crate) trait ParDatabase: Knobs {
|
|
#[salsa::input]
|
|
fn input(&self, key: char) -> usize;
|
|
|
|
fn sum(&self, key: &'static str) -> usize;
|
|
|
|
/// Invokes `sum`
|
|
fn sum2(&self, key: &'static str) -> usize;
|
|
|
|
/// Invokes `sum` but doesn't really care about the result.
|
|
fn sum2_drop_sum(&self, key: &'static str) -> usize;
|
|
|
|
/// Invokes `sum2`
|
|
fn sum3(&self, key: &'static str) -> usize;
|
|
|
|
/// Invokes `sum2_drop_sum`
|
|
fn sum3_drop_sum(&self, key: &'static str) -> usize;
|
|
}
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
pub(crate) struct Canceled;
|
|
|
|
impl Canceled {
|
|
fn throw() -> ! {
|
|
// Don't print backtrace
|
|
std::panic::resume_unwind(Box::new(Canceled));
|
|
}
|
|
}
|
|
|
|
/// Various "knobs" and utilities used by tests to force
|
|
/// a certain behavior.
|
|
pub(crate) trait Knobs {
|
|
fn knobs(&self) -> &KnobsStruct;
|
|
|
|
fn signal(&self, stage: usize);
|
|
|
|
fn wait_for(&self, stage: usize);
|
|
}
|
|
|
|
pub(crate) trait WithValue<T> {
|
|
fn with_value<R>(&self, value: T, closure: impl FnOnce() -> R) -> R;
|
|
}
|
|
|
|
impl<T> WithValue<T> for Cell<T> {
|
|
fn with_value<R>(&self, value: T, closure: impl FnOnce() -> R) -> R {
|
|
let old_value = self.replace(value);
|
|
|
|
let result = closure();
|
|
|
|
self.set(old_value);
|
|
|
|
result
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
pub(crate) enum CancelationFlag {
|
|
Down,
|
|
Panic,
|
|
SpecialValue,
|
|
}
|
|
|
|
impl Default for CancelationFlag {
|
|
fn default() -> CancelationFlag {
|
|
CancelationFlag::Down
|
|
}
|
|
}
|
|
|
|
/// Various "knobs" that can be used to customize how the queries
|
|
/// behave on one specific thread. Note that this state is
|
|
/// intentionally thread-local (apart from `signal`).
|
|
#[derive(Clone, Default)]
|
|
pub(crate) struct KnobsStruct {
|
|
/// A kind of flexible barrier used to coordinate execution across
|
|
/// threads to ensure we reach various weird states.
|
|
pub(crate) signal: Arc<Signal>,
|
|
|
|
/// When this database is about to block, send a signal.
|
|
pub(crate) signal_on_will_block: Cell<usize>,
|
|
|
|
/// Invocations of `sum` will signal this stage on entry.
|
|
pub(crate) sum_signal_on_entry: Cell<usize>,
|
|
|
|
/// Invocations of `sum` will wait for this stage on entry.
|
|
pub(crate) sum_wait_for_on_entry: Cell<usize>,
|
|
|
|
/// If true, invocations of `sum` will panic before they exit.
|
|
pub(crate) sum_should_panic: Cell<bool>,
|
|
|
|
/// If true, invocations of `sum` will wait for cancellation before
|
|
/// they exit.
|
|
pub(crate) sum_wait_for_cancellation: Cell<CancelationFlag>,
|
|
|
|
/// Invocations of `sum` will wait for this stage prior to exiting.
|
|
pub(crate) sum_wait_for_on_exit: Cell<usize>,
|
|
|
|
/// Invocations of `sum` will signal this stage prior to exiting.
|
|
pub(crate) sum_signal_on_exit: Cell<usize>,
|
|
|
|
/// Invocations of `sum3_drop_sum` will panic unconditionally
|
|
pub(crate) sum3_drop_sum_should_panic: Cell<bool>,
|
|
}
|
|
|
|
fn sum(db: &dyn ParDatabase, key: &'static str) -> usize {
|
|
let mut sum = 0;
|
|
|
|
db.signal(db.knobs().sum_signal_on_entry.get());
|
|
|
|
db.wait_for(db.knobs().sum_wait_for_on_entry.get());
|
|
|
|
if db.knobs().sum_should_panic.get() {
|
|
panic!("query set to panic before exit")
|
|
}
|
|
|
|
for ch in key.chars() {
|
|
sum += db.input(ch);
|
|
}
|
|
|
|
match db.knobs().sum_wait_for_cancellation.get() {
|
|
CancelationFlag::Down => (),
|
|
flag => {
|
|
log::debug!("waiting for cancellation");
|
|
while !db.salsa_runtime().is_current_revision_canceled() {
|
|
std::thread::yield_now();
|
|
}
|
|
log::debug!("observed cancelation");
|
|
if flag == CancelationFlag::Panic {
|
|
Canceled::throw();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for cancelation and return MAX if so. Note that we check
|
|
// for cancelation *deterministically* -- but if
|
|
// `sum_wait_for_cancellation` is set, we will block
|
|
// beforehand. Deterministic execution is a requirement for valid
|
|
// salsa user code. It's also important to some tests that `sum`
|
|
// *attempts* to invoke `is_current_revision_canceled` even if we
|
|
// know it will not be canceled, because that helps us keep the
|
|
// accounting up to date.
|
|
if db.salsa_runtime().is_current_revision_canceled() {
|
|
return std::usize::MAX; // when we are cancelled, we return usize::MAX.
|
|
}
|
|
|
|
db.wait_for(db.knobs().sum_wait_for_on_exit.get());
|
|
|
|
db.signal(db.knobs().sum_signal_on_exit.get());
|
|
|
|
sum
|
|
}
|
|
|
|
fn sum2(db: &dyn ParDatabase, key: &'static str) -> usize {
|
|
db.sum(key)
|
|
}
|
|
|
|
fn sum2_drop_sum(db: &dyn ParDatabase, key: &'static str) -> usize {
|
|
let _ = db.sum(key);
|
|
22
|
|
}
|
|
|
|
fn sum3(db: &dyn ParDatabase, key: &'static str) -> usize {
|
|
db.sum2(key)
|
|
}
|
|
|
|
fn sum3_drop_sum(db: &dyn ParDatabase, key: &'static str) -> usize {
|
|
if db.knobs().sum3_drop_sum_should_panic.get() {
|
|
panic!("sum3_drop_sum executed")
|
|
}
|
|
db.sum2_drop_sum(key)
|
|
}
|
|
|
|
#[salsa::database(Par)]
|
|
#[derive(Default)]
|
|
pub(crate) struct ParDatabaseImpl {
|
|
storage: salsa::Storage<Self>,
|
|
knobs: KnobsStruct,
|
|
}
|
|
|
|
impl Database for ParDatabaseImpl {
|
|
fn salsa_event(&self, event: salsa::Event) {
|
|
match event.kind {
|
|
salsa::EventKind::WillBlockOn { .. } => {
|
|
self.signal(self.knobs().signal_on_will_block.get());
|
|
}
|
|
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn on_propagated_panic(&self) -> ! {
|
|
Canceled::throw()
|
|
}
|
|
}
|
|
|
|
impl ParallelDatabase for ParDatabaseImpl {
|
|
fn snapshot(&self) -> Snapshot<Self> {
|
|
Snapshot::new(ParDatabaseImpl {
|
|
storage: self.storage.snapshot(),
|
|
knobs: self.knobs.clone(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Knobs for ParDatabaseImpl {
|
|
fn knobs(&self) -> &KnobsStruct {
|
|
&self.knobs
|
|
}
|
|
|
|
fn signal(&self, stage: usize) {
|
|
self.knobs.signal.signal(stage);
|
|
}
|
|
|
|
fn wait_for(&self, stage: usize) {
|
|
self.knobs.signal.wait_for(stage);
|
|
}
|
|
}
|