#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrashPoint {
AfterStart,
AfterStep,
BeforeFinish,
AfterFinish,
}
#[cfg(any(test, feature = "fault-injection"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrashCase {
CommitNotDurable,
CommitDurableNotAcknowledged,
MultiTombstoneDrop,
GroupCommit,
TakeoverMidCommit,
FencedWriterResumes,
GenesisInterrupted,
ConcurrentGenesis,
StagedBuildInterrupted,
ReclamationInterrupted,
MigrationInterrupted,
}
#[cfg(any(test, feature = "fault-injection"))]
impl CrashCase {
pub const ALL: [Self; 11] = [
Self::CommitNotDurable,
Self::CommitDurableNotAcknowledged,
Self::MultiTombstoneDrop,
Self::GroupCommit,
Self::TakeoverMidCommit,
Self::FencedWriterResumes,
Self::GenesisInterrupted,
Self::ConcurrentGenesis,
Self::StagedBuildInterrupted,
Self::ReclamationInterrupted,
Self::MigrationInterrupted,
];
#[must_use]
pub fn seams(self) -> &'static [CrashPoint] {
match self {
Self::MigrationInterrupted => &[
CrashPoint::AfterStart,
CrashPoint::AfterStep,
CrashPoint::BeforeFinish,
CrashPoint::AfterFinish,
],
_ => &[],
}
}
}
#[cfg(any(test, feature = "fault-injection"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SyntheticMigration {
#[default]
None,
MoveOptionScope,
MoveOptionScopeThenLink,
}
#[cfg(any(test, feature = "fault-injection"))]
mod armed {
use std::cell::Cell;
use super::{CrashPoint, SyntheticMigration};
use crate::{
error::{Error, Result},
transaction::migration::{MigrationUnit, synthetic},
};
thread_local! {
static ARMED: Cell<Option<CrashPoint>> = const { Cell::new(None) };
static INSTALLED: Cell<SyntheticMigration> = const { Cell::new(SyntheticMigration::None) };
}
pub fn inject_crash(point: Option<CrashPoint>) {
ARMED.with(|armed| armed.set(point));
}
pub fn install_migration(units: SyntheticMigration) {
INSTALLED.with(|installed| installed.set(units));
}
pub(crate) fn installed_migrations() -> &'static [&'static MigrationUnit] {
match INSTALLED.with(Cell::get) {
SyntheticMigration::None => &[],
SyntheticMigration::MoveOptionScope => synthetic::REWRITE,
SyntheticMigration::MoveOptionScopeThenLink => synthetic::REWRITE_THEN_LINK,
}
}
pub(crate) fn crash_seam(point: CrashPoint) -> Result<()> {
let tripped = ARMED.with(|armed| {
if armed.get() == Some(point) {
armed.set(None);
true
} else {
false
}
});
if tripped {
return Err(Error::Migration(
"fault-injection: simulated crash at a migration seam".to_string(),
));
}
Ok(())
}
}
#[cfg(any(test, feature = "fault-injection"))]
pub(crate) use armed::{crash_seam, installed_migrations};
#[cfg(any(test, feature = "fault-injection"))]
pub use armed::{inject_crash, install_migration};
#[cfg(not(any(test, feature = "fault-injection")))]
#[inline]
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn crash_seam(_point: CrashPoint) -> crate::error::Result<()> {
Ok(())
}
#[cfg(not(any(test, feature = "fault-injection")))]
#[inline]
pub(crate) fn installed_migrations()
-> &'static [&'static crate::transaction::migration::MigrationUnit] {
&[]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn armed_point_trips_its_seam_once() {
inject_crash(Some(CrashPoint::AfterStart));
assert!(crash_seam(CrashPoint::AfterStart).is_err());
assert!(crash_seam(CrashPoint::AfterStart).is_ok());
}
#[test]
fn unarmed_seam_is_a_noop() {
inject_crash(None);
assert!(crash_seam(CrashPoint::AfterFinish).is_ok());
}
#[test]
fn other_points_do_not_trip() {
inject_crash(Some(CrashPoint::BeforeFinish));
assert!(crash_seam(CrashPoint::AfterStep).is_ok());
}
#[test]
fn one_case_owns_the_driver_seams_and_the_rest_own_none() {
let seam_driven: Vec<CrashCase> = CrashCase::ALL
.into_iter()
.filter(|case| !case.seams().is_empty())
.collect();
assert_eq!(seam_driven, vec![CrashCase::MigrationInterrupted]);
assert_eq!(
CrashCase::MigrationInterrupted.seams(),
[
CrashPoint::AfterStart,
CrashPoint::AfterStep,
CrashPoint::BeforeFinish,
CrashPoint::AfterFinish
]
);
}
#[test]
fn installed_units_replace_and_clear() {
assert!(installed_migrations().is_empty());
install_migration(SyntheticMigration::MoveOptionScopeThenLink);
assert_eq!(installed_migrations().len(), 2);
install_migration(SyntheticMigration::MoveOptionScope);
assert_eq!(installed_migrations().len(), 1);
install_migration(SyntheticMigration::None);
assert!(installed_migrations().is_empty());
}
}