summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Carey <jcarey@argv.me>2018-05-15 14:19:45 -0400
committerJason Carey <jcarey@argv.me>2018-05-16 15:09:15 -0400
commit066b3749d946e435c7b8d95f0b7cd0d71903c0bc (patch)
treece13ce0ed6d420c9dd5f8523cf438e3d727429b4
parenta2774d00b637d178ed593abd212b0f8e7ee38669 (diff)
downloadmongo-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.cpp7
-rw-r--r--src/mongo/util/fail_point.cpp7
-rw-r--r--src/mongo/util/fail_point.h62
-rw-r--r--src/mongo/util/fail_point_test.cpp33
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");
+ }
+}
}