-
Notifications
You must be signed in to change notification settings - Fork 4
/
babb.h
189 lines (154 loc) · 6.51 KB
/
babb.h
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
178
179
180
181
182
183
184
185
186
187
188
189
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2019 Herb Sutter and Marshall Clow. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef BABB_BABB_H
#define BABB_BABB_H
#include <memory>
#include <limits>
#include <random>
#include <cassert>
namespace babb {
//----------------------------------------------------------------------------
// State values to control failure frequency and status
// We'll keep a global state, and a per-thread state
//----------------------------------------------------------------------------
class state {
protected:
int once_per = 100000; // avg #allocations between failures
int run_length = 5; // max #consecutive failures
bool paused = false; // is failure injection currently paused
// non-auto explicit return type is for portability to pre-C++14 compilers
bool invariant() noexcept
{ return once_per > 0 && run_length > 0; }
public:
//----------------------------------------------------------------------------
//
// set_failure_profile: Change current failure injection profile
//
// Call:
// shared.set_failure_profile to set the current global defaults
// this_thread.set_failure_profile to set this thread's current values
// Each thread initially defaults to the shared values.
//
// fail_once_per: avg #allocations between failures
// max_run_length: max #consecutive failures (once we have triggered a new one)
//
//----------------------------------------------------------------------------
void set_failure_profile(int fail_once_per, int max_run_length) noexcept {
once_per = fail_once_per;
run_length = max_run_length;
assert(invariant());
}
//----------------------------------------------------------------------------
//
// pause: Pause or unpause fault injection on this thread.
//
// This can be useful to work around individual calls to OOM-unsafe functions
// in third-party libraries (though if those are failing that's data too).
//
// on: true to pause, false to resume
//
//----------------------------------------------------------------------------
void pause(bool on) noexcept {
paused = on;
}
};
//----------------------------------------------------------------------------
//
// Helper RAII type to save/restore current settings
//
// This can be useful to work around entire third-party libraries that are
// OOM-unsafe (though if those are failing that's data too).
//
//----------------------------------------------------------------------------
class state_guard {
state& original;
state saved;
public:
state_guard(state& s) noexcept : original(s), saved(s) { }
~state_guard() noexcept { original = saved; }
};
//----------------------------------------------------------------------------
// Global state (used for thread defaults)
//----------------------------------------------------------------------------
state shared;
//----------------------------------------------------------------------------
// Per-thread state
//----------------------------------------------------------------------------
class this_thread_ : public state {
class prng {
// minstd_rand is sufficient and uses 1 word of storage
// mt19937_64 is generally better but is overkill here, it uses 600+
// words and we want this to be usable in constrained environments
std::minstd_rand r;
using rtype = decltype(r)::result_type;
public:
prng() noexcept : r((rtype)reinterpret_cast<std::size_t>(this)) { }
double operator()() noexcept
{ return 1.*r() / std::numeric_limits<rtype>::max(); }
};
prng random;
int run_in_progress = 0;
public:
this_thread_() : state(shared) { }
//----------------------------------------------------------------------------
//
// should_inject_random_failure()
//
// Returns true if it's time to inject a failure in this thread.
//
//----------------------------------------------------------------------------
bool should_inject_random_failure() noexcept {
assert(invariant());
if (paused) return false;
auto trigger_a_new_run =
[&]{ return random() < 1./once_per/(run_length/2.); };
if (run_in_progress == 0 && trigger_a_new_run()) {
run_in_progress = 1 + int(random()*(run_length-1));
assert(invariant() && run_in_progress > 0);
}
if (run_in_progress > 0) {
--run_in_progress;
return true;
}
else
return false;
}
//----------------------------------------------------------------------------
//
// inject_random_failure()
//
// Put a call to this function inside each of your custom allocation functions
// that could throw an allocation exception, including any custom non-nothrow
// operator new and any custom allocator's allocate function that can fail by
// throwing.
//
//----------------------------------------------------------------------------
template<class E = std::bad_alloc>
void inject_random_failure() {
if (should_inject_random_failure())
throw E();
// NOTE: We don't have to take care here to ensure that this doesn't allocate
// normal memory, because the implementation is already required to be robust
// so that "throw bad_alloc()" works in low-memory situations. Typically that
// means bad_alloc objects must live in dedicated private "emergency reserve"
// memory; otherwise, if "new int" fails an ordinary "new bad_alloc" to throw
// the exception will also immediately fail. So it's up to implementations to
// make this line work, and if they don't then that's useful data too.
}
};
thread_local this_thread_ this_thread;
}
#endif