fault_injection/lib.rs
1/// Facilitates fault injection. Every time any IO operation
2/// is performed, this is decremented. If it hits 0, an
3/// io::Error is returned from that IO operation. Use this
4/// to ensure that error handling is being performed, by
5/// running some test workload, checking the counter, and
6/// then setting this to an incrementally-lower number while
7/// asserting that your application properly handles the
8/// error that will propagate up. Defaults to `u64::MAX`,
9/// so it won't be hit normally unless you do something 6 billion
10/// times per second for 100 years. If you're building something
11/// like that, maybe consider re-setting this to `u64::MAX` every
12/// few decades for safety.
13pub static FAULT_INJECT_COUNTER: core::sync::atomic::AtomicU64 =
14 core::sync::atomic::AtomicU64::new(u64::MAX);
15
16/// Facilitates delay injection. If you set this to something other
17/// than 0, the `fallible!` macro will randomly call `std::thread::yield_now()`,
18/// with the nubmer of times being multiplied by this value. You should not
19/// need to set it very high to get a lot of delays, but you'll need
20/// to play with the number sometimes for specific concurrent systems under test.
21pub static SLEEPINESS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
22
23#[doc(hidden)]
24pub type Trigger = fn(crate_name: &'static str, file_name: &'static str, line_number: u32);
25
26/// This function will be called any time the [`FAULT_INJECT_COUNTER`] reaches 0
27/// and an error is injected. You can use this to re-set the counter for deep
28/// fault tree enumeration, test auditing, etc...
29///
30/// The function accepts the crate name, file name, and line number as arguments.
31///
32/// [`FAULT_INJECT_COUNTER`]: FAULT_INJECT_COUNTER
33pub fn set_trigger_function(f: Trigger) {
34 TRIGGER_FN.store(
35 f as usize as *mut Trigger,
36 core::sync::atomic::Ordering::Release,
37 );
38}
39
40#[doc(hidden)]
41pub static TRIGGER_FN: core::sync::atomic::AtomicPtr<Trigger> =
42 core::sync::atomic::AtomicPtr::new(0 as _);
43
44/// Similar to the `try!` macro or `?` operator,
45/// but externally controllable to inject faults
46/// during testing. Unlike the `try!` macro or `?`
47/// operator, this additionally annotates the
48/// description of the error to include the crate,
49/// file name, and line number where the error
50/// originated from to facilitate quick debugging.
51/// It is specialized to work with `io::Result`
52/// types, and will return an `io::Error` for faults,
53/// with `into()` called similar to the `try!` macro
54/// or `?` operator.
55/// Decrements the [`FAULT_INJECT_COUNTER`] by `1`
56/// (it is set to `u64::MAX` by default), and if
57/// it hits 0, returns an `io::Error` with a kind
58/// of `Other`. If [`SLEEPINESS`] is set to
59/// something other than 0, this macro will also
60/// inject weakly pseudorandom delays for
61/// facilitating a basic form of concurrency testing.
62///
63/// # Examples
64/// ```
65/// use std::io;
66///
67/// use fault_injection::{fallible, set_trigger_function, FAULT_INJECT_COUNTER};
68///
69/// fn trigger_fn(crate_name: &str, file_name: &str, line_number: u32) {
70/// println!(
71/// "fault injected at {} {} {}",
72/// crate_name, file_name, line_number
73/// );
74/// }
75///
76/// fn do_io() -> io::Result<()> {
77/// Ok(())
78/// }
79///
80/// // this will return an injected error
81/// fn use_it() -> std::io::Result<()> {
82/// set_trigger_function(trigger_fn);
83/// FAULT_INJECT_COUNTER.store(1, std::sync::atomic::Ordering::Release);
84///
85/// fallible!(do_io());
86///
87/// Ok(())
88/// }
89///
90/// assert!(use_it().is_err());
91/// ```
92///
93///
94/// [`FAULT_INJECT_COUNTER`]: FAULT_INJECT_COUNTER
95/// [`SLEEPINESS`]: SLEEPINESS
96#[macro_export]
97macro_rules! fallible {
98 ($e:expr) => {{
99 fault_injection::maybe!($e)?
100 }};
101}
102
103/// Performs the same fault injection as [`fallible`] but does not
104/// early-return, and does not try to convert the injected
105/// `io::Error` using the `?` operator.
106///
107/// [`fallible`]: fallible
108#[macro_export]
109macro_rules! maybe {
110 ($e:expr) => {{
111 let sleepiness = fault_injection::SLEEPINESS.load(core::sync::atomic::Ordering::Acquire);
112 if sleepiness > 0 {
113 #[cfg(target_arch = "x86")]
114 let rdtsc = unsafe { core::arch::x86::_rdtsc() as u16 };
115
116 #[cfg(target_arch = "x86_64")]
117 let rdtsc = unsafe { core::arch::x86_64::_rdtsc() as u16 };
118
119 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
120 let rdtsc = 0b10_u16;
121
122 let random_sleeps = rdtsc.trailing_zeros() as u32 * sleepiness;
123
124 for _ in 0..random_sleeps {
125 std::thread::yield_now();
126 }
127 }
128
129 const CRATE_NAME: &str = if let Some(name) = core::option_env!("CARGO_CRATE_NAME") {
130 name
131 } else {
132 ""
133 };
134
135 if fault_injection::FAULT_INJECT_COUNTER.fetch_sub(1, core::sync::atomic::Ordering::AcqRel)
136 == 1
137 {
138 let trigger_fn = fault_injection::TRIGGER_FN.load(core::sync::atomic::Ordering::Acquire);
139 if !trigger_fn.is_null() {
140 unsafe {
141 let f: &fault_injection::Trigger = std::mem::transmute(&trigger_fn);
142 (f)(CRATE_NAME, file!(), line!());
143 }
144 }
145
146 Err(fault_injection::annotate!(std::io::Error::new(
147 std::io::ErrorKind::Other,
148 "injected fault",
149 )))
150 } else {
151 match $e {
152 Ok(ok) => Ok(ok),
153 Err(e) => Err(fault_injection::annotate!(e)),
154 }
155 }
156 }};
157}
158
159/// Annotates an io::Error with the crate, file, and line number
160/// where the annotation has been performed.
161#[macro_export]
162macro_rules! annotate {
163 ($e:expr) => {{
164 const CRATE_NAME: &str = if let Some(name) = core::option_env!("CARGO_CRATE_NAME") {
165 name
166 } else {
167 ""
168 };
169
170 std::io::Error::new(
171 $e.kind(),
172 format!(
173 "{}:{}:{} -> {}",
174 CRATE_NAME,
175 file!(),
176 line!(),
177 $e.to_string()
178 ),
179 )
180 }};
181}