diff options
-rw-r--r-- | jstests/fail_point/fail_point.js | 143 | ||||
-rw-r--r-- | src/mongo/util/fail_point.cpp | 37 | ||||
-rw-r--r-- | src/mongo/util/fail_point.h | 11 |
3 files changed, 114 insertions, 77 deletions
diff --git a/jstests/fail_point/fail_point.js b/jstests/fail_point/fail_point.js index b8c877cb9b2..a16971857b6 100644 --- a/jstests/fail_point/fail_point.js +++ b/jstests/fail_point/fail_point.js @@ -1,78 +1,91 @@ -/** - * Performs basic checks on the configureFailPoint command. Also check - * mongo/util/fail_point_test.cpp for unit tests. - * - * @param adminDB {DB} the admin database database object - */ -var runTest = function(adminDB) { - var expectFailPointState = function(fpState, expectedMode, expectedData) { - assert.eq(expectedMode, fpState.mode); +(function() { + 'use strict'; - // Check that all expected data is present. - for (var field in expectedData) { // Valid only for 1 level field checks - assert.eq(expectedData[field], fpState.data[field]); - } + /** + * Performs basic checks on the configureFailPoint command. Also check + * mongo/util/fail_point_test.cpp for unit tests. + * + * @param adminDB {DB} the admin database database object + */ + function runTest(adminDB) { + function expectFailPointState(fpState, expectedMode, expectedData) { + assert.eq(expectedMode, fpState.mode); + + // Check that all expected data is present. + for (var field in expectedData) { // Valid only for 1 level field checks + assert.eq(expectedData[field], fpState.data[field]); + } - // Check that all present data is expected. - for (field in fpState.data) { - assert.eq(expectedData[field], fpState.data[field]); + // Check that all present data is expected. + for (field in fpState.data) { + assert.eq(expectedData[field], fpState.data[field]); + } } - }; - // A failpoint's state can be read through getParameter by prefixing its name with "failpoint." + var res; + + // A failpoint's state can be read through getParameter by prefixing its name with + // "failpoint" + + // Test non-existing fail point + assert.commandFailed( + adminDB.runCommand({configureFailPoint: 'fpNotExist', mode: 'alwaysOn', data: {x: 1}})); - // Test non-existing fail point - assert.commandFailed( - adminDB.runCommand({configureFailPoint: 'fpNotExist', mode: 'alwaysOn', data: {x: 1}})); + // Test bad mode string + assert.commandFailed( + adminDB.runCommand({configureFailPoint: 'dummy', mode: 'badMode', data: {x: 1}})); + res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); + assert.commandWorked(res); + expectFailPointState(res["failpoint.dummy"], 0, {}); - // Test bad mode string - assert.commandFailed( - adminDB.runCommand({configureFailPoint: 'dummy', mode: 'badMode', data: {x: 1}})); - var res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); - assert.commandWorked(res); - expectFailPointState(res["failpoint.dummy"], 0, {}); + // Test bad mode obj + assert.commandFailed( + adminDB.runCommand({configureFailPoint: 'dummy', mode: {foo: 3}, data: {x: 1}})); + res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); + assert.commandWorked(res); + expectFailPointState(res["failpoint.dummy"], 0, {}); - // Test bad mode obj - assert.commandFailed( - adminDB.runCommand({configureFailPoint: 'dummy', mode: {foo: 3}, data: {x: 1}})); - var res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); - assert.commandWorked(res); - expectFailPointState(res["failpoint.dummy"], 0, {}); + // Test bad mode type + assert.commandFailed( + adminDB.runCommand({configureFailPoint: 'dummy', mode: true, data: {x: 1}})); + res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); + assert.commandWorked(res); + expectFailPointState(res["failpoint.dummy"], 0, {}); - // Test bad mode type - assert.commandFailed( - adminDB.runCommand({configureFailPoint: 'dummy', mode: true, data: {x: 1}})); - var res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); - assert.commandWorked(res); - expectFailPointState(res["failpoint.dummy"], 0, {}); + // Test bad data type + assert.commandFailed( + adminDB.runCommand({configureFailPoint: 'dummy', mode: 'alwaysOn', data: 'data'})); + res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); + assert.commandWorked(res); + expectFailPointState(res["failpoint.dummy"], 0, {}); - // Test bad data type - assert.commandFailed( - adminDB.runCommand({configureFailPoint: 'dummy', mode: 'alwaysOn', data: 'data'})); - var res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); - assert.commandWorked(res); - expectFailPointState(res["failpoint.dummy"], 0, {}); + // Test setting mode to off. + assert.commandWorked(adminDB.runCommand({configureFailPoint: 'dummy', mode: 'off'})); + res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); + assert.commandWorked(res); + expectFailPointState(res["failpoint.dummy"], 0, {}); - // Test setting mode to off. - assert.commandWorked(adminDB.runCommand({configureFailPoint: 'dummy', mode: 'off'})); - var res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); - assert.commandWorked(res); - expectFailPointState(res["failpoint.dummy"], 0, {}); + // Test setting mode to skip. + assert.commandWorked(adminDB.runCommand({configureFailPoint: 'dummy', mode: {skip: 2}})); + res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); + assert.commandWorked(res); + expectFailPointState(res["failpoint.dummy"], 4, {}); - // Test good command w/ data - assert.commandWorked( - adminDB.runCommand({configureFailPoint: 'dummy', mode: 'alwaysOn', data: {x: 1}})); - var res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); - assert.commandWorked(res); - expectFailPointState(res["failpoint.dummy"], 1, {x: 1}); -}; + // Test good command w/ data + assert.commandWorked( + adminDB.runCommand({configureFailPoint: 'dummy', mode: 'alwaysOn', data: {x: 1}})); + res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1}); + assert.commandWorked(res); + expectFailPointState(res["failpoint.dummy"], 1, {x: 1}); + } -var conn = MongoRunner.runMongod(); -runTest(conn.getDB('admin')); -MongoRunner.stopMongod(conn); + var conn = MongoRunner.runMongod(); + runTest(conn.getDB('admin')); + MongoRunner.stopMongod(conn); -/////////////////////////////////////////////////////////// -// Test mongos -var st = new ShardingTest({shards: 1}); -runTest(st.s.getDB('admin')); -st.stop(); + /////////////////////////////////////////////////////////// + // Test mongos + var st = new ShardingTest({shards: 1}); + runTest(st.s.getDB('admin')); + st.stop(); +})(); diff --git a/src/mongo/util/fail_point.cpp b/src/mongo/util/fail_point.cpp index 8f85ba18f17..75be5f48231 100644 --- a/src/mongo/util/fail_point.cpp +++ b/src/mongo/util/fail_point.cpp @@ -76,7 +76,7 @@ void FailPoint::setThreadPRNGSeed(int32_t seed) { FailPointPRNG::current()->resetSeed(seed); } -FailPoint::FailPoint() : _fpInfo(0), _mode(off), _timesOrPeriod(0) {} +FailPoint::FailPoint() = default; void FailPoint::shouldFailCloseBlock() { _fpInfo.subtractAndFetch(1); @@ -151,24 +151,28 @@ FailPoint::RetCode FailPoint::slowShouldFailOpenBlock() { switch (_mode) { case alwaysOn: return slowOn; - case random: { const AtomicInt32::WordType maxActivationValue = _timesOrPeriod.load(); - if (FailPointPRNG::current()->nextPositiveInt32() < maxActivationValue) { + if (FailPointPRNG::current()->nextPositiveInt32() < maxActivationValue) return slowOn; - } + return slowOff; } case nTimes: { - AtomicInt32::WordType newVal = _timesOrPeriod.subtractAndFetch(1); - - if (newVal <= 0) { + if (_timesOrPeriod.subtractAndFetch(1) <= 0) disableFailPoint(); - } return slowOn; } + case skip: { + // Ensure that once the skip counter reaches within some delta from 0 we don't continue + // decrementing it unboundedly because at some point it will roll over and become + // positive again + if (_timesOrPeriod.load() <= 0 || _timesOrPeriod.subtractAndFetch(1) < 0) + return slowOn; + return slowOff; + } default: error() << "FailPoint Mode not supported: " << static_cast<int>(_mode); fassertFailed(16444); @@ -212,6 +216,23 @@ StatusWith<std::tuple<FailPoint::Mode, FailPoint::ValType, BSONObj>> FailPoint:: return {ErrorCodes::BadValue, "'times' option to 'mode' is too large"}; } val = static_cast<int>(longVal); + } else if (modeObj.hasField("skip")) { + mode = FailPoint::skip; + + long long longVal; + auto status = bsonExtractIntegerField(modeObj, "skip", &longVal); + if (!status.isOK()) { + return status; + } + + if (longVal < 0) { + return {ErrorCodes::BadValue, "'skip' option to 'mode' must be positive"}; + } + + if (longVal > std::numeric_limits<int>::max()) { + return {ErrorCodes::BadValue, "'skip' option to 'mode' is too large"}; + } + val = static_cast<int>(longVal); } else if (modeObj.hasField("activationProbability")) { mode = FailPoint::random; diff --git a/src/mongo/util/fail_point.h b/src/mongo/util/fail_point.h index 77bb9ab882a..a5fc58d21d5 100644 --- a/src/mongo/util/fail_point.h +++ b/src/mongo/util/fail_point.h @@ -69,7 +69,7 @@ class FailPoint { public: typedef AtomicUInt32::WordType ValType; - enum Mode { off, alwaysOn, random, nTimes }; + enum Mode { off, alwaysOn, random, nTimes, skip }; enum RetCode { fastOff = 0, slowOff, slowOn }; /** @@ -136,6 +136,9 @@ public: * activate. * - nTimes: the number of times this fail point will be active when * #shouldFail or #shouldFailOpenBlock is called. + * - skip: the number of times this failpoint will be inactive when + * #shouldFail or #shouldFailOpenBlock is called. After this number is reached, the + * failpoint will always be active. * * @param extra arbitrary BSON object that can be stored to this fail point * that can be referenced afterwards with #getData. Defaults to an empty @@ -155,11 +158,11 @@ private: // Bit layout: // 31: tells whether this fail point is active. // 0~30: unsigned ref counter for active dynamic instances. - AtomicUInt32 _fpInfo; + AtomicUInt32 _fpInfo{0}; // Invariant: These should be read only if ACTIVE_BIT of _fpInfo is set. - Mode _mode; - AtomicInt32 _timesOrPeriod; + Mode _mode{off}; + AtomicInt32 _timesOrPeriod{0}; BSONObj _data; // protects _mode, _timesOrPeriod, _data |