Files
mongo/src/mongo/db/commands/sleep_command.cpp
sl-mongodb 5f0a92e84d SERVER-102624: added intent registration before resourceIdReplicationStateTransition… (#36380)
GitOrigin-RevId: ffe2623c79c0c6b20816a75816780a641c666983
2025-06-23 16:21:18 +00:00

253 lines
10 KiB
C++

/**
* Copyright (C) 2018-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.
*/
#include "mongo/base/status.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/db/commands.h"
#include "mongo/db/concurrency/d_concurrency.h"
#include "mongo/db/concurrency/lock_manager_defs.h"
#include "mongo/db/database_name.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/service_context.h"
#include "mongo/logv2/log.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/clock_source.h"
#include "mongo/util/duration.h"
#include "mongo/util/scopeguard.h"
#include "mongo/util/str.h"
#include "mongo/util/time_support.h"
#include <string>
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand
namespace mongo {
/* For diagnostic / testing purposes. Enabled via command line. See docs/test_commands.md. */
class CmdSleep : public BasicCommand {
public:
bool supportsWriteConcern(const BSONObj& cmd) const override {
return false;
}
bool adminOnly() const override {
return true;
}
AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
return AllowedOnSecondary::kAlways;
}
std::string help() const override {
return "internal testing command. Run a no-op command for an arbitrary amount of time. "
"If neither 'secs' nor 'millis' is set, command will sleep for 10 seconds. "
"If both are set, command will sleep for the sum of 'secs' and 'millis.'\n"
" w:<bool> (deprecated: use 'lock' instead) if true, takes a write lock.\n"
" lock: r, ir, w, iw, none. If r or w, db will block under a lock.\n"
" If ir or iw, db will block under an intent lock. Defaults to ir."
" 'lock' and 'w' may not both be set.\n"
" secs:<seconds> Amount of time to sleep, in seconds.\n"
" millis:<milliseconds> Amount of time to sleep, in ms.\n"
"'seconds' may be used as an alias for 'secs'.\n";
}
// No auth needed because it only works when enabled via command line.
Status checkAuthForOperation(OperationContext*,
const DatabaseName&,
const BSONObj&) const override {
return Status::OK();
}
/**
* An empty 'ns' causes the global lock to be taken.
* A 'ns' that contains <db> causes only the rstl/global/database locks to be taken.
* A complete 'ns' <coll>.<db> causes the rstl/global/database/collection locks to be taken.
*
* Any higher level locks are taken in the appropriate MODE_IS or MODE_IX to match 'mode'.
*/
void _sleepInLock(mongo::OperationContext* opCtx,
long long millis,
LockMode mode,
StringData ns) {
if (ns.empty()) {
Lock::GlobalLock lk(opCtx, mode, Date_t::max(), Lock::InterruptBehavior::kThrow);
LOGV2(6001601,
"Global lock acquired by sleep command.",
"lockMode"_attr = modeName(mode));
opCtx->sleepFor(Milliseconds(millis));
return;
}
// This is not ran in multitenancy since sleep is an internal testing command.
auto nss =
NamespaceStringUtil::deserialize(boost::none, ns, SerializationContext::stateDefault());
uassert(50961, "lockTarget is not a valid namespace", DatabaseName::isValid(nss.dbName()));
auto dbMode = mode;
if (!nss.isDbOnly()) {
// Only acquire minimum dbLock mode required for collection lock acquisition.
dbMode = isSharedLockMode(mode) ? MODE_IS : MODE_IX;
}
Lock::DBLock dbLock(opCtx, nss.dbName(), dbMode, Date_t::max());
if (nss.isDbOnly()) {
LOGV2(6001602,
"Database lock acquired by sleep command.",
"lockMode"_attr = modeName(dbMode));
opCtx->sleepFor(Milliseconds(millis));
return;
}
// Need to acquire DBLock before attempting to acquire a collection lock.
uassert(50962,
"lockTarget is not a valid namespace",
NamespaceString::validCollectionComponent(nss));
Lock::CollectionLock collLock(opCtx, nss, mode, Date_t::max());
LOGV2(6001603,
"Collection lock acquired by sleep command.",
"lockMode"_attr = modeName(mode));
opCtx->sleepFor(Milliseconds(millis));
}
void _sleepInRSTL(mongo::OperationContext* opCtx, long long millis) {
Lock::ResourceLock rstl(opCtx, resourceIdReplicationStateTransitionLock, MODE_X);
LOGV2(6001600, "RSTL MODE_X lock acquired by sleep command.");
opCtx->sleepFor(Milliseconds(millis));
}
CmdSleep() : BasicCommand("sleep") {}
bool run(OperationContext* opCtx,
const DatabaseName&,
const BSONObj& cmdObj,
BSONObjBuilder& result) override {
LOGV2(20504, "Test-only command 'sleep' invoked");
long long msToSleep = 0;
if (cmdObj["secs"] || cmdObj["seconds"] || cmdObj["millis"]) {
uassert(51153,
"Only one of 'secs' and 'seconds' may be specified",
!(cmdObj["secs"] && cmdObj["seconds"]));
if (auto secsElem = cmdObj["secs"]) {
uassert(34344, "'secs' must be a number.", secsElem.isNumber());
msToSleep += secsElem.safeNumberLong() * 1000;
} else if (auto secondsElem = cmdObj["seconds"]) {
uassert(51154, "'seconds' must be a number.", secondsElem.isNumber());
msToSleep += secondsElem.safeNumberLong() * 1000;
}
if (auto millisElem = cmdObj["millis"]) {
uassert(34345, "'millis' must be a number.", millisElem.isNumber());
msToSleep += millisElem.safeNumberLong();
}
} else {
msToSleep = 10 * 1000;
}
auto now = opCtx->getServiceContext()->getFastClockSource()->now();
auto deadline = now + Milliseconds(msToSleep);
// Note that if the system clock moves _backwards_ (which has been known to happen), this
// could result in a much longer sleep than requested. Since this command is only used for
// testing, we're okay with this imprecision.
while (deadline > now) {
Milliseconds msRemaining = deadline - now;
// If the clock moves back by an absurd amount then uassert.
Milliseconds threshold(10000);
uassert(31173,
str::stream() << "Clock must have moved backwards by at least " << threshold
<< " ms during sleep command",
msRemaining.count() < msToSleep + threshold.count());
ON_BLOCK_EXIT(
[&now, opCtx] { now = opCtx->getServiceContext()->getFastClockSource()->now(); });
StringData lockTarget;
if (cmdObj["lockTarget"]) {
lockTarget = cmdObj["lockTarget"].checkAndGetStringData();
}
// TODO: remove in SERVER-103635
if (lockTarget == "RSTL") {
_sleepInRSTL(opCtx, msRemaining.count());
continue;
}
if (!cmdObj["lock"]) {
// The caller may specify either 'w' as true or false to take a global X lock or
// global S lock, respectively.
if (cmdObj.getBoolField("w")) {
_sleepInLock(opCtx, msRemaining.count(), MODE_X, lockTarget);
} else {
_sleepInLock(opCtx, msRemaining.count(), MODE_S, lockTarget);
}
} else {
uassert(34346, "Only one of 'w' and 'lock' may be set.", !cmdObj["w"]);
std::string lock(cmdObj.getStringField("lock"));
if (lock == "none") {
opCtx->sleepFor(Milliseconds(msRemaining));
} else if (lock == "w") {
_sleepInLock(opCtx, msRemaining.count(), MODE_X, lockTarget);
} else if (lock == "iw") {
_sleepInLock(opCtx, msRemaining.count(), MODE_IX, lockTarget);
} else if (lock == "r") {
_sleepInLock(opCtx, msRemaining.count(), MODE_S, lockTarget);
} else {
uassert(
34347, "'lock' must be one of 'r', 'ir', 'w', 'iw', 'none'.", lock == "ir");
_sleepInLock(opCtx, msRemaining.count(), MODE_IS, lockTarget);
}
}
}
// Interrupt point for testing (e.g. maxTimeMS).
opCtx->checkForInterrupt();
return true;
}
ReadWriteType getReadWriteType() const override {
return ReadWriteType::kRead;
}
bool allowedWithSecurityToken() const override {
// This is a test only command, we can safely allow it with multi-tenancy.
return true;
}
};
MONGO_REGISTER_COMMAND(CmdSleep).testOnly().forShard();
} // namespace mongo