use crate::Backtrace;
use crate::DatabaseKeyIndex;
use crate::function::memo::{Memo, MemoHeader};
use crate::function::{Configuration, IngredientImpl};
use crate::zalsa_local::QueryRevisions;
use std::fmt;
impl<C> IngredientImpl<C>
where
C: Configuration,
{
pub(super) fn backdate_if_appropriate<'db>(
&self,
old_memo: &Memo<'db, C>,
index: DatabaseKeyIndex,
revisions: &mut QueryRevisions,
value: &C::Output<'db>,
) {
if old_memo.header.can_backdate(revisions)
&& old_memo
.value
.as_ref()
.is_some_and(|old_value| C::values_equal(old_value, value))
{
old_memo.header.backdate(index, revisions);
}
}
}
impl MemoHeader {
fn can_backdate(&self, revisions: &QueryRevisions) -> bool {
revisions.cycle_heads().is_empty()
&& !self.may_be_provisional()
&& revisions.durability >= self.revisions.durability
}
fn backdate(&self, index: DatabaseKeyIndex, revisions: &mut QueryRevisions) {
crate::tracing::debug!(
"{index:?} value is equal, back-dating to {:?}",
self.revisions.changed_at,
);
if self.revisions.changed_at > revisions.changed_at {
report_backdate_violation(index, self.revisions.changed_at, revisions.changed_at);
}
revisions.changed_at = self.revisions.changed_at;
}
}
#[cold]
#[inline(never)]
fn report_backdate_violation(
index: DatabaseKeyIndex,
old_changed_at: crate::Revision,
new_changed_at: crate::Revision,
) {
if cfg!(debug_assertions) {
let message = BackdateViolation {
index,
old_changed_at,
new_changed_at,
backtrace: None,
};
panic!("{message}");
} else {
let message = BackdateViolation {
index,
old_changed_at,
new_changed_at,
backtrace: Backtrace::capture(),
};
crate::tracing::warn!("{message}");
}
}
struct BackdateViolation {
index: DatabaseKeyIndex,
old_changed_at: crate::Revision,
new_changed_at: crate::Revision,
backtrace: Option<Backtrace>,
}
impl fmt::Display for BackdateViolation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"query {:?} returned the same value, but the previous execution changed at {:?} and \
the new execution changed at {:?}. This usually means the query re-executed because \
an input changed, but then branched on untracked state (for example, a global \
variable, a non-salsa field on the database, or filesystem state read outside salsa) \
and no longer read that input. This is usually a bug in the query implementation. \
Queries that branch on untracked state can also produce stale results. If the query \
has no untracked reads, please open a Salsa issue.",
self.index, self.old_changed_at, self.new_changed_at,
)?;
if let Some(backtrace) = &self.backtrace {
write!(f, "\n{backtrace}")?;
}
Ok(())
}
}