SERVER-39165 Add waitForFailpoint command and update non-repl tests

This commit is contained in:
Cheahuychou Mao 2019-10-25 15:42:45 +00:00 committed by evergreen
parent 232a30488c
commit f79498e9aa
19 changed files with 283 additions and 42 deletions

View File

@ -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();
/**

View File

@ -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());

View File

@ -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);

View File

@ -532,6 +532,7 @@ let viewsCommandTests = {
voteCommitIndexBuild: {skip: isUnrelated},
voteCommitTransaction: {skip: isUnrelated},
voteAbortTransaction: {skip: isUnrelated},
waitForFailPoint: {skip: isUnrelated},
whatsmyuri: {skip: isUnrelated},
whatsmysni: {skip: isUnrelated}
};

View File

@ -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();

View File

@ -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"}));
}
};
};
})();

View File

@ -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);

View File

@ -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)"},
};

View File

@ -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"}
};

View File

@ -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"}
};

View File

@ -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"}
};

View File

@ -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)"},
};

View File

@ -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

View File

@ -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());

View File

@ -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',

View File

@ -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

View File

@ -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)

View File

@ -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`

View File

@ -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