1 2 /** 3 * Copyright (C) 2018-present MongoDB, Inc. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the Server Side Public License, version 1, 7 * as published by MongoDB, Inc. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * Server Side Public License for more details. 13 * 14 * You should have received a copy of the Server Side Public License 15 * along with this program. If not, see 16 * <http://www.mongodb.com/licensing/server-side-public-license>. 17 * 18 * As a special exception, the copyright holders give permission to link the 19 * code of portions of this program with the OpenSSL library under certain 20 * conditions as described in each individual source file and distribute 21 * linked combinations including the program with the OpenSSL library. You 22 * must comply with the Server Side Public License in all respects for 23 * all of the code used other than as permitted herein. If you modify file(s) 24 * with this exception, you may extend this exception to your version of the 25 * file(s), but you are not obligated to do so. If you do not wish to do so, 26 * delete this exception statement from your version. If you delete this 27 * exception statement from all source files in the program, then also delete 28 * it in the license file. 29 */ 30 31 #pragma once 32 33 #include "mongo/base/disallow_copying.h" 34 #include "mongo/base/status_with.h" 35 #include "mongo/db/jsobj.h" 36 #include "mongo/platform/atomic_word.h" 37 #include "mongo/stdx/mutex.h" 38 39 namespace mongo { 40 /** 41 * A simple thread-safe fail point implementation that can be activated and 42 * deactivated, as well as embed temporary data into it. 43 * 44 * The fail point has a static instance, which is represented by a FailPoint 45 * object, and dynamic instance, which are all the threads in between 46 * shouldFailOpenBlock and shouldFailCloseBlock. 47 * 48 * Sample use: 49 * // Declared somewhere: 50 * FailPoint makeBadThingsHappen; 51 * 52 * // Somewhere in the code 53 * return false || MONGO_FAIL_POINT(makeBadThingsHappen); 54 * 55 * or 56 * 57 * // Somewhere in the code 58 * MONGO_FAIL_POINT_BLOCK(makeBadThingsHappen, blockMakeBadThingsHappen) { 59 * const BSONObj& data = blockMakeBadThingsHappen.getData(); 60 * // Do something 61 * } 62 * 63 * Invariants: 64 * 65 * 1. Always refer to _fpInfo first to check if failPoint is active or not before 66 * entering fail point or modifying fail point. 67 * 2. Client visible fail point states are read-only when active. 68 */ 69 class FailPoint { 70 MONGO_DISALLOW_COPYING(FailPoint); 71 72 public: 73 typedef AtomicUInt32::WordType ValType; 74 enum Mode { off, alwaysOn, random, nTimes, skip }; 75 enum RetCode { fastOff = 0, slowOff, slowOn }; 76 77 /** 78 * Explicitly resets the seed used for the PRNG in this thread. If not called on a thread, 79 * an instance of SecureRandom is used to seed the PRNG. 80 */ 81 static void setThreadPRNGSeed(int32_t seed); 82 83 /** 84 * Parses the FailPoint::Mode, FailPoint::ValType, and data BSONObj from the BSON. 85 */ 86 static StatusWith<std::tuple<Mode, ValType, BSONObj>> parseBSON(const BSONObj& obj); 87 88 FailPoint(); 89 90 /** 91 * Note: This is not side-effect free - it can change the state to OFF after calling. 92 * 93 * @return true if fail point is active. 94 */ shouldFail()95 inline bool shouldFail() { 96 RetCode ret = shouldFailOpenBlock(); 97 98 if (MONGO_likely(ret == fastOff)) { 99 return false; 100 } 101 102 shouldFailCloseBlock(); 103 return ret == slowOn; 104 } 105 106 /** 107 * Checks whether fail point is active and increments the reference counter without 108 * decrementing it. Must call shouldFailCloseBlock afterwards when the return value 109 * is not fastOff. Otherwise, this will remain read-only forever. 110 * 111 * @return slowOn if fail point is active. 112 */ shouldFailOpenBlock()113 inline RetCode shouldFailOpenBlock() { 114 if (MONGO_likely((_fpInfo.loadRelaxed() & ACTIVE_BIT) == 0)) { 115 return fastOff; 116 } 117 118 return slowShouldFailOpenBlock(); 119 } 120 121 /** 122 * Decrements the reference counter. 123 * @see #shouldFailOpenBlock 124 */ 125 void shouldFailCloseBlock(); 126 127 /** 128 * Changes the settings of this fail point. This will turn off the fail point 129 * and waits for all dynamic instances referencing this fail point to go away before 130 * actually modifying the settings. 131 * 132 * @param mode the new mode for this fail point. 133 * @param val the value that can have different usage depending on the mode: 134 * 135 * - off, alwaysOn: ignored 136 * - random: static_cast<int32_t>(std::numeric_limits<int32_t>::max() * p), where 137 * where p is the probability that any given evaluation of the failpoint should 138 * activate. 139 * - nTimes: the number of times this fail point will be active when 140 * #shouldFail or #shouldFailOpenBlock is called. 141 * - skip: the number of times this failpoint will be inactive when 142 * #shouldFail or #shouldFailOpenBlock is called. After this number is reached, the 143 * failpoint will always be active. 144 * 145 * @param extra arbitrary BSON object that can be stored to this fail point 146 * that can be referenced afterwards with #getData. Defaults to an empty 147 * document. 148 */ 149 void setMode(Mode mode, ValType val = 0, const BSONObj& extra = BSONObj()); 150 151 /** 152 * @returns a BSON object showing the current mode and data stored. 153 */ 154 BSONObj toBSON() const; 155 156 private: 157 static const ValType ACTIVE_BIT = 1 << 31; 158 static const ValType REF_COUNTER_MASK = ~ACTIVE_BIT; 159 160 // Bit layout: 161 // 31: tells whether this fail point is active. 162 // 0~30: unsigned ref counter for active dynamic instances. 163 AtomicUInt32 _fpInfo{0}; 164 165 // Invariant: These should be read only if ACTIVE_BIT of _fpInfo is set. 166 Mode _mode{off}; 167 AtomicInt32 _timesOrPeriod{0}; 168 BSONObj _data; 169 170 // protects _mode, _timesOrPeriod, _data 171 mutable stdx::mutex _modMutex; 172 173 /** 174 * Enables this fail point. 175 */ 176 void enableFailPoint(); 177 178 /** 179 * Disables this fail point. 180 */ 181 void disableFailPoint(); 182 183 /** 184 * slow path for #shouldFailOpenBlock 185 */ 186 RetCode slowShouldFailOpenBlock(); 187 188 /** 189 * @return the stored BSONObj in this fail point. Note that this cannot be safely 190 * read if this fail point is off. 191 */ 192 const BSONObj& getData() const; 193 194 friend class ScopedFailPoint; 195 }; 196 197 /** 198 * Helper class for making sure that FailPoint#shouldFailCloseBlock is called when 199 * FailPoint#shouldFailOpenBlock was called. This should only be used within the 200 * MONGO_FAIL_POINT_BLOCK macro. 201 */ 202 class ScopedFailPoint { 203 MONGO_DISALLOW_COPYING(ScopedFailPoint); 204 205 public: ScopedFailPoint(FailPoint * failPoint)206 ScopedFailPoint(FailPoint* failPoint) 207 : _failPoint(failPoint), _once(false), _shouldClose(false) {} 208 ~ScopedFailPoint()209 ~ScopedFailPoint() { 210 if (_shouldClose) { 211 _failPoint->shouldFailCloseBlock(); 212 } 213 } 214 215 /** 216 * @return true if fail point is on. This will be true at most once. 217 */ isActive()218 inline bool isActive() { 219 if (_once) { 220 return false; 221 } 222 223 _once = true; 224 225 FailPoint::RetCode ret = _failPoint->shouldFailOpenBlock(); 226 _shouldClose = ret != FailPoint::fastOff; 227 return ret == FailPoint::slowOn; 228 } 229 230 /** 231 * @return the data stored in the fail point. #isActive must be true 232 * before you can call this. 233 */ getData()234 const BSONObj& getData() const { 235 // Assert when attempting to get data without incrementing ref counter. 236 fassert(16445, _shouldClose); 237 return _failPoint->getData(); 238 } 239 240 private: 241 FailPoint* _failPoint; 242 bool _once; 243 bool _shouldClose; 244 }; 245 246 #define MONGO_FAIL_POINT(symbol) MONGO_unlikely(symbol.shouldFail()) 247 248 #define MONGO_FAIL_POINT_PAUSE_WHILE_SET(symbol) \ 249 do { \ 250 while (MONGO_FAIL_POINT(symbol)) { \ 251 sleepmillis(100); \ 252 } \ 253 } while (false) 254 255 /** 256 * Macro for creating a fail point with block context. Also use this when 257 * you want to access the data stored in the fail point. 258 */ 259 #define MONGO_FAIL_POINT_BLOCK(symbol, blockSymbol) \ 260 for (mongo::ScopedFailPoint blockSymbol(&symbol); MONGO_unlikely(blockSymbol.isActive());) 261 } 262