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