summaryrefslogtreecommitdiff
path: root/src/mongo/db/error_labels.cpp
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/error_labels.cpp
parentad581efda69010443fac7f964180666476b99785 (diff)
downloadmongo-0ff5e5e7cc09c31d3fe260cf6602f461e6e20bb6.tar.gz
SERVER-41245: Add RetryableWriteError error label
Diffstat (limited to 'src/mongo/db/error_labels.cpp')
-rw-r--r--src/mongo/db/error_labels.cpp139
1 files changed, 139 insertions, 0 deletions
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