mirror of https://github.com/mongodb/mongo
SERVER-39165 Add waitForFailpoint command and update non-repl tests
This commit is contained in:
parent
232a30488c
commit
f79498e9aa
|
|
@ -22,7 +22,7 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
load("jstests/libs/check_log.js");
|
||||
load("jstests/libs/fail_point_util.js");
|
||||
|
||||
const forceCheckpoint = () => {
|
||||
assert.commandWorked(db.fsyncLock());
|
||||
|
|
@ -67,8 +67,7 @@ assert(res.valid, "Validate cmd with {background:true} failed: " + tojson(res));
|
|||
|
||||
// Set a failpoint in the background validation code to pause validation while holding a collection
|
||||
// lock.
|
||||
assert.commandWorked(
|
||||
db.adminCommand({configureFailPoint: "pauseCollectionValidationWithLock", mode: "alwaysOn"}));
|
||||
let failPoint = configureFailPoint(db, "pauseCollectionValidationWithLock");
|
||||
|
||||
// Start an asynchronous thread to run collection validation with {background:true}.
|
||||
let awaitValidateCommand = startParallelShell(function() {
|
||||
|
|
@ -82,7 +81,7 @@ let awaitValidateCommand = startParallelShell(function() {
|
|||
});
|
||||
|
||||
// Wait for background validation command to start.
|
||||
checkLog.contains(db.getMongo(), "Failpoint 'pauseCollectionValidationWithLock' activated.");
|
||||
failPoint.wait();
|
||||
|
||||
jsTest.log("Should start hanging now......");
|
||||
|
||||
|
|
@ -96,8 +95,7 @@ assert.eq(1,
|
|||
"expected to find a single document, found: " + tojson(docRes.toArray()));
|
||||
|
||||
// Clear the failpoint and make sure the validate command was successful.
|
||||
assert.commandWorked(
|
||||
db.adminCommand({configureFailPoint: "pauseCollectionValidationWithLock", mode: "off"}));
|
||||
failPoint.off();
|
||||
awaitValidateCommand();
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
(function() {
|
||||
"use strict";
|
||||
|
||||
load('jstests/libs/parallelTester.js');
|
||||
load("jstests/libs/check_log.js");
|
||||
load("jstests/libs/fail_point_util.js");
|
||||
load('jstests/libs/parallelTester.js');
|
||||
|
||||
const dbName = "test";
|
||||
const collName = "kill_op_on_txn_expiry";
|
||||
|
|
@ -46,10 +47,8 @@ try {
|
|||
}));
|
||||
|
||||
jsTestLog("Enabling fail point to block batch inserts");
|
||||
assert.commandWorked(
|
||||
testDB.adminCommand({configureFailPoint: "hangDuringBatchInsert", mode: "alwaysOn"}));
|
||||
// Clear ramlog so checkLog can't find log messages from previous times this fail point was
|
||||
// enabled.
|
||||
let failPoint = configureFailPoint(testDB, "hangDuringBatchInsert");
|
||||
// Clear ramlog so checkLog can't find log messages from the previous times this test was run.
|
||||
assert.commandWorked(testDB.adminCommand({clearLog: 'global'}));
|
||||
|
||||
jsTestLog("Starting insert operation in parallel thread");
|
||||
|
|
@ -70,15 +69,14 @@ try {
|
|||
workerThread.start();
|
||||
|
||||
jsTestLog("Wait for insert to be blocked");
|
||||
checkLog.contains(db.getMongo(), "hangDuringBatchInsert fail point enabled");
|
||||
failPoint.wait();
|
||||
|
||||
jsTestLog("Wait for the transaction to expire");
|
||||
checkLog.contains(db.getMongo(), "Aborting transaction with txnNumber " + txnNumber);
|
||||
|
||||
jsTestLog("Disabling fail point to enable insert to proceed and detect that the session " +
|
||||
"has been killed");
|
||||
assert.commandWorked(
|
||||
testDB.adminCommand({configureFailPoint: "hangDuringBatchInsert", mode: "off"}));
|
||||
failPoint.off();
|
||||
|
||||
workerThread.join();
|
||||
assert(!workerThread.hasFailed());
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
load("jstests/libs/check_log.js");
|
||||
|
||||
load('jstests/core/txns/libs/prepare_helpers.js');
|
||||
load("jstests/libs/fail_point_util.js");
|
||||
load('jstests/libs/parallel_shell_helpers.js');
|
||||
|
||||
TestData.dbName = 'test';
|
||||
|
|
@ -36,11 +37,12 @@ TestData.otherDocFilter = {
|
|||
* field. This function is run in a separate thread and tests that oplog visibility blocks
|
||||
* certain reads and that prepare conflicts block other types of reads.
|
||||
*/
|
||||
const readThreadFunc = function(readFunc, _collName) {
|
||||
const readThreadFunc = function(readFunc, _collName, timesEntered) {
|
||||
load("jstests/libs/check_log.js");
|
||||
|
||||
// Do not start reads until we are blocked in 'prepareTransaction'.
|
||||
checkLog.contains(db.getMongo(), "hangAfterReservingPrepareTimestamp fail point enabled");
|
||||
assert.commandWorked(db.adminCommand(
|
||||
{waitForFailPoint: "hangAfterReservingPrepareTimestamp", timesEntered: timesEntered}));
|
||||
|
||||
// Create a 'readFuncObj' from the 'readFunc'.
|
||||
const readFuncObj = readFunc(_collName);
|
||||
|
|
@ -65,8 +67,7 @@ function runTest(prefix, readFunc) {
|
|||
testColl.drop({writeConcern: {w: "majority"}});
|
||||
assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: 'majority'}}));
|
||||
|
||||
assert.commandWorked(testDB.adminCommand(
|
||||
{configureFailPoint: 'hangAfterReservingPrepareTimestamp', mode: 'alwaysOn'}));
|
||||
let failPoint = configureFailPoint(testDB, "hangAfterReservingPrepareTimestamp");
|
||||
|
||||
// Insert a document for the transaction.
|
||||
assert.commandWorked(testColl.insert(TestData.txnDoc));
|
||||
|
|
@ -89,7 +90,8 @@ function runTest(prefix, readFunc) {
|
|||
// Clear the log history to ensure we only see the most recent 'prepareTransaction'
|
||||
// failpoint log message.
|
||||
assert.commandWorked(db.adminCommand({clearLog: 'global'}));
|
||||
const joinReadThread = startParallelShell(funWithArgs(readThreadFunc, readFunc, collName));
|
||||
const joinReadThread = startParallelShell(
|
||||
funWithArgs(readThreadFunc, readFunc, collName, failPoint.timesEntered + 1));
|
||||
|
||||
jsTestLog("Preparing the transaction for " + prefix);
|
||||
const prepareTimestamp = PrepareHelpers.prepareTransaction(session);
|
||||
|
|
|
|||
|
|
@ -532,6 +532,7 @@ let viewsCommandTests = {
|
|||
voteCommitIndexBuild: {skip: isUnrelated},
|
||||
voteCommitTransaction: {skip: isUnrelated},
|
||||
voteAbortTransaction: {skip: isUnrelated},
|
||||
waitForFailPoint: {skip: isUnrelated},
|
||||
whatsmyuri: {skip: isUnrelated},
|
||||
whatsmysni: {skip: isUnrelated}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -79,6 +79,16 @@ function runTest(adminDB) {
|
|||
res = adminDB.runCommand({getParameter: 1, "failpoint.dummy": 1});
|
||||
assert.commandWorked(res);
|
||||
expectFailPointState(res["failpoint.dummy"], 1, {x: 1});
|
||||
|
||||
// Test that the timeout for waitForFailPoint can be set via maxTimeMS.
|
||||
var configureFailPointRes = adminDB.runCommand({configureFailPoint: 'dummy', mode: 'alwaysOn'});
|
||||
assert.commandWorked(configureFailPointRes);
|
||||
assert.commandFailedWithCode(adminDB.adminCommand({
|
||||
waitForFailPoint: "dummy",
|
||||
timesEntered: configureFailPointRes.count + 1,
|
||||
maxTimeMS: 10
|
||||
}),
|
||||
ErrorCodes.MaxTimeMSExpired);
|
||||
}
|
||||
|
||||
var conn = MongoRunner.runMongod();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Utilities for turning on/off and waiting for fail points.
|
||||
*/
|
||||
|
||||
var configureFailPoint;
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
if (configureFailPoint) {
|
||||
return; // Protect against this file being double-loaded.
|
||||
}
|
||||
|
||||
configureFailPoint = function(conn, failPointName, data = {}, failPointMode = "alwaysOn") {
|
||||
return {
|
||||
conn: conn,
|
||||
failPointName: failPointName,
|
||||
timesEntered: assert
|
||||
.commandWorked(conn.adminCommand(
|
||||
{configureFailPoint: failPointName, mode: failPointMode, data: data}))
|
||||
.count,
|
||||
wait:
|
||||
function(additionalTimes = 1, maxTimeMS = 5 * 60 * 1000) {
|
||||
// Can only be called once because this function does not keep track of the
|
||||
// number of times the fail point is entered between the time it returns
|
||||
// and the next time it gets called.
|
||||
assert.commandWorked(conn.adminCommand({
|
||||
waitForFailPoint: failPointName,
|
||||
timesEntered: this.timesEntered + additionalTimes,
|
||||
maxTimeMS: maxTimeMS
|
||||
}));
|
||||
},
|
||||
off:
|
||||
function() {
|
||||
assert.commandWorked(
|
||||
conn.adminCommand({configureFailPoint: failPointName, mode: "off"}));
|
||||
}
|
||||
};
|
||||
};
|
||||
})();
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
(function() {
|
||||
"use strict";
|
||||
load("jstests/libs/check_log.js");
|
||||
|
||||
load("jstests/libs/fail_point_util.js");
|
||||
|
||||
const conn = MongoRunner.runMongod({});
|
||||
const db = conn.getDB("test");
|
||||
|
|
@ -14,19 +15,17 @@ const db = conn.getDB("test");
|
|||
assert.commandWorked(db.runCommand({insert: "a", documents: [{x: 1}]}));
|
||||
assert.commandWorked(db.createView("view", "a", []));
|
||||
|
||||
assert.commandWorked(
|
||||
db.adminCommand({configureFailPoint: "hangDuringDropCollection", mode: "alwaysOn"}));
|
||||
const failPoint = configureFailPoint(db, "hangDuringDropCollection");
|
||||
|
||||
// This only holds a database IX lock.
|
||||
const awaitDrop =
|
||||
startParallelShell(() => assert(db.getSiblingDB("test")["view"].drop()), conn.port);
|
||||
checkLog.contains(conn, "hangDuringDropCollection fail point enabled");
|
||||
failPoint.wait();
|
||||
|
||||
// This takes a database IX lock and should not be blocked.
|
||||
assert.commandWorked(db.runCommand({insert: "a", documents: [{y: 1}]}));
|
||||
|
||||
assert.commandWorked(
|
||||
db.adminCommand({configureFailPoint: "hangDuringDropCollection", mode: "off"}));
|
||||
failPoint.off();
|
||||
|
||||
awaitDrop();
|
||||
MongoRunner.stopMongod(conn);
|
||||
|
|
|
|||
|
|
@ -710,6 +710,7 @@ let testCases = {
|
|||
},
|
||||
}
|
||||
},
|
||||
waitForFailPoint: {skip: "executes locally on mongos (not sent to any remote node)"},
|
||||
whatsmyuri: {skip: "executes locally on mongos (not sent to any remote node)"},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -303,6 +303,7 @@ let testCases = {
|
|||
updateZoneKeyRange: {skip: "primary only"},
|
||||
usersInfo: {skip: "primary only"},
|
||||
validate: {skip: "does not return user data"},
|
||||
waitForFailPoint: {skip: "does not return user data"},
|
||||
waitForOngoingChunkSplits: {skip: "does not return user data"},
|
||||
whatsmyuri: {skip: "does not return user data"}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -338,6 +338,7 @@ let testCases = {
|
|||
updateZoneKeyRange: {skip: "primary only"},
|
||||
usersInfo: {skip: "primary only"},
|
||||
validate: {skip: "does not return user data"},
|
||||
waitForFailPoint: {skip: "does not return user data"},
|
||||
waitForOngoingChunkSplits: {skip: "does not return user data"},
|
||||
whatsmyuri: {skip: "does not return user data"}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -309,6 +309,7 @@ let testCases = {
|
|||
usersInfo: {skip: "primary only"},
|
||||
validate: {skip: "does not return user data"},
|
||||
waitForOngoingChunkSplits: {skip: "does not return user data"},
|
||||
waitForFailPoint: {skip: "does not return user data"},
|
||||
whatsmyuri: {skip: "does not return user data"}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -336,6 +336,7 @@ let testCases = {
|
|||
return {validate: collName};
|
||||
},
|
||||
},
|
||||
waitForFailPoint: {skip: "executes locally on mongos (not sent to any remote node)"},
|
||||
whatsmyuri: {skip: "executes locally on mongos (not sent to any remote node)"},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
#include "mongo/db/auth/privilege.h"
|
||||
#include "mongo/db/commands.h"
|
||||
#include "mongo/db/commands/test_commands_enabled.h"
|
||||
#include "mongo/s/request_types/wait_for_fail_point_gen.h"
|
||||
#include "mongo/util/fail_point.h"
|
||||
#include "mongo/util/log.h"
|
||||
|
||||
|
|
@ -102,10 +103,62 @@ public:
|
|||
const BSONObj& cmdObj,
|
||||
BSONObjBuilder& result) override {
|
||||
const std::string failPointName(cmdObj.firstElement().str());
|
||||
setGlobalFailPoint(failPointName, cmdObj);
|
||||
|
||||
const auto timesEntered = setGlobalFailPoint(failPointName, cmdObj);
|
||||
result.appendIntOrLL("count", timesEntered);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Command for waiting for installed fail points.
|
||||
*/
|
||||
class WaitForFailPointCommand : public TypedCommand<WaitForFailPointCommand> {
|
||||
public:
|
||||
using Request = WaitForFailPoint;
|
||||
class Invocation final : public InvocationBase {
|
||||
public:
|
||||
using InvocationBase::InvocationBase;
|
||||
|
||||
void typedRun(OperationContext* opCtx) {
|
||||
const std::string failPointName = request().getCommandParameter().toString();
|
||||
FailPoint* failPoint = globalFailPointRegistry().find(failPointName);
|
||||
if (failPoint == nullptr)
|
||||
uasserted(ErrorCodes::FailPointSetFailed, failPointName + " not found");
|
||||
failPoint->waitForTimesEntered(opCtx, request().getTimesEntered());
|
||||
}
|
||||
|
||||
private:
|
||||
bool supportsWriteConcern() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The command parameter happens to be string so it's historically been interpreted
|
||||
// by parseNs as a collection. Continuing to do so here for unexamined compatibility.
|
||||
NamespaceString ns() const override {
|
||||
return NamespaceString(request().getDbName(), "");
|
||||
}
|
||||
|
||||
// No auth needed because it only works when enabled via command line.
|
||||
void doCheckAuthorization(OperationContext* opCtx) const override {}
|
||||
};
|
||||
|
||||
std::string help() const override {
|
||||
return "wait for a fail point to be entered a certain number of times";
|
||||
}
|
||||
|
||||
AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
|
||||
return AllowedOnSecondary::kAlways;
|
||||
}
|
||||
|
||||
bool adminOnly() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool requiresAuth() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
} WaitForFailPointCmd;
|
||||
|
||||
MONGO_REGISTER_TEST_COMMAND(FaultInjectCmd);
|
||||
} // namespace mongo
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ TEST_F(MongodbCAPITest, CreateIndex) {
|
|||
mongo::BSONObj inputObj = mongo::fromjson(
|
||||
R"raw_delimiter({
|
||||
createIndexes: 'items',
|
||||
indexes:
|
||||
indexes:
|
||||
[
|
||||
{
|
||||
key: {
|
||||
|
|
@ -266,7 +266,7 @@ TEST_F(MongodbCAPITest, CreateBackgroundIndex) {
|
|||
mongo::BSONObj inputObj = mongo::fromjson(
|
||||
R"raw_delimiter({
|
||||
createIndexes: 'items',
|
||||
indexes:
|
||||
indexes:
|
||||
[
|
||||
{
|
||||
key: {
|
||||
|
|
@ -292,7 +292,7 @@ TEST_F(MongodbCAPITest, CreateTTLIndex) {
|
|||
mongo::BSONObj inputObj = mongo::fromjson(
|
||||
R"raw_delimiter({
|
||||
createIndexes: 'items',
|
||||
indexes:
|
||||
indexes:
|
||||
[
|
||||
{
|
||||
key: {
|
||||
|
|
@ -621,6 +621,7 @@ TEST_F(MongodbCAPITest, RunListCommands) {
|
|||
"trimMemory",
|
||||
"update",
|
||||
"validate",
|
||||
"waitForFailPoint",
|
||||
"whatsmysni"};
|
||||
|
||||
std::sort(whitelist.begin(), whitelist.end());
|
||||
|
|
|
|||
|
|
@ -188,7 +188,8 @@ env.Library(
|
|||
env.Idlc('request_types/shard_collection.idl')[0],
|
||||
env.Idlc('request_types/clone_collection_options_from_primary_shard.idl')[0],
|
||||
env.Idlc('request_types/refine_collection_shard_key.idl')[0],
|
||||
env.Idlc('request_types/rename_collection.idl')[0]
|
||||
env.Idlc('request_types/rename_collection.idl')[0],
|
||||
env.Idlc('request_types/wait_for_fail_point.idl')[0],
|
||||
],
|
||||
LIBDEPS=[
|
||||
'$BUILD_DIR/mongo/client/connection_string',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
# Copyright (C) 2019-present MongoDB, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the Server Side Public License, version 1,
|
||||
# as published by MongoDB, Inc.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# Server Side Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the Server Side Public License
|
||||
# along with this program. If not, see
|
||||
# <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
#
|
||||
# As a special exception, the copyright holders give permission to link the
|
||||
# code of portions of this program with the OpenSSL library under certain
|
||||
# conditions as described in each individual source file and distribute
|
||||
# linked combinations including the program with the OpenSSL library. You
|
||||
# must comply with the Server Side Public License in all respects for
|
||||
# all of the code used other than as permitted herein. If you modify file(s)
|
||||
# with this exception, you may extend this exception to your version of the
|
||||
# file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
# delete this exception statement from your version. If you delete this
|
||||
# exception statement from all source files in the program, then also delete
|
||||
# it in the license file.
|
||||
#
|
||||
|
||||
# waitForFailPoint IDL file
|
||||
|
||||
global:
|
||||
cpp_namespace: "mongo"
|
||||
|
||||
imports:
|
||||
- "mongo/idl/basic_types.idl"
|
||||
|
||||
commands:
|
||||
waitForFailPoint:
|
||||
description: "wait for a fail point to be entered a certain number of times"
|
||||
strict: false
|
||||
namespace: type
|
||||
type: string
|
||||
fields:
|
||||
timesEntered:
|
||||
type: safeInt64
|
||||
description: "The number of times the fail point has been entered."
|
||||
optional: false
|
||||
|
|
@ -76,7 +76,7 @@ void FailPoint::_shouldFailCloseBlock() {
|
|||
_fpInfo.subtractAndFetch(1);
|
||||
}
|
||||
|
||||
void FailPoint::setMode(Mode mode, ValType val, BSONObj extra) {
|
||||
int64_t FailPoint::setMode(Mode mode, ValType val, BSONObj extra) {
|
||||
/**
|
||||
* Outline:
|
||||
*
|
||||
|
|
@ -103,6 +103,20 @@ void FailPoint::setMode(Mode mode, ValType val, BSONObj extra) {
|
|||
if (_mode != off) {
|
||||
_enable();
|
||||
}
|
||||
|
||||
return _timesEntered.load();
|
||||
}
|
||||
|
||||
void FailPoint::waitForTimesEntered(int64_t timesEntered) {
|
||||
while (_timesEntered.load() < timesEntered) {
|
||||
sleepmillis(100);
|
||||
};
|
||||
}
|
||||
|
||||
void FailPoint::waitForTimesEntered(OperationContext* opCtx, int64_t timesEntered) {
|
||||
while (_timesEntered.load() < timesEntered) {
|
||||
opCtx->sleepFor(Milliseconds(100));
|
||||
}
|
||||
}
|
||||
|
||||
const BSONObj& FailPoint::_getData() const {
|
||||
|
|
@ -117,7 +131,7 @@ void FailPoint::_disable() {
|
|||
_fpInfo.fetchAndBitAnd(~kActiveBit);
|
||||
}
|
||||
|
||||
FailPoint::RetCode FailPoint::_slowShouldFailOpenBlock(
|
||||
FailPoint::RetCode FailPoint::_slowShouldFailOpenBlockImpl(
|
||||
std::function<bool(const BSONObj&)> cb) noexcept {
|
||||
ValType localFpInfo = _fpInfo.addAndFetch(1);
|
||||
|
||||
|
|
@ -142,15 +156,15 @@ FailPoint::RetCode FailPoint::_slowShouldFailOpenBlock(
|
|||
case nTimes: {
|
||||
if (_timesOrPeriod.subtractAndFetch(1) <= 0)
|
||||
_disable();
|
||||
|
||||
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)
|
||||
if (_timesOrPeriod.load() <= 0 || _timesOrPeriod.subtractAndFetch(1) < 0) {
|
||||
return slowOn;
|
||||
}
|
||||
|
||||
return slowOff;
|
||||
}
|
||||
|
|
@ -160,6 +174,15 @@ FailPoint::RetCode FailPoint::_slowShouldFailOpenBlock(
|
|||
}
|
||||
}
|
||||
|
||||
FailPoint::RetCode FailPoint::_slowShouldFailOpenBlock(
|
||||
std::function<bool(const BSONObj&)> cb) noexcept {
|
||||
auto ret = _slowShouldFailOpenBlockImpl(cb);
|
||||
if (ret == slowOn) {
|
||||
_timesEntered.addAndFetch(1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
StatusWith<FailPoint::ModeOptions> FailPoint::parseBSON(const BSONObj& obj) {
|
||||
Mode mode = FailPoint::alwaysOn;
|
||||
ValType val = 0;
|
||||
|
|
@ -254,6 +277,7 @@ BSONObj FailPoint::toBSON() const {
|
|||
stdx::lock_guard<Latch> scoped(_modMutex);
|
||||
builder.append("mode", _mode);
|
||||
builder.append("data", _data);
|
||||
builder.append("timesEntered", _timesEntered.load());
|
||||
|
||||
return builder.obj();
|
||||
}
|
||||
|
|
@ -267,12 +291,13 @@ FailPointRegistry& globalFailPointRegistry() {
|
|||
return p;
|
||||
}
|
||||
|
||||
void setGlobalFailPoint(const std::string& failPointName, const BSONObj& cmdObj) {
|
||||
int64_t setGlobalFailPoint(const std::string& failPointName, const BSONObj& cmdObj) {
|
||||
FailPoint* failPoint = globalFailPointRegistry().find(failPointName);
|
||||
if (failPoint == nullptr)
|
||||
uasserted(ErrorCodes::FailPointSetFailed, failPointName + " not found");
|
||||
failPoint->setMode(uassertStatusOK(FailPoint::parseBSON(cmdObj)));
|
||||
auto timesEntered = failPoint->setMode(uassertStatusOK(FailPoint::parseBSON(cmdObj)));
|
||||
warning() << "failpoint: " << failPointName << " set to: " << failPoint->toBSON();
|
||||
return timesEntered;
|
||||
}
|
||||
|
||||
FailPointEnableBlock::FailPointEnableBlock(std::string failPointName)
|
||||
|
|
|
|||
|
|
@ -244,12 +244,28 @@ public:
|
|||
* @param extra arbitrary BSON object that can be stored to this fail point
|
||||
* that can be referenced afterwards with #getData. Defaults to an empty
|
||||
* document.
|
||||
*
|
||||
* @returns the number of times the fail point has been entered so far.
|
||||
*/
|
||||
void setMode(Mode mode, ValType val = 0, BSONObj extra = {});
|
||||
void setMode(ModeOptions opt) {
|
||||
setMode(std::move(opt.mode), std::move(opt.val), std::move(opt.extra));
|
||||
int64_t setMode(Mode mode, ValType val = 0, BSONObj extra = {});
|
||||
int64_t setMode(ModeOptions opt) {
|
||||
return setMode(std::move(opt.mode), std::move(opt.val), std::move(opt.extra));
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the fail point has been entered the desired number of times.
|
||||
*
|
||||
* @param timesEntered the number of times the fail point has been entered.
|
||||
*/
|
||||
void waitForTimesEntered(int64_t timesEntered);
|
||||
|
||||
/**
|
||||
* Like `waitForTimesEntered`, but interruptible via the `opCtx->sleepFor` mechanism. See
|
||||
* `mongo::Interruptible::sleepFor` (Interruptible is a base class of
|
||||
* OperationContext).
|
||||
*/
|
||||
void waitForTimesEntered(OperationContext* opCtx, int64_t timesEntered);
|
||||
|
||||
/**
|
||||
* @returns a BSON object showing the current mode and data stored.
|
||||
*/
|
||||
|
|
@ -359,6 +375,14 @@ private:
|
|||
* 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 _slowShouldFailOpenBlockImpl(std::function<bool(const BSONObj&)> cb) noexcept;
|
||||
|
||||
/**
|
||||
* slow path for #_shouldFailOpenBlock
|
||||
*
|
||||
* Calls _slowShouldFailOpenBlockImpl. If it returns slowOn, increments the number of times
|
||||
* the fail point has been entered before returning the RetCode.
|
||||
*/
|
||||
RetCode _slowShouldFailOpenBlock(std::function<bool(const BSONObj&)> cb) noexcept;
|
||||
|
||||
/**
|
||||
|
|
@ -374,6 +398,9 @@ private:
|
|||
// 0~30: unsigned ref counter for active dynamic instances.
|
||||
AtomicWord<std::uint32_t> _fpInfo{0};
|
||||
|
||||
// Total number of times the fail point has been entered.
|
||||
AtomicWord<int64_t> _timesEntered{0};
|
||||
|
||||
// Invariant: These should be read only if kActiveBit of _fpInfo is set.
|
||||
Mode _mode{off};
|
||||
AtomicWord<int> _timesOrPeriod{0};
|
||||
|
|
@ -435,10 +462,11 @@ private:
|
|||
|
||||
/**
|
||||
* Set a fail point in the global registry to a given value via BSON
|
||||
* @return the number of times the fail point has been entered so far.
|
||||
* @throw DBException corresponding to ErrorCodes::FailPointSetFailed if no failpoint
|
||||
* called failPointName exists.
|
||||
*/
|
||||
void setGlobalFailPoint(const std::string& failPointName, const BSONObj& cmdObj);
|
||||
int64_t setGlobalFailPoint(const std::string& failPointName, const BSONObj& cmdObj);
|
||||
|
||||
/**
|
||||
* Registration object for FailPoint. Its static-initializer registers FailPoint `fp`
|
||||
|
|
|
|||
|
|
@ -36,11 +36,14 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mongo/db/client.h"
|
||||
#include "mongo/platform/mutex.h"
|
||||
#include "mongo/stdx/thread.h"
|
||||
#include "mongo/unittest/unittest.h"
|
||||
#include "mongo/util/clock_source_mock.h"
|
||||
#include "mongo/util/fail_point.h"
|
||||
#include "mongo/util/log.h"
|
||||
#include "mongo/util/tick_source_mock.h"
|
||||
#include "mongo/util/time_support.h"
|
||||
|
||||
using mongo::BSONObj;
|
||||
|
|
@ -426,3 +429,33 @@ TEST(FailPoint, FailPointBlockIfBasicTest) {
|
|||
failPoint.executeIf([](auto&&) { ASSERT(!"shouldn't get here"); }, [](auto&&) { return true; });
|
||||
}
|
||||
} // namespace mongo_test
|
||||
|
||||
namespace mongo {
|
||||
|
||||
TEST(FailPoint, WaitForFailPointTimeout) {
|
||||
FailPoint failPoint;
|
||||
failPoint.setMode(FailPoint::alwaysOn);
|
||||
|
||||
const auto service = ServiceContext::make();
|
||||
const std::shared_ptr<ClockSourceMock> mockClock = std::make_shared<ClockSourceMock>();
|
||||
service->setFastClockSource(std::make_unique<SharedClockSourceAdapter>(mockClock));
|
||||
service->setPreciseClockSource(std::make_unique<SharedClockSourceAdapter>(mockClock));
|
||||
service->setTickSource(std::make_unique<TickSourceMock<>>());
|
||||
|
||||
const auto client = service->makeClient("WaitForFailPointTest");
|
||||
auto opCtx = client->makeOperationContext();
|
||||
opCtx->setDeadlineAfterNowBy(Milliseconds{999}, ErrorCodes::ExceededTimeLimit);
|
||||
|
||||
stdx::thread waitForFailPoint([&] {
|
||||
ASSERT_THROWS_CODE(failPoint.waitForTimesEntered(opCtx.get(), 1),
|
||||
AssertionException,
|
||||
ErrorCodes::ExceededTimeLimit);
|
||||
});
|
||||
|
||||
mockClock->advance(Milliseconds{1000});
|
||||
waitForFailPoint.join();
|
||||
|
||||
failPoint.setMode(FailPoint::off);
|
||||
}
|
||||
|
||||
} // namespace mongo
|
||||
|
|
|
|||
Loading…
Reference in New Issue