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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
/// 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);
#[doc(hidden)]
pub type Trigger = fn(&'static str, &'static str, u32);
/// This function will be called any time the [`FAULT_INJECT_COUNTER`] reaches 0
/// and an error is injected. You can use this to re-set the counter for deep
/// fault tree enumeration, test auditing, etc...
///
/// The function accepts the crate name, file name, and line number as arguments.
///
/// [`FAULT_INJECT_COUNTER`]: FAULT_INJECT_COUNTER
pub fn set_trigger_function(
f: fn(crate_name: &'static str, file_name: &'static str, line_number: u32),
) {
TRIGGER_FN.store(f as usize as _, core::sync::atomic::Ordering::Release);
}
#[doc(hidden)]
pub static TRIGGER_FN: core::sync::atomic::AtomicPtr<Trigger> =
core::sync::atomic::AtomicPtr::new(0 as usize as _);
/// 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 std::io;
///
/// use fault_injection::{fallible, set_trigger_function, FAULT_INJECT_COUNTER};
///
/// fn trigger_fn(crate_name: &str, file_name: &str, line_number: u32) {
/// println!(
/// "fault injected at {} {} {}",
/// crate_name, file_name, line_number
/// );
/// }
///
/// fn do_io() -> io::Result<()> {
/// Ok(())
/// }
///
/// // this will return an injected error
/// fn use_it() -> std::io::Result<()> {
/// set_trigger_function(trigger_fn);
/// FAULT_INJECT_COUNTER.store(1, std::sync::atomic::Ordering::Release);
///
/// fallible!(do_io());
///
/// Ok(())
/// }
///
/// assert!(use_it().is_err());
/// ```
///
///
/// [`FAULT_INJECT_COUNTER`]: FAULT_INJECT_COUNTER
/// [`SLEEPINESS`]: SLEEPINESS
#[macro_export]
macro_rules! fallible {
($e:expr) => {{
fault_injection::maybe!($e)?
}};
}
/// Performs the same fault injection as [`fallible`] but does not
/// early-return, and does not try to convert the injected
/// `io::Error` using the `?` operator.
///
/// [`fallible`]: fallible
#[macro_export]
macro_rules! maybe {
($e:expr) => {{
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 = 0b10_u16;
let random_sleeps = rdtsc.trailing_zeros() as u32 * sleepiness;
for _ in 0..random_sleeps {
std::thread::yield_now();
}
}
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
{
let trigger_fn = fault_injection::TRIGGER_FN.load(core::sync::atomic::Ordering::Acquire);
if !trigger_fn.is_null() {
unsafe {
let f: fault_injection::Trigger = std::mem::transmute(trigger_fn);
(f)(CRATE_NAME, file!(), line!());
}
}
Err(fault_injection::annotate(std::io::Error::new(
std::io::ErrorKind::Other,
"injected fault",
)))
} else {
match $e {
Ok(ok) => Ok(ok),
Err(e) => Err(fault_injection::annotate(e)),
}
}
}};
}
/// Annotates an io::Error with the crate, file, and line number
/// where the annotation has been performed.
pub fn annotate(error: std::io::Error) -> std::io::Error {
const CRATE_NAME: &str = if let Some(name) = core::option_env!("CARGO_CRATE_NAME") {
name
} else {
""
};
std::io::Error::new(
error.kind(),
format!(
"{}:{}:{} -> {}",
CRATE_NAME,
file!(),
line!(),
error.to_string()
),
)
}