1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
/// Facilitates fault injection. Every time any IO operation
/// is performed, this is decremented. If it hits 0, an
/// io::Error is returned from that IO operation. Use this
/// to ensure that error handling is being performed, by
/// running some test workload, checking the counter, and
/// then setting this to an incrementally-lower number while
/// asserting that your application properly handles the
/// error that will propagate up. Defaults to `u64::MAX`,
/// so it won't be hit normally unless you do something 6 billion
/// times per second for 100 years. If you're building something
/// like that, maybe consider re-setting this to `u64::MAX` every
/// few decades for safety.
pub static FAULT_INJECT_COUNTER: core::sync::atomic::AtomicU64 =
core::sync::atomic::AtomicU64::new(u64::MAX);
/// Facilitates delay injection. If you set this to something other
/// than 0, the `fallible!` macro will randomly call `std::thread::yield_now()`,
/// with the nubmer of times being multiplied by this value. You should not
/// need to set it very high to get a lot of delays, but you'll need
/// to play with the number sometimes for specific concurrent systems under test.
pub static SLEEPINESS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
/// Similar to the `try!` macro or `?` operator,
/// but externally controllable to inject faults
/// during testing. Unlike the `try!` macro or `?`
/// operator, this additionally annotates the
/// description of the error to include the crate,
/// file name, and line number where the error
/// originated from to facilitate quick debugging.
/// It is specialized to work with `io::Result`
/// types, and will return an `io::Error` for faults,
/// with `into()` called similar to the `try!` macro
/// or `?` operator.
/// Decrements the [`FAULT_INJECT_COUNTER`] by `1`
/// (it is set to `u64::MAX` by default), and if
/// it hits 0, returns an `io::Error` with a kind
/// of `Other`. If [`SLEEPINESS`] is set to
/// something other than 0, this macro will also
/// inject weakly pseudorandom delays for
/// facilitating a basic form of concurrency testing.
///
/// # Examples
/// ```
/// use fault_injection::{fallible, FAULT_INJECT_COUNTER};
///
/// fn do_io() -> std::io::Result<()> {
/// Ok(())
/// }
///
/// // this will return an injected error
/// fn use_it() -> std::io::Result<()> {
/// FAULT_INJECT_COUNTER.store(1, std::sync::atomic::Ordering::Release);
///
/// fallible!(do_io());
///
/// Ok(())
/// }
/// ```
///
///
/// [`FAULT_INJECT_COUNTER`]: FAULT_INJECT_COUNTER
/// [`SLEEPINESS`]: SLEEPINESS
#[macro_export]
macro_rules! fallible {
($e:expr) => {{
const CRATE_NAME: &str = if let Some(name) = core::option_env!("CARGO_CRATE_NAME") {
name
} else {
""
};
if fault_injection::FAULT_INJECT_COUNTER.fetch_sub(1, core::sync::atomic::Ordering::AcqRel)
== 1
{
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("injected fault at {}:{}:{}", CRATE_NAME, file!(), line!()),
)).into();
}
let sleepiness = fault_injection::SLEEPINESS.load(core::sync::atomic::Ordering::Acquire);
if sleepiness > 0 {
#[cfg(target_arch = "x86")]
let rdtsc = unsafe { core::arch::x86::_rdtsc() as u16 };
#[cfg(target_arch = "x86_64")]
let rdtsc = unsafe { core::arch::x86_64::_rdtsc() as u16 };
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
let rdtsc = 0;
let random_sleeps = rdtsc.trailing_zeros() as u32 * sleepiness;
for _ in 0..random_sleeps {
std::thread::yield_now();
}
}
// annotates io::Error to include the source of the error
match $e {
Ok(ok) => ok,
Err(e) => {
return Err(std::io::Error::new(
e.kind(),
format!(
"{}:{}:{} -> {}",
CRATE_NAME,
file!(),
line!(),
e.to_string()
),
)).into()
}
}
}};
}