diff options
author | Kevin Pulo <kevin.pulo@mongodb.com> | 2019-11-29 08:59:07 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-11-29 08:59:07 +0000 |
commit | 81fd5d7451f804c978f70d9c890097ac15af9a08 (patch) | |
tree | 8655866841823459715fdb3c986a9e450791e100 /src | |
parent | 6d9ad57515131568e8dafa7116b9fbdf4b0e7d13 (diff) | |
download | mongo-81fd5d7451f804c978f70d9c890097ac15af9a08.tar.gz |
SERVER-43712 mongos use ReadWriteConcernDefaults for RC
Diffstat (limited to 'src')
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)); |