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()
        ),
    )
}