diff options
author | Jason Carey <jcarey@argv.me> | 2018-05-15 14:19:45 -0400 |
---|---|---|
committer | Jason Carey <jcarey@argv.me> | 2018-05-16 15:09:15 -0400 |
commit | 066b3749d946e435c7b8d95f0b7cd0d71903c0bc (patch) | |
tree | ce13ce0ed6d420c9dd5f8523cf438e3d727429b4 | |
parent | a2774d00b637d178ed593abd212b0f8e7ee38669 (diff) | |
download | mongo-066b3749d946e435c7b8d95f0b7cd0d71903c0bc.tar.gz |
SERVER-34960 Add MONGO_FAIL_POINT_BLOCK_IF
It's sometimes useful to be able to check a pre-condition on a fail
point without manipulating the fail point state (decrementing nTimes for
instance).
Adding a callable to shouldFailOpenBlock and shouldFail, and threading
that through a new block macro MONGO_FAIL_POINT_BLOCK_IF, allows for
observation of the fail point payload and a chance to abort without
extra overhead if the fail point is disabled.
-rw-r--r-- | src/mongo/db/service_entry_point_common.cpp | 7 | ||||
-rw-r--r-- | src/mongo/util/fail_point.cpp | 7 | ||||
-rw-r--r-- | src/mongo/util/fail_point.h | 62 | ||||
-rw-r--r-- | src/mongo/util/fail_point_test.cpp | 33 |
4 files changed, 87 insertions, 22 deletions
diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index 52c9ebab091..90e0b686c76 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -558,10 +558,9 @@ bool runCommandImpl(OperationContext* opCtx, * Maybe uassert according to the 'failCommand' fail point. */ void evaluateFailCommandFailPoint(OperationContext* opCtx, StringData commandName) { - if (failCommandIgnoreList.find(commandName) != failCommandIgnoreList.cend()) { - return; // Early return to keep tidy fail point stats. - } - MONGO_FAIL_POINT_BLOCK(failCommand, data) { + MONGO_FAIL_POINT_BLOCK_IF(failCommand, data, [&](const BSONObj& data) { + return failCommandIgnoreList.find(commandName) == failCommandIgnoreList.cend(); + }) { bool closeConnection; if (bsonExtractBooleanField(data.getData(), "closeConnection", &closeConnection).isOK() && closeConnection) { diff --git a/src/mongo/util/fail_point.cpp b/src/mongo/util/fail_point.cpp index 75be5f48231..4ba84bee3ec 100644 --- a/src/mongo/util/fail_point.cpp +++ b/src/mongo/util/fail_point.cpp @@ -141,13 +141,18 @@ void FailPoint::disableFailPoint() { } while (expectedCurrentVal != currentVal); } -FailPoint::RetCode FailPoint::slowShouldFailOpenBlock() { +FailPoint::RetCode FailPoint::slowShouldFailOpenBlock( + stdx::function<bool(const BSONObj&)> cb) noexcept { ValType localFpInfo = _fpInfo.addAndFetch(1); if ((localFpInfo & ACTIVE_BIT) == 0) { return slowOff; } + if (cb && !cb(getData())) { + return userIgnored; + } + switch (_mode) { case alwaysOn: return slowOn; diff --git a/src/mongo/util/fail_point.h b/src/mongo/util/fail_point.h index a5fc58d21d5..8872f077dc6 100644 --- a/src/mongo/util/fail_point.h +++ b/src/mongo/util/fail_point.h @@ -32,6 +32,7 @@ #include "mongo/base/status_with.h" #include "mongo/db/jsobj.h" #include "mongo/platform/atomic_word.h" +#include "mongo/stdx/functional.h" #include "mongo/stdx/mutex.h" namespace mongo { @@ -70,7 +71,7 @@ class FailPoint { public: typedef AtomicUInt32::WordType ValType; enum Mode { off, alwaysOn, random, nTimes, skip }; - enum RetCode { fastOff = 0, slowOff, slowOn }; + enum RetCode { fastOff = 0, slowOff, slowOn, userIgnored }; /** * Explicitly resets the seed used for the PRNG in this thread. If not called on a thread, @@ -87,11 +88,13 @@ public: /** * Note: This is not side-effect free - it can change the state to OFF after calling. + * Note: see MONGO_FAIL_POINT_BLOCK_IF for information on the passed callable * * @return true if fail point is active. */ - inline bool shouldFail() { - RetCode ret = shouldFailOpenBlock(); + template <typename Callable = std::nullptr_t> + inline bool shouldFail(Callable&& cb = nullptr) { + RetCode ret = shouldFailOpenBlock(std::forward<Callable>(cb)); if (MONGO_likely(ret == fastOff)) { return false; @@ -106,14 +109,20 @@ public: * decrementing it. Must call shouldFailCloseBlock afterwards when the return value * is not fastOff. Otherwise, this will remain read-only forever. * - * @return slowOn if fail point is active. + * Note: see MONGO_FAIL_POINT_BLOCK_IF for information on the passed callable + * + * @return slowOn if its active and needs to be closed + * userIgnored if its active and needs to be closed, but shouldn't be acted on + * slowOff if its disabled and needs to be closed + * fastOff if its disabled and doesn't need to be closed */ - inline RetCode shouldFailOpenBlock() { + template <typename Callable = std::nullptr_t> + inline RetCode shouldFailOpenBlock(Callable&& cb = nullptr) { if (MONGO_likely((_fpInfo.loadRelaxed() & ACTIVE_BIT) == 0)) { return fastOff; } - return slowShouldFailOpenBlock(); + return slowShouldFailOpenBlock(std::forward<Callable>(cb)); } /** @@ -180,8 +189,11 @@ private: /** * slow path for #shouldFailOpenBlock + * + * If a callable is passed, and returns false, this will return userIgnored and avoid altering + * the mode in any way. The argument is the fail point payload. */ - RetCode slowShouldFailOpenBlock(); + RetCode slowShouldFailOpenBlock(stdx::function<bool(const BSONObj&)> cb) noexcept; /** * @return the stored BSONObj in this fail point. Note that this cannot be safely @@ -201,8 +213,12 @@ class ScopedFailPoint { MONGO_DISALLOW_COPYING(ScopedFailPoint); public: - ScopedFailPoint(FailPoint* failPoint) - : _failPoint(failPoint), _once(false), _shouldClose(false) {} + template <typename Callable = std::nullptr_t> + ScopedFailPoint(FailPoint* failPoint, Callable&& cb = nullptr) : _failPoint(failPoint) { + FailPoint::RetCode ret = _failPoint->shouldFailOpenBlock(std::forward<Callable>(cb)); + _shouldClose = ret != FailPoint::fastOff; + _shouldRun = ret == FailPoint::slowOn; + } ~ScopedFailPoint() { if (_shouldClose) { @@ -214,15 +230,14 @@ public: * @return true if fail point is on. This will be true at most once. */ inline bool isActive() { - if (_once) { + if (!_shouldRun) { return false; } - _once = true; - - FailPoint::RetCode ret = _failPoint->shouldFailOpenBlock(); - _shouldClose = ret != FailPoint::fastOff; - return ret == FailPoint::slowOn; + // We use this in a for loop to prevent iteration, thus flipping to inactive after the first + // time. + _shouldRun = false; + return true; } /** @@ -237,7 +252,7 @@ public: private: FailPoint* _failPoint; - bool _once; + bool _shouldRun; bool _shouldClose; }; @@ -256,4 +271,17 @@ private: */ #define MONGO_FAIL_POINT_BLOCK(symbol, blockSymbol) \ for (mongo::ScopedFailPoint blockSymbol(&symbol); MONGO_unlikely(blockSymbol.isActive());) -} + +/** + * Macro for creating a fail point with block context and a pre-flight condition. Also use this when + * you want to access the data stored in the fail point. + * + * Your passed in callable should take a const BSONObj& (the fail point payload) and return bool. + * If it returns true, you'll process the block as normal. If you return false, you'll exit the + * block without evaluating it and avoid altering the mode in any way (you won't consume nTimes for + * instance). + */ +#define MONGO_FAIL_POINT_BLOCK_IF(symbol, blockSymbol, ...) \ + for (mongo::ScopedFailPoint blockSymbol(&symbol, __VA_ARGS__); \ + MONGO_unlikely(blockSymbol.isActive());) +} // namespace mongo diff --git a/src/mongo/util/fail_point_test.cpp b/src/mongo/util/fail_point_test.cpp index 37bf8936f21..e2bc5f56a6e 100644 --- a/src/mongo/util/fail_point_test.cpp +++ b/src/mongo/util/fail_point_test.cpp @@ -414,4 +414,37 @@ TEST(FailPoint, FailPointBlockBasicTest) { ASSERT_FALSE(failPoint->shouldFail()); } + +TEST(FailPoint, FailPointBlockIfBasicTest) { + FailPoint failPoint; + failPoint.setMode(FailPoint::nTimes, 1, BSON("skip" << true)); + + { + bool hit = false; + + MONGO_FAIL_POINT_BLOCK_IF(failPoint, scopedFp, [&](const BSONObj& obj) { + hit = obj["skip"].trueValue(); + return false; + }) { + ASSERT(!"shouldn't get here"); + } + + ASSERT(hit); + } + + { + bool hit = false; + + MONGO_FAIL_POINT_BLOCK_IF(failPoint, scopedFp, [](auto) { return true; }) { + hit = true; + ASSERT(!scopedFp.getData().isEmpty()); + } + + ASSERT(hit); + } + + MONGO_FAIL_POINT_BLOCK_IF(failPoint, scopedFp, [](auto) { return true; }) { + ASSERT(!"shouldn't get here"); + } +} } |