summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKevin Pulo <kevin.pulo@mongodb.com>2019-11-29 08:59:07 +0000
committerevergreen <evergreen@mongodb.com>2019-11-29 08:59:07 +0000
commit81fd5d7451f804c978f70d9c890097ac15af9a08 (patch)
tree8655866841823459715fdb3c986a9e450791e100 /src
parent6d9ad57515131568e8dafa7116b9fbdf4b0e7d13 (diff)
downloadmongo-81fd5d7451f804c978f70d9c890097ac15af9a08.tar.gz
SERVER-43712 mongos use ReadWriteConcernDefaults for RC
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/commands.cpp2
-rw-r--r--src/mongo/db/commands.h21
-rw-r--r--src/mongo/db/commands/count_cmd.cpp3
-rw-r--r--src/mongo/db/commands/distinct.cpp3
-rw-r--r--src/mongo/db/commands/getmore_cmd.cpp13
-rw-r--r--src/mongo/db/commands/haystack.cpp3
-rw-r--r--src/mongo/db/commands/txn_cmds.cpp18
-rw-r--r--src/mongo/db/operation_context.h15
-rw-r--r--src/mongo/db/query/query_request.cpp8
-rw-r--r--src/mongo/db/query/query_request.h9
-rw-r--r--src/mongo/db/service_entry_point_common.cpp28
-rw-r--r--src/mongo/db/transaction_validation.cpp18
-rw-r--r--src/mongo/db/transaction_validation.h11
-rw-r--r--src/mongo/s/SConscript1
-rw-r--r--src/mongo/s/cluster_commands_helpers.cpp51
-rw-r--r--src/mongo/s/cluster_commands_helpers.h5
-rw-r--r--src/mongo/s/commands/cluster_abort_transaction_cmd.cpp18
-rw-r--r--src/mongo/s/commands/cluster_count_cmd.cpp3
-rw-r--r--src/mongo/s/commands/cluster_distinct_cmd.cpp3
-rw-r--r--src/mongo/s/commands/cluster_find_and_modify_cmd.cpp7
-rw-r--r--src/mongo/s/commands/cluster_find_cmd.cpp9
-rw-r--r--src/mongo/s/commands/cluster_getmore_cmd.cpp13
-rw-r--r--src/mongo/s/commands/cluster_killcursors_cmd.cpp3
-rw-r--r--src/mongo/s/commands/strategy.cpp54
-rw-r--r--src/mongo/s/transaction_router.cpp16
25 files changed, 244 insertions, 91 deletions
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp
index 766d6472105..469c71c49a2 100644
--- a/src/mongo/db/commands.cpp
+++ b/src/mongo/db/commands.cpp
@@ -646,7 +646,7 @@ private:
}
ReadConcernSupportResult supportsReadConcern(repl::ReadConcernLevel level) const override {
- return _command->supportsReadConcern(_dbName, cmdObj(), level);
+ return _command->supportsReadConcern(cmdObj(), level);
}
bool allowsAfterClusterTime() const override {
diff --git a/src/mongo/db/commands.h b/src/mongo/db/commands.h
index 2350a040b6d..69ececd390c 100644
--- a/src/mongo/db/commands.h
+++ b/src/mongo/db/commands.h
@@ -484,9 +484,12 @@ public:
* Returns this invocation's support for readConcern.
*/
virtual ReadConcernSupportResult supportsReadConcern(repl::ReadConcernLevel level) const {
- return {{level != repl::ReadConcernLevel::kLocalReadConcern,
- {ErrorCodes::InvalidOptions, "read concern not supported"}},
- {{ErrorCodes::InvalidOptions, "default read concern not permitted"}}};
+ static const Status kReadConcernNotSupported{ErrorCodes::InvalidOptions,
+ "read concern not supported"};
+ static const Status kDefaultReadConcernNotPermitted{ErrorCodes::InvalidOptions,
+ "default read concern not permitted"};
+ return {{level != repl::ReadConcernLevel::kLocalReadConcern, kReadConcernNotSupported},
+ {kDefaultReadConcernNotPermitted}};
}
/**
@@ -633,12 +636,14 @@ public:
* the option to the shards as needed. We rely on the shards to fail the commands in the
* cases where it isn't supported.
*/
- virtual ReadConcernSupportResult supportsReadConcern(const std::string& dbName,
- const BSONObj& cmdObj,
+ virtual ReadConcernSupportResult supportsReadConcern(const BSONObj& cmdObj,
repl::ReadConcernLevel level) const {
- return {{level != repl::ReadConcernLevel::kLocalReadConcern,
- {ErrorCodes::InvalidOptions, "read concern not supported"}},
- {{ErrorCodes::InvalidOptions, "default read concern not permitted"}}};
+ static const Status kReadConcernNotSupported{ErrorCodes::InvalidOptions,
+ "read concern not supported"};
+ static const Status kDefaultReadConcernNotPermitted{ErrorCodes::InvalidOptions,
+ "default read concern not permitted"};
+ return {{level != repl::ReadConcernLevel::kLocalReadConcern, kReadConcernNotSupported},
+ {kDefaultReadConcernNotPermitted}};
}
virtual bool allowsAfterClusterTime(const BSONObj& cmdObj) const {
diff --git a/src/mongo/db/commands/count_cmd.cpp b/src/mongo/db/commands/count_cmd.cpp
index 74826de1215..168f8c12c67 100644
--- a/src/mongo/db/commands/count_cmd.cpp
+++ b/src/mongo/db/commands/count_cmd.cpp
@@ -90,8 +90,7 @@ public:
return false;
}
- ReadConcernSupportResult supportsReadConcern(const std::string& dbName,
- const BSONObj& cmdObj,
+ ReadConcernSupportResult supportsReadConcern(const BSONObj& cmdObj,
repl::ReadConcernLevel level) const override {
return ReadConcernSupportResult::allSupportedAndDefaultPermitted();
}
diff --git a/src/mongo/db/commands/distinct.cpp b/src/mongo/db/commands/distinct.cpp
index 6dadccff56b..b4e16653a51 100644
--- a/src/mongo/db/commands/distinct.cpp
+++ b/src/mongo/db/commands/distinct.cpp
@@ -84,8 +84,7 @@ public:
return true;
}
- ReadConcernSupportResult supportsReadConcern(const std::string& dbName,
- const BSONObj& cmdObj,
+ ReadConcernSupportResult supportsReadConcern(const BSONObj& cmdObj,
repl::ReadConcernLevel level) const override {
return ReadConcernSupportResult::allSupportedAndDefaultPermitted();
}
diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp
index 62a5d38cd0a..687e5e32de8 100644
--- a/src/mongo/db/commands/getmore_cmd.cpp
+++ b/src/mongo/db/commands/getmore_cmd.cpp
@@ -73,6 +73,15 @@ MONGO_FAIL_POINT_DEFINE(GetMoreHangBeforeReadLock);
// The timeout when waiting for linearizable read concern on a getMore command.
static constexpr int kLinearizableReadConcernTimeout = 15000;
+// getMore can run with any readConcern, because cursor-creating commands like find can run with any
+// readConcern. However, since getMore automatically uses the readConcern of the command that
+// created the cursor, it is not appropriate to apply the default readConcern (just as
+// client-specified readConcern isn't appropriate).
+static const ReadConcernSupportResult kSupportsReadConcernResult{
+ Status::OK(),
+ {{ErrorCodes::InvalidOptions,
+ "default read concern not permitted (getMore uses the cursor's read concern)"}}};
+
/**
* Validates that the lsid of 'opCtx' matches that of 'cursor'. This must be called after
* authenticating, so that it is safe to report the lsid of 'cursor'.
@@ -237,6 +246,10 @@ public:
return false;
}
+ ReadConcernSupportResult supportsReadConcern(repl::ReadConcernLevel level) const override {
+ return kSupportsReadConcernResult;
+ }
+
bool allowsAfterClusterTime() const override {
return false;
}
diff --git a/src/mongo/db/commands/haystack.cpp b/src/mongo/db/commands/haystack.cpp
index 3065e3e3f7c..5477c34b53a 100644
--- a/src/mongo/db/commands/haystack.cpp
+++ b/src/mongo/db/commands/haystack.cpp
@@ -73,8 +73,7 @@ public:
return AllowedOnSecondary::kAlways;
}
- ReadConcernSupportResult supportsReadConcern(const std::string& dbName,
- const BSONObj& cmdObj,
+ ReadConcernSupportResult supportsReadConcern(const BSONObj& cmdObj,
repl::ReadConcernLevel level) const final {
return ReadConcernSupportResult::allSupportedAndDefaultPermitted();
}
diff --git a/src/mongo/db/commands/txn_cmds.cpp b/src/mongo/db/commands/txn_cmds.cpp
index 4575eef8a62..9f9e7afa02e 100644
--- a/src/mongo/db/commands/txn_cmds.cpp
+++ b/src/mongo/db/commands/txn_cmds.cpp
@@ -43,6 +43,7 @@
#include "mongo/db/s/transaction_coordinator_service.h"
#include "mongo/db/service_context.h"
#include "mongo/db/transaction_participant.h"
+#include "mongo/db/transaction_validation.h"
#include "mongo/util/log.h"
namespace mongo {
@@ -146,6 +147,11 @@ public:
} commitTxn;
+static const Status kOnlyTransactionsReadConcernsSupported{
+ ErrorCodes::InvalidOptions, "only read concerns valid in transactions are supported"};
+static const Status kDefaultReadConcernNotPermitted{ErrorCodes::InvalidOptions,
+ "default read concern not permitted"};
+
class CmdAbortTxn : public BasicCommand {
public:
CmdAbortTxn() : BasicCommand("abortTransaction") {}
@@ -162,6 +168,18 @@ public:
return true;
}
+ ReadConcernSupportResult supportsReadConcern(const BSONObj& cmdObj,
+ repl::ReadConcernLevel level) const override {
+ // abortTransaction commences running inside a transaction (even though the transaction will
+ // be ended by the time it completes). Therefore it needs to accept any readConcern which
+ // is valid within a transaction. However it is not appropriate to apply the default
+ // readConcern, since the readConcern of the transaction (set by the first operation) is
+ // what must apply.
+ return {{!isReadConcernLevelAllowedInTransaction(level),
+ kOnlyTransactionsReadConcernsSupported},
+ {kDefaultReadConcernNotPermitted}};
+ }
+
std::string help() const override {
return "Aborts a transaction";
}
diff --git a/src/mongo/db/operation_context.h b/src/mongo/db/operation_context.h
index 03a385651eb..f0ff525e304 100644
--- a/src/mongo/db/operation_context.h
+++ b/src/mongo/db/operation_context.h
@@ -372,6 +372,20 @@ public:
_inMultiDocumentTransaction = true;
}
+ /**
+ * Returns whether this operation is starting a multi-document transaction.
+ */
+ bool isStartingMultiDocumentTransaction() const {
+ return _isStartingMultiDocumentTransaction;
+ }
+
+ /**
+ * Sets whether this operation is starting a multi-document transaction.
+ */
+ void setIsStartingMultiDocumentTransaction(bool isStartingMultiDocumentTransaction) {
+ _isStartingMultiDocumentTransaction = isStartingMultiDocumentTransaction;
+ }
+
void setComment(const BSONObj& comment) {
_comment = comment.getOwned();
}
@@ -509,6 +523,7 @@ private:
bool _writesAreReplicated = true;
bool _shouldParticipateInFlowControl = true;
bool _inMultiDocumentTransaction = false;
+ bool _isStartingMultiDocumentTransaction = false;
// If populated, this is an owned singleton BSONObj whose only field, 'comment', is a copy of
// the 'comment' field from the input command object.
diff --git a/src/mongo/db/query/query_request.cpp b/src/mongo/db/query/query_request.cpp
index fcfa9915611..a33557263ac 100644
--- a/src/mongo/db/query/query_request.cpp
+++ b/src/mongo/db/query/query_request.cpp
@@ -487,8 +487,8 @@ void QueryRequest::asFindCommandInternal(BSONObjBuilder* cmdBuilder) const {
cmdBuilder->append(kHintField, _hint);
}
- if (!_readConcern.isEmpty()) {
- cmdBuilder->append(repl::ReadConcernArgs::kReadConcernFieldName, _readConcern);
+ if (_readConcern) {
+ cmdBuilder->append(repl::ReadConcernArgs::kReadConcernFieldName, *_readConcern);
}
if (!_collation.isEmpty()) {
@@ -1159,8 +1159,8 @@ StatusWith<BSONObj> QueryRequest::asAggregationCommand() const {
if (!_hint.isEmpty()) {
aggregationBuilder.append("hint", _hint);
}
- if (!_readConcern.isEmpty()) {
- aggregationBuilder.append("readConcern", _readConcern);
+ if (_readConcern) {
+ aggregationBuilder.append("readConcern", *_readConcern);
}
if (!_unwrappedReadPref.isEmpty()) {
aggregationBuilder.append(QueryRequest::kUnwrappedReadPrefField, _unwrappedReadPref);
diff --git a/src/mongo/db/query/query_request.h b/src/mongo/db/query/query_request.h
index 10c5cbab658..07da6b23f8e 100644
--- a/src/mongo/db/query/query_request.h
+++ b/src/mongo/db/query/query_request.h
@@ -180,7 +180,12 @@ public:
}
const BSONObj& getReadConcern() const {
- return _readConcern;
+ if (_readConcern) {
+ return *_readConcern;
+ } else {
+ static const auto empty = BSONObj();
+ return empty;
+ }
}
void setReadConcern(BSONObj readConcern) {
@@ -495,7 +500,7 @@ private:
// {$hint: <String>}, where <String> is the index name hinted.
BSONObj _hint;
// The read concern is parsed elsewhere.
- BSONObj _readConcern;
+ boost::optional<BSONObj> _readConcern;
// The collation is parsed elsewhere.
BSONObj _collation;
diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp
index d6c5f260565..3bff6836fa9 100644
--- a/src/mongo/db/service_entry_point_common.cpp
+++ b/src/mongo/db/service_entry_point_common.cpp
@@ -262,33 +262,27 @@ StatusWith<repl::ReadConcernArgs> _extractReadConcern(OperationContext* opCtx,
const auto rcDefault = ReadWriteConcernDefaults::get(opCtx->getServiceContext())
.getDefaultReadConcern();
if (rcDefault) {
- readConcernArgs = *rcDefault;
+ readConcernArgs = std::move(*rcDefault);
LOG(2) << "Applying default readConcern on "
<< invocation->definition()->getName() << " of " << *rcDefault;
+ // Update the readConcernSupport, since the default RC was applied.
+ readConcernSupport =
+ invocation->supportsReadConcern(readConcernArgs.getLevel());
}
}
}
}
- // Update the readConcernSupport, in case the default RC was applied.
- readConcernSupport = invocation->supportsReadConcern(readConcernArgs.getLevel());
-
// If we are starting a transaction, we only need to check whether the read concern is
// appropriate for running a transaction. There is no need to check whether the specific
// command supports the read concern, because all commands that are allowed to run in a
// transaction must support all applicable read concerns.
if (startTransaction) {
- switch (readConcernArgs.getLevel()) {
- case repl::ReadConcernLevel::kLocalReadConcern:
- case repl::ReadConcernLevel::kMajorityReadConcern:
- case repl::ReadConcernLevel::kSnapshotReadConcern:
- // Acceptable readConcern for a transaction.
- break;
- default:
- return {ErrorCodes::InvalidOptions,
- "The readConcern level must be either 'local' (default), 'majority' or "
- "'snapshot' in "
- "order to run in a transaction"};
+ if (!isReadConcernLevelAllowedInTransaction(readConcernArgs.getLevel())) {
+ return {ErrorCodes::InvalidOptions,
+ "The readConcern level must be either 'local' (default), 'majority' or "
+ "'snapshot' in "
+ "order to run in a transaction"};
}
if (readConcernArgs.getArgsOpTime()) {
return {ErrorCodes::InvalidOptions,
@@ -955,6 +949,10 @@ void execCommandDatabase(OperationContext* opCtx,
opCtx->lockState()->setShouldConflictWithSecondaryBatchApplication(false);
}
+ // Remember whether or not this operation is starting a transaction, in case something later
+ // in the execution needs to adjust its behavior based on this.
+ opCtx->setIsStartingMultiDocumentTransaction(startTransaction);
+
auto& oss = OperationShardingState::get(opCtx);
if (!opCtx->getClient()->isInDirectClient() &&
diff --git a/src/mongo/db/transaction_validation.cpp b/src/mongo/db/transaction_validation.cpp
index 3401ceea111..c036569dc47 100644
--- a/src/mongo/db/transaction_validation.cpp
+++ b/src/mongo/db/transaction_validation.cpp
@@ -51,21 +51,31 @@ const StringMap<int> retryableWriteCommands = {
// Commands that can be sent with session info but should not check out a session.
const StringMap<int> skipSessionCheckoutList = {{"coordinateCommitTransaction", 1}};
+const StringMap<int> transactionCommands = {{"commitTransaction", 1},
+ {"coordinateCommitTransaction", 1},
+ {"abortTransaction", 1},
+ {"prepareTransaction", 1}};
+
bool isRetryableWriteCommand(StringData cmdName) {
return retryableWriteCommands.find(cmdName) != retryableWriteCommands.cend();
}
} // namespace
-bool commandSupportsWriteConcernInTransaction(StringData cmdName) {
- return cmdName == "commitTransaction" || cmdName == "coordinateCommitTransaction" ||
- cmdName == "abortTransaction" || cmdName == "prepareTransaction";
+bool isTransactionCommand(StringData cmdName) {
+ return transactionCommands.find(cmdName) != transactionCommands.cend();
}
void validateWriteConcernForTransaction(const WriteConcernOptions& wcResult, StringData cmdName) {
uassert(ErrorCodes::InvalidOptions,
"writeConcern is not allowed within a multi-statement transaction",
- wcResult.usedDefault || commandSupportsWriteConcernInTransaction(cmdName));
+ wcResult.usedDefault || isTransactionCommand(cmdName));
+}
+
+bool isReadConcernLevelAllowedInTransaction(repl::ReadConcernLevel readConcernLevel) {
+ return readConcernLevel == repl::ReadConcernLevel::kSnapshotReadConcern ||
+ readConcernLevel == repl::ReadConcernLevel::kMajorityReadConcern ||
+ readConcernLevel == repl::ReadConcernLevel::kLocalReadConcern;
}
bool shouldCommandSkipSessionCheckout(StringData cmdName) {
diff --git a/src/mongo/db/transaction_validation.h b/src/mongo/db/transaction_validation.h
index 2e57866eeb1..f2447f6ab3a 100644
--- a/src/mongo/db/transaction_validation.h
+++ b/src/mongo/db/transaction_validation.h
@@ -30,14 +30,16 @@
#pragma once
#include "mongo/db/logical_session_id.h"
+#include "mongo/db/repl/read_concern_level.h"
#include "mongo/db/write_concern_options.h"
namespace mongo {
/**
- * Returns true if the given cmd name is allowed to specify write concern in a transaction.
+ * Returns true if the given cmd name is a transaction control command. These are also the only
+ * commands allowed to specify write concern in a transaction.
*/
-bool commandSupportsWriteConcernInTransaction(StringData cmdName);
+bool isTransactionCommand(StringData cmdName);
/**
* Throws if the given write concern is not allowed in a transaction.
@@ -45,6 +47,11 @@ bool commandSupportsWriteConcernInTransaction(StringData cmdName);
void validateWriteConcernForTransaction(const WriteConcernOptions& wcResult, StringData cmdName);
/**
+ * Returns true if the given readConcern level is valid for use in a transaction.
+ */
+bool isReadConcernLevelAllowedInTransaction(repl::ReadConcernLevel readConcernLevel);
+
+/**
* Returns true if the given command is one of the commands that does not check out a session
* regardless of its session options, e.g. two-phase commit commands.
*/
diff --git a/src/mongo/s/SConscript b/src/mongo/s/SConscript
index 2096d9c1a47..e1953394a38 100644
--- a/src/mongo/s/SConscript
+++ b/src/mongo/s/SConscript
@@ -69,6 +69,7 @@ env.Library(
'$BUILD_DIR/mongo/db/commands/txn_cmd_request',
'$BUILD_DIR/mongo/db/curop',
'$BUILD_DIR/mongo/db/logical_session_id_helpers',
+ '$BUILD_DIR/mongo/db/shared_request_handling',
'$BUILD_DIR/mongo/db/repl/read_concern_args',
'async_requests_sender',
'common_s',
diff --git a/src/mongo/s/cluster_commands_helpers.cpp b/src/mongo/s/cluster_commands_helpers.cpp
index 6036dcc31c8..3612afa499c 100644
--- a/src/mongo/s/cluster_commands_helpers.cpp
+++ b/src/mongo/s/cluster_commands_helpers.cpp
@@ -41,6 +41,7 @@
#include "mongo/db/logical_clock.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/query/cursor_response.h"
+#include "mongo/db/repl/read_concern_args.h"
#include "mongo/executor/task_executor_pool.h"
#include "mongo/rpc/get_status_from_command_result.h"
#include "mongo/rpc/write_concern_error_detail.h"
@@ -267,19 +268,41 @@ BSONObj appendAllowImplicitCreate(BSONObj cmdObj, bool allow) {
return newCmdBuilder.obj();
}
-BSONObj applyReadWriteConcern(OperationContext* opCtx, bool appendWC, const BSONObj& cmdObj) {
- // Never apply write concern to ordinary operations inside transactions. Applying writeConcern
- // to terminal operations such as abortTransaction and commitTransaction is done directly by the
- // TransactionRouter.
+BSONObj applyReadWriteConcern(OperationContext* opCtx,
+ bool appendRC,
+ bool appendWC,
+ const BSONObj& cmdObj) {
if (TransactionRouter::get(opCtx)) {
- return cmdObj;
+ // When running in a transaction, the rules are:
+ // - Never apply writeConcern. Applying writeConcern to terminal operations such as
+ // abortTransaction and commitTransaction is done directly by the TransactionRouter.
+ // - Apply readConcern only if this is the first operation in the transaction.
+
+ if (!opCtx->isStartingMultiDocumentTransaction()) {
+ // Cannot apply either read or writeConcern, so short-circuit.
+ return cmdObj;
+ }
+
+ if (!appendRC) {
+ // First operation in transaction, but the caller has not requested readConcern be
+ // applied, so there's nothing to do.
+ return cmdObj;
+ }
+
+ // First operation in transaction, so ensure that writeConcern is not applied, then continue
+ // and apply the readConcern.
+ appendWC = false;
}
// Append all original fields except the readConcern/writeConcern field to the new command.
BSONObjBuilder output;
+ bool seenReadConcern = false;
bool seenWriteConcern = false;
for (const auto& elem : cmdObj) {
const auto name = elem.fieldNameStringData();
+ if (appendRC && name == repl::ReadConcernArgs::kReadConcernFieldName) {
+ seenReadConcern = true;
+ }
if (appendWC && name == WriteConcernOptions::kWriteConcernField) {
seenWriteConcern = true;
}
@@ -289,6 +312,10 @@ BSONObj applyReadWriteConcern(OperationContext* opCtx, bool appendWC, const BSON
}
// Finally, add the new read/write concern.
+ if (appendRC && !seenReadConcern) {
+ const auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx);
+ output.appendElements(readConcernArgs.toBSON());
+ }
if (appendWC && !seenWriteConcern) {
output.append(WriteConcernOptions::kWriteConcernField, opCtx->getWriteConcern().toBSON());
}
@@ -299,11 +326,21 @@ BSONObj applyReadWriteConcern(OperationContext* opCtx, bool appendWC, const BSON
BSONObj applyReadWriteConcern(OperationContext* opCtx,
CommandInvocation* invocation,
const BSONObj& cmdObj) {
- return applyReadWriteConcern(opCtx, invocation->supportsWriteConcern(), cmdObj);
+ const auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx);
+ const auto readConcernSupport = invocation->supportsReadConcern(readConcernArgs.getLevel());
+ return applyReadWriteConcern(opCtx,
+ readConcernSupport.readConcernSupport.isOK(),
+ invocation->supportsWriteConcern(),
+ cmdObj);
}
BSONObj applyReadWriteConcern(OperationContext* opCtx, BasicCommand* cmd, const BSONObj& cmdObj) {
- return applyReadWriteConcern(opCtx, cmd->supportsWriteConcern(cmdObj), cmdObj);
+ const auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx);
+ const auto readConcernSupport = cmd->supportsReadConcern(cmdObj, readConcernArgs.getLevel());
+ return applyReadWriteConcern(opCtx,
+ readConcernSupport.readConcernSupport.isOK(),
+ cmd->supportsWriteConcern(cmdObj),
+ cmdObj);
}
BSONObj stripWriteConcern(const BSONObj& cmdObj) {
diff --git a/src/mongo/s/cluster_commands_helpers.h b/src/mongo/s/cluster_commands_helpers.h
index f65f39f9607..d7f001fee75 100644
--- a/src/mongo/s/cluster_commands_helpers.h
+++ b/src/mongo/s/cluster_commands_helpers.h
@@ -95,7 +95,10 @@ BSONObj appendAllowImplicitCreate(BSONObj cmdObj, bool allow);
* Returns a copy of 'cmdObj' with the read/writeConcern from the OpCtx appended, unless the
* cmdObj explicitly specifies read/writeConcern.
*/
-BSONObj applyReadWriteConcern(OperationContext* opCtx, bool appendWC, const BSONObj& cmdObj);
+BSONObj applyReadWriteConcern(OperationContext* opCtx,
+ bool appendRC,
+ bool appendWC,
+ const BSONObj& cmdObj);
/**
* Convenience versions of applyReadWriteConcern() for calling from within
diff --git a/src/mongo/s/commands/cluster_abort_transaction_cmd.cpp b/src/mongo/s/commands/cluster_abort_transaction_cmd.cpp
index 141b1db98f0..14f9b05d890 100644
--- a/src/mongo/s/commands/cluster_abort_transaction_cmd.cpp
+++ b/src/mongo/s/commands/cluster_abort_transaction_cmd.cpp
@@ -32,12 +32,18 @@
#include "mongo/platform/basic.h"
#include "mongo/db/commands.h"
+#include "mongo/db/transaction_validation.h"
#include "mongo/s/cluster_commands_helpers.h"
#include "mongo/s/transaction_router.h"
namespace mongo {
namespace {
+static const Status kOnlyTransactionsReadConcernsSupported{
+ ErrorCodes::InvalidOptions, "only read concerns valid in transactions are supported"};
+static const Status kDefaultReadConcernNotPermitted{ErrorCodes::InvalidOptions,
+ "default read concern not permitted"};
+
/**
* Implements the abortTransaction command on mongos.
*/
@@ -57,6 +63,18 @@ public:
return true;
}
+ ReadConcernSupportResult supportsReadConcern(const BSONObj& cmdObj,
+ repl::ReadConcernLevel level) const override {
+ // abortTransaction commences running inside a transaction (even though the transaction will
+ // be ended by the time it completes). Therefore it needs to accept any readConcern which
+ // is valid within a transaction. However it is not appropriate to apply the default
+ // readConcern, since the readConcern of the transaction (set by the first operation) is
+ // what must apply.
+ return {{!isReadConcernLevelAllowedInTransaction(level),
+ kOnlyTransactionsReadConcernsSupported},
+ {kDefaultReadConcernNotPermitted}};
+ }
+
std::string help() const override {
return "Aborts a transaction";
}
diff --git a/src/mongo/s/commands/cluster_count_cmd.cpp b/src/mongo/s/commands/cluster_count_cmd.cpp
index a3dd0039dfb..c6187f79c06 100644
--- a/src/mongo/s/commands/cluster_count_cmd.cpp
+++ b/src/mongo/s/commands/cluster_count_cmd.cpp
@@ -65,8 +65,7 @@ public:
return false;
}
- ReadConcernSupportResult supportsReadConcern(const std::string& dbName,
- const BSONObj& cmdObj,
+ ReadConcernSupportResult supportsReadConcern(const BSONObj& cmdObj,
repl::ReadConcernLevel level) const override {
return ReadConcernSupportResult::allSupportedAndDefaultPermitted();
}
diff --git a/src/mongo/s/commands/cluster_distinct_cmd.cpp b/src/mongo/s/commands/cluster_distinct_cmd.cpp
index f0493f9fe98..bba351c4f58 100644
--- a/src/mongo/s/commands/cluster_distinct_cmd.cpp
+++ b/src/mongo/s/commands/cluster_distinct_cmd.cpp
@@ -72,8 +72,7 @@ public:
return false;
}
- ReadConcernSupportResult supportsReadConcern(const std::string& dbName,
- const BSONObj& cmdObj,
+ ReadConcernSupportResult supportsReadConcern(const BSONObj& cmdObj,
repl::ReadConcernLevel level) const final {
return ReadConcernSupportResult::allSupportedAndDefaultPermitted();
}
diff --git a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp
index 84133e9c5b8..a7c60a5228a 100644
--- a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp
+++ b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp
@@ -153,8 +153,7 @@ public:
return true;
}
- ReadConcernSupportResult supportsReadConcern(const std::string& dbName,
- const BSONObj& cmdObj,
+ ReadConcernSupportResult supportsReadConcern(const BSONObj& cmdObj,
repl::ReadConcernLevel level) const final {
return ReadConcernSupportResult::allSupportedAndDefaultPermitted();
}
@@ -206,7 +205,7 @@ public:
chunkMgr->getVersion(shard->getId()),
boost::none,
nss,
- applyReadWriteConcern(opCtx, false, explainCmd),
+ applyReadWriteConcern(opCtx, false, false, explainCmd),
&bob);
} else {
_runCommand(opCtx,
@@ -214,7 +213,7 @@ public:
ChunkVersion::UNSHARDED(),
routingInfo.db().databaseVersion(),
nss,
- applyReadWriteConcern(opCtx, false, explainCmd),
+ applyReadWriteConcern(opCtx, false, false, explainCmd),
&bob);
}
diff --git a/src/mongo/s/commands/cluster_find_cmd.cpp b/src/mongo/s/commands/cluster_find_cmd.cpp
index d6a14cd7428..ff5c2d8ef4a 100644
--- a/src/mongo/s/commands/cluster_find_cmd.cpp
+++ b/src/mongo/s/commands/cluster_find_cmd.cpp
@@ -63,6 +63,15 @@ std::unique_ptr<QueryRequest> parseCmdObjectToQueryRequest(OperationContext* opC
bool isExplain) {
auto qr = uassertStatusOK(
QueryRequest::makeFromFindCommand(std::move(nss), std::move(cmdObj), isExplain));
+ if (qr->getReadConcern().isEmpty()) {
+ if (opCtx->isStartingMultiDocumentTransaction() || !opCtx->inMultiDocumentTransaction()) {
+ // If there is no explicit readConcern in the cmdObj, and this is either the first
+ // operation in a transaction, or not running in a transaction, then use the readConcern
+ // from the opCtx (which may be a cluster-wide default).
+ const auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx);
+ qr->setReadConcern(readConcernArgs.toBSON()["readConcern"].Obj());
+ }
+ }
uassert(
51202, "Cannot specify runtime constants option to a mongos", !qr->getRuntimeConstants());
qr->setRuntimeConstants(Variables::generateRuntimeConstants(opCtx));
diff --git a/src/mongo/s/commands/cluster_getmore_cmd.cpp b/src/mongo/s/commands/cluster_getmore_cmd.cpp
index f149aafa929..fdc167d2cb7 100644
--- a/src/mongo/s/commands/cluster_getmore_cmd.cpp
+++ b/src/mongo/s/commands/cluster_getmore_cmd.cpp
@@ -40,6 +40,15 @@
namespace mongo {
namespace {
+// getMore can run with any readConcern, because cursor-creating commands like find can run with any
+// readConcern. However, since getMore automatically uses the readConcern of the command that
+// created the cursor, it is not appropriate to apply the default readConcern (just as
+// client-specified readConcern isn't appropriate).
+static const ReadConcernSupportResult kSupportsReadConcernResult{
+ Status::OK(),
+ {{ErrorCodes::InvalidOptions,
+ "default read concern not permitted (getMore uses the cursor's read concern)"}}};
+
/**
* Implements the getMore command on mongos. Retrieves more from an existing mongos cursor
* corresponding to the cursor id passed from the application. In order to generate these results,
@@ -70,6 +79,10 @@ public:
return false;
}
+ ReadConcernSupportResult supportsReadConcern(repl::ReadConcernLevel level) const override {
+ return kSupportsReadConcernResult;
+ }
+
void doCheckAuthorization(OperationContext* opCtx) const override {
uassertStatusOK(AuthorizationSession::get(opCtx->getClient())
->checkAuthForGetMore(_request.nss,
diff --git a/src/mongo/s/commands/cluster_killcursors_cmd.cpp b/src/mongo/s/commands/cluster_killcursors_cmd.cpp
index 2408f00fb0b..106a6f7f1d6 100644
--- a/src/mongo/s/commands/cluster_killcursors_cmd.cpp
+++ b/src/mongo/s/commands/cluster_killcursors_cmd.cpp
@@ -42,8 +42,7 @@ class ClusterKillCursorsCmd final : public KillCursorsCmdBase {
public:
ClusterKillCursorsCmd() = default;
- ReadConcernSupportResult supportsReadConcern(const std::string& dbName,
- const BSONObj& cmdObj,
+ ReadConcernSupportResult supportsReadConcern(const BSONObj& cmdObj,
repl::ReadConcernLevel level) const final {
// killCursors must support read concerns in order to be run in transactions.
return ReadConcernSupportResult::allSupportedAndDefaultPermitted();
diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp
index b0a591cadc5..5625c83f37c 100644
--- a/src/mongo/s/commands/strategy.cpp
+++ b/src/mongo/s/commands/strategy.cpp
@@ -394,8 +394,6 @@ void runCommand(OperationContext* opCtx,
!readConcernArgs.getArgsAtClusterTime());
}
- auto readConcernSupport = invocation->supportsReadConcern(readConcernArgs.getLevel());
-
boost::optional<RouterOperationContextSession> routerSession;
try {
CommandHelpers::evaluateFailCommandFailPoint(opCtx, commandName, invocation->ns());
@@ -439,8 +437,7 @@ void runCommand(OperationContext* opCtx,
}
if (supportsWriteConcern && wc.usedDefault &&
- (!TransactionRouter::get(opCtx) ||
- commandSupportsWriteConcernInTransaction(commandName))) {
+ (!TransactionRouter::get(opCtx) || isTransactionCommand(commandName))) {
// This command supports WC, but wasn't given one - so apply the default, if there is
// one.
if (const auto wcDefault = ReadWriteConcernDefaults::get(opCtx->getServiceContext())
@@ -459,25 +456,42 @@ void runCommand(OperationContext* opCtx,
opCtx->setWriteConcern(wc);
}
+ auto readConcernSupport = invocation->supportsReadConcern(readConcernArgs.getLevel());
+ if (readConcernSupport.defaultReadConcernPermit.isOK() &&
+ (startTransaction || !TransactionRouter::get(opCtx))) {
+ if (readConcernArgs.isEmpty()) {
+ const auto rcDefault = ReadWriteConcernDefaults::get(opCtx->getServiceContext())
+ .getDefaultReadConcern();
+ if (rcDefault) {
+ {
+ // We must obtain the client lock to set ReadConcernArgs, because it's an
+ // in-place reference to the object on the operation context, which may be
+ // concurrently used elsewhere (eg. read by currentOp).
+ stdx::lock_guard<Client> lk(*opCtx->getClient());
+ readConcernArgs = std::move(*rcDefault);
+ }
+ LOG(2) << "Applying default readConcern on "
+ << invocation->definition()->getName() << " of " << *rcDefault;
+ // Update the readConcernSupport, since the default RC was applied.
+ readConcernSupport =
+ invocation->supportsReadConcern(readConcernArgs.getLevel());
+ }
+ }
+ }
+
// If we are starting a transaction, we only need to check whether the read concern is
// appropriate for running a transaction. There is no need to check whether the specific
// command supports the read concern, because all commands that are allowed to run in a
// transaction must support all applicable read concerns.
if (startTransaction) {
- switch (readConcernArgs.getLevel()) {
- case repl::ReadConcernLevel::kLocalReadConcern:
- case repl::ReadConcernLevel::kMajorityReadConcern:
- case repl::ReadConcernLevel::kSnapshotReadConcern:
- // Acceptable readConcern for a transaction.
- break;
- default:
- auto responseBuilder = replyBuilder->getBodyBuilder();
- CommandHelpers::appendCommandStatusNoThrow(
- responseBuilder,
- {ErrorCodes::InvalidOptions,
- "The readConcern level must be either 'local' (default), 'majority' or "
- "'snapshot' in order to run in a transaction"});
- return;
+ if (!isReadConcernLevelAllowedInTransaction(readConcernArgs.getLevel())) {
+ auto responseBuilder = replyBuilder->getBodyBuilder();
+ CommandHelpers::appendCommandStatusNoThrow(
+ responseBuilder,
+ {ErrorCodes::InvalidOptions,
+ "The readConcern level must be either 'local' (default), 'majority' or "
+ "'snapshot' in order to run in a transaction"});
+ return;
}
if (readConcernArgs.getArgsOpTime()) {
auto responseBuilder = replyBuilder->getBodyBuilder();
@@ -508,6 +522,10 @@ void runCommand(OperationContext* opCtx,
}
}
+ // Remember whether or not this operation is starting a transaction, in case something later
+ // in the execution needs to adjust its behavior based on this.
+ opCtx->setIsStartingMultiDocumentTransaction(startTransaction);
+
for (int tries = 0;; ++tries) {
// Try kMaxNumStaleVersionRetries times. On the last try, exceptions are rethrown.
bool canRetry = tries < kMaxNumStaleVersionRetries - 1;
diff --git a/src/mongo/s/transaction_router.cpp b/src/mongo/s/transaction_router.cpp
index 1e5a332902d..de0d4ef78ff 100644
--- a/src/mongo/s/transaction_router.cpp
+++ b/src/mongo/s/transaction_router.cpp
@@ -42,6 +42,7 @@
#include "mongo/db/logical_clock.h"
#include "mongo/db/logical_session_id.h"
#include "mongo/db/repl/read_concern_args.h"
+#include "mongo/db/transaction_validation.h"
#include "mongo/executor/task_executor_pool.h"
#include "mongo/rpc/get_status_from_command_result.h"
#include "mongo/s/async_requests_sender.h"
@@ -66,12 +67,6 @@ const char kReadConcernLevelSnapshotName[] = "snapshot";
const auto getTransactionRouter = Session::declareDecoration<TransactionRouter>();
-bool isTransactionCommand(const BSONObj& cmd) {
- auto cmdName = cmd.firstElement().fieldNameStringData();
- return cmdName == "abortTransaction" || cmdName == "commitTransaction" ||
- cmdName == "prepareTransaction" || cmdName == CoordinateCommitTransaction::kCommandName;
-}
-
/**
* Attaches the given atClusterTime to the readConcern object in the given command object, removing
* afterClusterTime if present. Assumes the given command object has a readConcern field and has
@@ -152,12 +147,6 @@ BSONObjBuilder appendFieldsForStartTransaction(BSONObj cmd,
const StringMap<int> alwaysRetryableCmds = {
{"aggregate", 1}, {"distinct", 1}, {"find", 1}, {"getMore", 1}, {"killCursors", 1}};
-bool isReadConcernLevelAllowedInTransaction(repl::ReadConcernLevel readConcernLevel) {
- return readConcernLevel == repl::ReadConcernLevel::kSnapshotReadConcern ||
- readConcernLevel == repl::ReadConcernLevel::kMajorityReadConcern ||
- readConcernLevel == repl::ReadConcernLevel::kLocalReadConcern;
-}
-
// Returns if a transaction's commit result is unknown based on the given statuses. A result is
// considered unknown if it would be given the "UnknownTransactionCommitResult" as defined by the
// driver transactions specification or fails with one of the errors for invalid write concern that
@@ -429,7 +418,8 @@ BSONObj TransactionRouter::Participant::attachTxnFieldsIfNeeded(
// The first command sent to a participant must start a transaction, unless it is a transaction
// command, which don't support the options that start transactions, i.e. startTransaction and
// readConcern. Otherwise the command must not have a read concern.
- bool mustStartTransaction = isFirstStatementInThisParticipant && !isTransactionCommand(cmd);
+ auto cmdName = cmd.firstElement().fieldNameStringData();
+ bool mustStartTransaction = isFirstStatementInThisParticipant && !isTransactionCommand(cmdName);
if (!mustStartTransaction) {
dassert(!cmd.hasField(repl::ReadConcernArgs::kReadConcernFieldName));