summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorLingzhi Deng <lingzhi.deng@mongodb.com>2019-11-06 15:18:44 +0000
committerevergreen <evergreen@mongodb.com>2019-11-06 15:18:44 +0000
commit0ff5e5e7cc09c31d3fe260cf6602f461e6e20bb6 (patch)
tree7340799f87257c4fc5a69574ca9bf2ae0a9d3f5d /src/mongo/db
parentad581efda69010443fac7f964180666476b99785 (diff)
downloadmongo-0ff5e5e7cc09c31d3fe260cf6602f461e6e20bb6.tar.gz
SERVER-41245: Add RetryableWriteError error label
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/SConscript13
-rw-r--r--src/mongo/db/error_labels.cpp139
-rw-r--r--src/mongo/db/error_labels.h (renamed from src/mongo/db/handle_request_response.h)47
-rw-r--r--src/mongo/db/error_labels_test.cpp209
-rw-r--r--src/mongo/db/handle_request_response.cpp61
-rw-r--r--src/mongo/db/ops/write_ops_exec.cpp4
-rw-r--r--src/mongo/db/service_entry_point_common.cpp32
7 files changed, 428 insertions, 77 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index 8305d3dc094..3375d49d295 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -1330,12 +1330,22 @@ env.Library(
)
env.Library(
+ target='error_labels',
+ source=[
+ 'error_labels.cpp',
+ ],
+ LIBDEPS=[
+ 'logical_session_id',
+ ],
+)
+
+env.Library(
target='shared_request_handling',
source=[
- 'handle_request_response.cpp',
'transaction_validation.cpp',
],
LIBDEPS=[
+ 'error_labels',
'logical_session_cache_impl',
],
)
@@ -1794,6 +1804,7 @@ envWithAsio.CppUnitTest(
'transaction_participant_test.cpp',
'update_index_data_test.cpp',
'write_concern_options_test.cpp',
+ 'error_labels_test.cpp',
env.Idlc('commands_test_example.idl')[0],
],
LIBDEPS=[
diff --git a/src/mongo/db/error_labels.cpp b/src/mongo/db/error_labels.cpp
new file mode 100644
index 00000000000..d56ba6dd82d
--- /dev/null
+++ b/src/mongo/db/error_labels.cpp
@@ -0,0 +1,139 @@
+/**
+ * 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/db/error_labels.h"
+
+namespace mongo {
+
+bool ErrorLabelBuilder::isTransientTransactionError() const {
+ // Note that we only apply the TransientTransactionError label if the "autocommit" field is
+ // present in the session options. When present, "autocommit" will always be false, so we
+ // don't check its value. There is no point in returning TransientTransactionError label if
+ // we have already tried to abort it. An error code for which isTransientTransactionError()
+ // is true indicates a transaction failure with no persistent side effects.
+ return _code && _sessionOptions.getTxnNumber() && _sessionOptions.getAutocommit() &&
+ mongo::isTransientTransactionError(_code.get(), _wcCode != boost::none, _isCommitOrAbort());
+}
+
+bool ErrorLabelBuilder::isRetryableWriteError() const {
+ // Do not return RetryableWriteError labels to internal clients (e.g. mongos).
+ if (_isInternalClient) {
+ return false;
+ }
+
+ auto isRetryableWrite = [&]() {
+ return _sessionOptions.getTxnNumber() && !_sessionOptions.getAutocommit();
+ };
+
+ auto isTransactionCommitOrAbort = [&]() {
+ return _sessionOptions.getTxnNumber() && _sessionOptions.getAutocommit() &&
+ _isCommitOrAbort();
+ };
+
+ // Return with RetryableWriteError label on retryable error codes for retryable writes or
+ // transactions commit/abort.
+ if (isRetryableWrite() || isTransactionCommitOrAbort()) {
+ if ((_code && ErrorCodes::isRetriableError(_code.get())) ||
+ (_wcCode && ErrorCodes::isRetriableError(_wcCode.get()))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ErrorLabelBuilder::isNonResumableChangeStreamError() const {
+ return _code && ErrorCodes::isNonResumableChangeStreamError(_code.get());
+}
+
+void ErrorLabelBuilder::build(BSONArrayBuilder& labels) const {
+ // PLEASE CONSULT DRIVERS BEFORE ADDING NEW ERROR LABELS.
+ bool hasTransientTransactionError = false;
+ if (isTransientTransactionError()) {
+ labels << ErrorLabel::kTransientTransaction;
+ hasTransientTransactionError = true;
+ }
+ if (isRetryableWriteError()) {
+ // RetryableWriteError and TransientTransactionError are mutually exclusive.
+ invariant(!hasTransientTransactionError);
+ labels << ErrorLabel::kRetryableWrite;
+ }
+ if (isNonResumableChangeStreamError()) {
+ labels << ErrorLabel::kNonResumableChangeStream;
+ }
+ return;
+}
+
+bool ErrorLabelBuilder::_isCommitOrAbort() const {
+ return _commandName == "commitTransaction" || _commandName == "coordinateCommitTransaction" ||
+ _commandName == "abortTransaction";
+}
+
+BSONObj getErrorLabels(const OperationSessionInfoFromClient& sessionOptions,
+ const std::string& commandName,
+ boost::optional<ErrorCodes::Error> code,
+ boost::optional<ErrorCodes::Error> wcCode,
+ bool isInternalClient) {
+ BSONArrayBuilder labelArray;
+
+ ErrorLabelBuilder labelBuilder(sessionOptions, commandName, code, wcCode, isInternalClient);
+ labelBuilder.build(labelArray);
+
+ return (labelArray.arrSize() > 0) ? BSON("errorLabels" << labelArray.arr()) : BSONObj();
+}
+
+bool isTransientTransactionError(ErrorCodes::Error code,
+ bool hasWriteConcernError,
+ bool isCommitOrAbort) {
+ bool isTransient;
+ switch (code) {
+ case ErrorCodes::WriteConflict:
+ case ErrorCodes::LockTimeout:
+ case ErrorCodes::PreparedTransactionInProgress:
+ isTransient = true;
+ break;
+ default:
+ isTransient = false;
+ break;
+ }
+
+ isTransient |= ErrorCodes::isSnapshotError(code) || ErrorCodes::isNeedRetargettingError(code) ||
+ code == ErrorCodes::StaleDbVersion;
+
+ if (isCommitOrAbort) {
+ // On NoSuchTransaction it's safe to retry the whole transaction only if the data cannot be
+ // rolled back.
+ isTransient |= code == ErrorCodes::NoSuchTransaction && !hasWriteConcernError;
+ } else {
+ isTransient |= ErrorCodes::isRetriableError(code) || code == ErrorCodes::NoSuchTransaction;
+ }
+
+ return isTransient;
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/handle_request_response.h b/src/mongo/db/error_labels.h
index 859a8a35cf3..7490d862372 100644
--- a/src/mongo/db/handle_request_response.h
+++ b/src/mongo/db/error_labels.h
@@ -33,12 +33,55 @@
namespace mongo {
+namespace ErrorLabel {
+// PLEASE CONSULT DRIVERS BEFORE ADDING NEW ERROR LABELS.
+static constexpr StringData kTransientTransaction = "TransientTransactionError"_sd;
+static constexpr StringData kRetryableWrite = "RetryableWriteError"_sd;
+static constexpr StringData kNonResumableChangeStream = "NonResumableChangeStreamError"_sd;
+} // namespace ErrorLabel
+
+class ErrorLabelBuilder {
+public:
+ ErrorLabelBuilder(const OperationSessionInfoFromClient& sessionOptions,
+ const std::string& commandName,
+ boost::optional<ErrorCodes::Error> code,
+ boost::optional<ErrorCodes::Error> wcCode,
+ bool isInternalClient)
+ : _sessionOptions(sessionOptions),
+ _commandName(commandName),
+ _code(code),
+ _wcCode(wcCode),
+ _isInternalClient(isInternalClient) {}
+
+ void build(BSONArrayBuilder& labels) const;
+
+ bool isTransientTransactionError() const;
+ bool isRetryableWriteError() const;
+ bool isNonResumableChangeStreamError() const;
+
+private:
+ bool _isCommitOrAbort() const;
+ const OperationSessionInfoFromClient& _sessionOptions;
+ const std::string& _commandName;
+ boost::optional<ErrorCodes::Error> _code;
+ boost::optional<ErrorCodes::Error> _wcCode;
+ bool _isInternalClient;
+};
+
/**
* Returns the error labels for the given error.
*/
BSONObj getErrorLabels(const OperationSessionInfoFromClient& sessionOptions,
const std::string& commandName,
- ErrorCodes::Error code,
- bool hasWriteConcernError);
+ boost::optional<ErrorCodes::Error> code,
+ boost::optional<ErrorCodes::Error> wcCode,
+ bool isInternalClient);
+
+/**
+ * Whether a write error in a transaction should be labelled with "TransientTransactionError".
+ */
+bool isTransientTransactionError(ErrorCodes::Error code,
+ bool hasWriteConcernError,
+ bool isCommitOrAbort);
} // namespace mongo
diff --git a/src/mongo/db/error_labels_test.cpp b/src/mongo/db/error_labels_test.cpp
new file mode 100644
index 00000000000..92d6d4b73a2
--- /dev/null
+++ b/src/mongo/db/error_labels_test.cpp
@@ -0,0 +1,209 @@
+/**
+ * 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/error_labels.h"
+#include "mongo/db/logical_session_id.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+TEST(IsTransientTransactionErrorTest, NetworkErrorsAreTransientBeforeCommit) {
+ ASSERT_TRUE(isTransientTransactionError(ErrorCodes::HostUnreachable,
+ false /* hasWriteConcernError */,
+ false /* isCommitOrAbort */));
+}
+
+TEST(IsTransientTransactionErrorTest, NetworkErrorsAreNotTransientOnCommit) {
+ ASSERT_FALSE(isTransientTransactionError(
+ ErrorCodes::HostUnreachable, false /* hasWriteConcernError */, true /* isCommitOrAbort */));
+}
+
+TEST(IsTransientTransactionErrorTest, RetryableWriteErrorsAreNotTransientOnAbort) {
+ ASSERT_FALSE(isTransientTransactionError(
+ ErrorCodes::NotMaster, false /* hasWriteConcernError */, true /* isCommitOrAbort */));
+}
+
+TEST(IsTransientTransactionErrorTest,
+ NoSuchTransactionWithWriteConcernErrorsAreNotTransientOnCommit) {
+ ASSERT_FALSE(isTransientTransactionError(ErrorCodes::NoSuchTransaction,
+ true /* hasWriteConcernError */,
+ true /* isCommitOrAbort */));
+}
+
+TEST(IsTransientTransactionErrorTest,
+ NoSuchTransactionWithoutWriteConcernErrorsAreTransientOnCommit) {
+ ASSERT_TRUE(isTransientTransactionError(ErrorCodes::NoSuchTransaction,
+ false /* hasWriteConcernError */,
+ true /* isCommitOrAbort */));
+}
+
+TEST(ErrorLabelBuilderTest, NonErrorCodesHaveNoLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ std::string commandName = "insert";
+ ErrorLabelBuilder builder(sessionInfo, commandName, boost::none, boost::none, false);
+ ASSERT_FALSE(builder.isTransientTransactionError());
+ ASSERT_FALSE(builder.isRetryableWriteError());
+ ASSERT_FALSE(builder.isNonResumableChangeStreamError());
+}
+
+TEST(ErrorLabelBuilderTest, NonTransactionsHaveNoTransientTransactionErrorLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ std::string commandName = "insert";
+ ErrorLabelBuilder builder(
+ sessionInfo, commandName, ErrorCodes::WriteConflict, boost::none, false);
+ ASSERT_FALSE(builder.isTransientTransactionError());
+}
+
+TEST(ErrorLabelBuilderTest, RetryableWritesHaveNoTransientTransactionErrorLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ sessionInfo.setTxnNumber(1);
+ std::string commandName = "insert";
+ ErrorLabelBuilder builder(
+ sessionInfo, commandName, ErrorCodes::WriteConflict, boost::none, false);
+ ASSERT_FALSE(builder.isTransientTransactionError());
+}
+
+TEST(ErrorLabelBuilderTest, NonTransientTransactionErrorsHaveNoTransientTransactionErrorLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ sessionInfo.setTxnNumber(1);
+ sessionInfo.setAutocommit(false);
+ std::string commandName = "commitTransaction";
+ ErrorLabelBuilder builder(sessionInfo, commandName, ErrorCodes::NotMaster, boost::none, false);
+ ASSERT_FALSE(builder.isTransientTransactionError());
+}
+
+TEST(ErrorLabelBuilderTest, TransientTransactionErrorsHaveTransientTransactionErrorLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ sessionInfo.setTxnNumber(1);
+ sessionInfo.setAutocommit(false);
+ std::string commandName = "commitTransaction";
+ ErrorLabelBuilder builder(
+ sessionInfo, commandName, ErrorCodes::NoSuchTransaction, boost::none, false);
+ ASSERT_TRUE(builder.isTransientTransactionError());
+}
+
+TEST(ErrorLabelBuilderTest, NonRetryableWritesHaveNoRetryableWriteErrorLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ std::string commandName = "insert";
+ ErrorLabelBuilder builder(sessionInfo, commandName, ErrorCodes::NotMaster, boost::none, false);
+
+ // Test regular writes.
+ ASSERT_FALSE(builder.isRetryableWriteError());
+
+ // Test transaction writes.
+ sessionInfo.setTxnNumber(1);
+ sessionInfo.setAutocommit(false);
+ ASSERT_FALSE(builder.isRetryableWriteError());
+}
+
+TEST(ErrorLabelBuilderTest, NonRetryableWriteErrorsHaveNoRetryableWriteErrorLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ sessionInfo.setTxnNumber(1);
+ std::string commandName = "update";
+ ErrorLabelBuilder builder(
+ sessionInfo, commandName, ErrorCodes::WriteConflict, boost::none, false);
+ ASSERT_FALSE(builder.isRetryableWriteError());
+}
+
+TEST(ErrorLabelBuilderTest, RetryableWriteErrorsHaveRetryableWriteErrorLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ sessionInfo.setTxnNumber(1);
+ std::string commandName = "update";
+ ErrorLabelBuilder builder(sessionInfo, commandName, ErrorCodes::NotMaster, boost::none, false);
+ ASSERT_TRUE(builder.isRetryableWriteError());
+}
+
+TEST(ErrorLabelBuilderTest, RetryableWriteErrorsHaveNoRetryableWriteErrorLabelForInternalClients) {
+ OperationSessionInfoFromClient sessionInfo;
+ sessionInfo.setTxnNumber(1);
+ std::string commandName = "update";
+ ErrorLabelBuilder builder(sessionInfo, commandName, ErrorCodes::NotMaster, boost::none, true);
+ ASSERT_FALSE(builder.isRetryableWriteError());
+}
+
+TEST(ErrorLabelBuilderTest,
+ NonRetryableWriteErrorsInWriteConcernErrorsHaveNoRetryableWriteErrorLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ sessionInfo.setTxnNumber(1);
+ std::string commandName = "update";
+ ErrorLabelBuilder builder(sessionInfo,
+ commandName,
+ ErrorCodes::WriteConcernFailed,
+ ErrorCodes::WriteConcernFailed,
+ false);
+ ASSERT_FALSE(builder.isRetryableWriteError());
+}
+
+TEST(ErrorLabelBuilderTest, RetryableWriteErrorsInWriteConcernErrorsHaveRetryableWriteErrorLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ sessionInfo.setTxnNumber(1);
+ std::string commandName = "update";
+ ErrorLabelBuilder builder(sessionInfo,
+ commandName,
+ ErrorCodes::WriteConcernFailed,
+ ErrorCodes::PrimarySteppedDown,
+ false);
+ ASSERT_TRUE(builder.isRetryableWriteError());
+}
+
+TEST(ErrorLabelBuilderTest, RetryableWriteErrorsOnCommitAbortHaveRetryableWriteErrorLabel) {
+ OperationSessionInfoFromClient sessionInfo;
+ sessionInfo.setTxnNumber(1);
+ sessionInfo.setAutocommit(false);
+ std::string commandName;
+
+ commandName = "commitTransaction";
+ ErrorLabelBuilder commitBuilder(
+ sessionInfo, commandName, ErrorCodes::NotMaster, boost::none, false);
+ ASSERT_TRUE(commitBuilder.isRetryableWriteError());
+
+ commandName = "coordinateCommitTransaction";
+ ErrorLabelBuilder coordinateCommitBuilder(
+ sessionInfo, commandName, ErrorCodes::NotMaster, boost::none, false);
+ ASSERT_TRUE(coordinateCommitBuilder.isRetryableWriteError());
+
+ commandName = "abortTransaction";
+ ErrorLabelBuilder abortBuilder(
+ sessionInfo, commandName, ErrorCodes::NotMaster, boost::none, false);
+ ASSERT_TRUE(abortBuilder.isRetryableWriteError());
+}
+
+TEST(ErrorLabelBuilderTest, NonResumableChangeStreamError) {
+ OperationSessionInfoFromClient sessionInfo;
+ std::string commandName;
+ ErrorLabelBuilder builder(
+ sessionInfo, commandName, ErrorCodes::ChangeStreamHistoryLost, boost::none, false);
+ ASSERT_TRUE(builder.isNonResumableChangeStreamError());
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/handle_request_response.cpp b/src/mongo/db/handle_request_response.cpp
deleted file mode 100644
index 6628c60e8bc..00000000000
--- a/src/mongo/db/handle_request_response.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * 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/db/handle_request_response.h"
-#include "mongo/base/transaction_error.h"
-
-namespace mongo {
-
-BSONObj getErrorLabels(const OperationSessionInfoFromClient& sessionOptions,
- const std::string& commandName,
- ErrorCodes::Error code,
- bool hasWriteConcernError) {
- BSONArrayBuilder labelArray;
-
- // Note that we only apply the TransientTxnError label if the "autocommit" field is present in
- // the session options. When present, "autocommit" will always be false, so we don't check its
- // value.
- if (sessionOptions.getAutocommit() &&
- isTransientTransactionError(code,
- hasWriteConcernError,
- commandName == "commitTransaction" ||
- commandName == "coordinateCommitTransaction")) {
- // An error code for which isTransientTransactionError() is true indicates a transaction
- // failure with no persistent side effects.
- labelArray << txn::TransientTxnErrorFieldName;
- }
-
- if (ErrorCodes::isNonResumableChangeStreamError(code)) {
- labelArray << "NonResumableChangeStreamError";
- }
-
- return (labelArray.arrSize() > 0) ? BSON("errorLabels" << labelArray.arr()) : BSONObj();
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/write_ops_exec.cpp b/src/mongo/db/ops/write_ops_exec.cpp
index 3f22f0fd379..0876a5598df 100644
--- a/src/mongo/db/ops/write_ops_exec.cpp
+++ b/src/mongo/db/ops/write_ops_exec.cpp
@@ -34,7 +34,6 @@
#include <memory>
#include "mongo/base/checked_cast.h"
-#include "mongo/base/transaction_error.h"
#include "mongo/db/audit.h"
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/catalog/collection.h"
@@ -46,6 +45,7 @@
#include "mongo/db/concurrency/write_conflict_exception.h"
#include "mongo/db/curop_failpoint_helpers.h"
#include "mongo/db/curop_metrics.h"
+#include "mongo/db/error_labels.h"
#include "mongo/db/exec/delete.h"
#include "mongo/db/exec/update_stage.h"
#include "mongo/db/introspect.h"
@@ -248,7 +248,7 @@ bool handleError(OperationContext* opCtx,
auto txnParticipant = TransactionParticipant::get(opCtx);
if (txnParticipant && opCtx->inMultiDocumentTransaction()) {
if (isTransientTransactionError(
- ex.code(), false /* hasWriteConcernError */, false /* isCommitTransaction */)) {
+ ex.code(), false /* hasWriteConcernError */, false /* isCommitOrAbort */)) {
// Tell the client to try the whole txn again, by returning ok: 0 with errorLabels.
throw;
}
diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp
index 6fb5557d3d4..c64241e5bff 100644
--- a/src/mongo/db/service_entry_point_common.cpp
+++ b/src/mongo/db/service_entry_point_common.cpp
@@ -49,7 +49,7 @@
#include "mongo/db/curop_metrics.h"
#include "mongo/db/cursor_manager.h"
#include "mongo/db/dbdirectclient.h"
-#include "mongo/db/handle_request_response.h"
+#include "mongo/db/error_labels.h"
#include "mongo/db/initialize_operation_session_info.h"
#include "mongo/db/introspect.h"
#include "mongo/db/jsobj.h"
@@ -706,17 +706,22 @@ bool runCommandImpl(OperationContext* opCtx,
}();
behaviors.attachCurOpErrInfo(opCtx, replyBuilder->getBodyBuilder().asTempObj());
- if (!ok) {
+ {
+ boost::optional<ErrorCodes::Error> wcCode;
+ boost::optional<ErrorCodes::Error> code;
auto response = replyBuilder->getBodyBuilder().asTempObj();
auto codeField = response["code"];
- const auto hasWriteConcern = response.hasField("writeConcernError");
- if (codeField.isNumber()) {
- auto code = ErrorCodes::Error(codeField.numberInt());
- // Append the error labels for transient transaction errors.
- auto errorLabels =
- getErrorLabels(sessionOptions, command->getName(), code, hasWriteConcern);
- replyBuilder->getBodyBuilder().appendElements(errorLabels);
+ if (!ok && codeField.isNumber()) {
+ code = ErrorCodes::Error(codeField.numberInt());
+ }
+ if (response.hasField("writeConcernError")) {
+ wcCode = ErrorCodes::Error(response["writeConcernError"]["code"].numberInt());
}
+ auto isInternalClient = opCtx->getClient()->session() &&
+ (opCtx->getClient()->session()->getTags() & transport::Session::kInternalClient);
+ auto errorLabels =
+ getErrorLabels(sessionOptions, command->getName(), code, wcCode, isInternalClient);
+ replyBuilder->getBodyBuilder().appendElements(errorLabels);
}
auto commandBodyBob = replyBuilder->getBodyBuilder();
@@ -1013,9 +1018,14 @@ void execCommandDatabase(OperationContext* opCtx,
// Append the error labels for transient transaction errors.
auto response = extraFieldsBuilder.asTempObj();
- auto hasWriteConcern = response.hasField("writeConcernError");
+ boost::optional<ErrorCodes::Error> wcCode;
+ if (response.hasField("writeConcernError")) {
+ wcCode = ErrorCodes::Error(response["writeConcernError"]["code"].numberInt());
+ }
+ auto isInternalClient = opCtx->getClient()->session() &&
+ (opCtx->getClient()->session()->getTags() & transport::Session::kInternalClient);
auto errorLabels =
- getErrorLabels(sessionOptions, command->getName(), e.code(), hasWriteConcern);
+ getErrorLabels(sessionOptions, command->getName(), e.code(), wcCode, isInternalClient);
extraFieldsBuilder.appendElements(errorLabels);
BSONObjBuilder metadataBob;