summaryrefslogtreecommitdiff
path: root/src/mongo/db/concurrency
diff options
context:
space:
mode:
authorAmirsaman Memaripour <amirsaman.memaripour@mongodb.com>2022-04-28 15:15:19 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-04-28 17:35:39 +0000
commit4fee73e53ecdbfff73d644dd743b66d5e16a1836 (patch)
tree296c1b4e97d71f6f07e48693ec0be31a4fc96fb5 /src/mongo/db/concurrency
parent2255f824d44caf48f9c8b0e23ffaf8483f4e0afe (diff)
downloadmongo-4fee73e53ecdbfff73d644dd743b66d5e16a1836.tar.gz
SERVER-54284 ExceptionFor<ErrorCodes::WriteConflict> should resolve to WriteConflictException
Diffstat (limited to 'src/mongo/db/concurrency')
-rw-r--r--src/mongo/db/concurrency/SConscript11
-rw-r--r--src/mongo/db/concurrency/d_concurrency_test.cpp2
-rw-r--r--src/mongo/db/concurrency/deferred_writer.cpp2
-rw-r--r--src/mongo/db/concurrency/exception_util.cpp (renamed from src/mongo/db/concurrency/temporarily_unavailable_exception.cpp)47
-rw-r--r--src/mongo/db/concurrency/exception_util.h118
-rw-r--r--src/mongo/db/concurrency/exception_util.idl (renamed from src/mongo/db/concurrency/write_conflict_exception.idl)4
-rw-r--r--src/mongo/db/concurrency/temporarily_unavailable_exception.h31
-rw-r--r--src/mongo/db/concurrency/write_conflict_exception.cpp26
-rw-r--r--src/mongo/db/concurrency/write_conflict_exception.h86
9 files changed, 178 insertions, 149 deletions
diff --git a/src/mongo/db/concurrency/SConscript b/src/mongo/db/concurrency/SConscript
index 848de10e74e..916d353bf05 100644
--- a/src/mongo/db/concurrency/SConscript
+++ b/src/mongo/db/concurrency/SConscript
@@ -15,18 +15,17 @@ env.Library(
],
LIBDEPS_PRIVATE=[
'$BUILD_DIR/mongo/db/catalog/catalog_helpers',
+ '$BUILD_DIR/mongo/db/concurrency/exception_util',
'$BUILD_DIR/mongo/db/db_raii',
'$BUILD_DIR/mongo/util/concurrency/thread_pool',
- 'write_conflict_exception',
],
)
env.Library(
- target='write_conflict_exception',
+ target='exception_util',
source=[
- 'temporarily_unavailable_exception.cpp',
- 'write_conflict_exception.cpp',
- 'write_conflict_exception.idl',
+ 'exception_util.cpp',
+ 'exception_util.idl',
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
@@ -111,7 +110,7 @@ env.CppUnitTest(
'$BUILD_DIR/mongo/transport/transport_layer_common',
'$BUILD_DIR/mongo/transport/transport_layer_mock',
'$BUILD_DIR/mongo/util/progress_meter',
+ 'exception_util',
'lock_manager',
- 'write_conflict_exception',
]
)
diff --git a/src/mongo/db/concurrency/d_concurrency_test.cpp b/src/mongo/db/concurrency/d_concurrency_test.cpp
index 233d1dd1842..eda8f50423b 100644
--- a/src/mongo/db/concurrency/d_concurrency_test.cpp
+++ b/src/mongo/db/concurrency/d_concurrency_test.cpp
@@ -38,9 +38,9 @@
#include <vector>
#include "mongo/db/concurrency/d_concurrency.h"
+#include "mongo/db/concurrency/exception_util.h"
#include "mongo/db/concurrency/lock_manager_test_help.h"
#include "mongo/db/concurrency/replication_state_transition_lock_guard.h"
-#include "mongo/db/concurrency/write_conflict_exception.h"
#include "mongo/db/service_context_d_test_fixture.h"
#include "mongo/db/storage/recovery_unit_noop.h"
#include "mongo/db/storage/ticketholders.h"
diff --git a/src/mongo/db/concurrency/deferred_writer.cpp b/src/mongo/db/concurrency/deferred_writer.cpp
index 9c6abe5ff47..7a7577ee8e5 100644
--- a/src/mongo/db/concurrency/deferred_writer.cpp
+++ b/src/mongo/db/concurrency/deferred_writer.cpp
@@ -32,7 +32,7 @@
#include "mongo/db/concurrency/deferred_writer.h"
#include "mongo/db/catalog/create_collection.h"
#include "mongo/db/client.h"
-#include "mongo/db/concurrency/write_conflict_exception.h"
+#include "mongo/db/concurrency/exception_util.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/operation_context.h"
#include "mongo/logv2/log.h"
diff --git a/src/mongo/db/concurrency/temporarily_unavailable_exception.cpp b/src/mongo/db/concurrency/exception_util.cpp
index fee20c4d268..880291b7da8 100644
--- a/src/mongo/db/concurrency/temporarily_unavailable_exception.cpp
+++ b/src/mongo/db/concurrency/exception_util.cpp
@@ -29,19 +29,30 @@
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kControl
-#include "mongo/db/concurrency/temporarily_unavailable_exception.h"
-#include "mongo/base/string_data.h"
+#include "mongo/db/concurrency/exception_util.h"
+
+#include "mongo/base/counter.h"
#include "mongo/db/commands/server_status_metric.h"
-#include "mongo/db/concurrency/write_conflict_exception.h"
-#include "mongo/db/server_options_general_gen.h"
+#include "mongo/db/namespace_string.h"
#include "mongo/logv2/log.h"
#include "mongo/util/duration.h"
+#include "mongo/util/log_and_backoff.h"
namespace mongo {
-// These are initialized by IDL as server parameters.
-AtomicWord<long long> TemporarilyUnavailableException::maxRetryAttempts;
-AtomicWord<long long> TemporarilyUnavailableException::retryBackoffBaseMs;
+MONGO_FAIL_POINT_DEFINE(skipWriteConflictRetries);
+
+void logWriteConflictAndBackoff(int attempt, StringData operation, StringData ns) {
+ logAndBackoff(4640401,
+ logv2::LogComponent::kWrite,
+ logv2::LogSeverity::Debug(1),
+ static_cast<size_t>(attempt),
+ "Caught WriteConflictException",
+ "operation"_attr = operation,
+ logAttrs(NamespaceString(ns)));
+}
+
+namespace {
Counter64 temporarilyUnavailableErrors;
Counter64 temporarilyUnavailableErrorsEscaped;
@@ -55,14 +66,13 @@ ServerStatusMetricField<Counter64> displayTemporarilyUnavailableErrorsConverted(
"operation.temporarilyUnavailableErrorsConvertedToWriteConflict",
&temporarilyUnavailableErrorsConvertedToWriteConflict);
-TemporarilyUnavailableException::TemporarilyUnavailableException(StringData context)
- : DBException(Status(ErrorCodes::TemporarilyUnavailable, context)) {}
+} // namespace
-void TemporarilyUnavailableException::handle(OperationContext* opCtx,
- int attempts,
- StringData opStr,
- StringData ns,
- const TemporarilyUnavailableException& e) {
+void handleTemporarilyUnavailableException(OperationContext* opCtx,
+ int attempts,
+ StringData opStr,
+ StringData ns,
+ const TemporarilyUnavailableException& e) {
opCtx->recoveryUnit()->abandonSnapshot();
temporarilyUnavailableErrors.increment(1);
if (opCtx->getClient()->isFromUserConnection() &&
@@ -92,11 +102,10 @@ void TemporarilyUnavailableException::handle(OperationContext* opCtx,
opCtx->sleepFor(sleepFor);
}
-void TemporarilyUnavailableException::handleInTransaction(
- OperationContext* opCtx,
- StringData opStr,
- StringData ns,
- const TemporarilyUnavailableException& e) {
+void handleTemporarilyUnavailableExceptionInTransaction(OperationContext* opCtx,
+ StringData opStr,
+ StringData ns,
+ const TemporarilyUnavailableException& e) {
// Since WriteConflicts are tagged as TransientTransactionErrors and TemporarilyUnavailable
// errors are not, we convert the error to a WriteConflict to allow users of multi-document
// transactions to retry without changing any behavior. Otherwise, we let the error escape as
diff --git a/src/mongo/db/concurrency/exception_util.h b/src/mongo/db/concurrency/exception_util.h
new file mode 100644
index 00000000000..718ee08339f
--- /dev/null
+++ b/src/mongo/db/concurrency/exception_util.h
@@ -0,0 +1,118 @@
+/**
+ * Copyright (C) 2022-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.
+ */
+
+#pragma once
+
+#include "mongo/base/string_data.h"
+#include "mongo/db/concurrency/temporarily_unavailable_exception.h"
+#include "mongo/db/concurrency/write_conflict_exception.h"
+#include "mongo/db/curop.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/fail_point.h"
+
+namespace mongo {
+
+extern FailPoint skipWriteConflictRetries;
+
+/**
+ * Will log a message if sensible and will do an exponential backoff to make sure
+ * we don't hammer the same doc over and over.
+ * @param attempt - what attempt is this, 1 based
+ * @param operation - e.g. "update"
+ */
+void logWriteConflictAndBackoff(int attempt, StringData operation, StringData ns);
+
+void handleTemporarilyUnavailableException(OperationContext* opCtx,
+ int attempts,
+ StringData opStr,
+ StringData ns,
+ const TemporarilyUnavailableException& e);
+
+/**
+ * Handle a TemporarilyUnavailableException inside a multi-document transaction.
+ */
+void handleTemporarilyUnavailableExceptionInTransaction(OperationContext* opCtx,
+ StringData opStr,
+ StringData ns,
+ const TemporarilyUnavailableException& e);
+
+/**
+ * Runs the argument function f as many times as needed for f to complete or throw an exception
+ * other than WriteConflictException or TemporarilyUnavailableException. For each time f throws an
+ * one of these exceptions, logs the error, waits a spell, cleans up, and then tries f again.
+ * Imposes no upper limit on the number of times to re-try f after a WriteConflictException, so any
+ * required timeout behavior must be enforced within f. When retrying a
+ * TemporarilyUnavailableException, f is called a finite number of times before we eventually let
+ * the error escape.
+ *
+ * If we are already in a WriteUnitOfWork, we assume that we are being called within a
+ * WriteConflictException retry loop up the call stack. Hence, this retry loop is reduced to an
+ * invocation of the argument function f without any exception handling and retry logic.
+ */
+template <typename F>
+auto writeConflictRetry(OperationContext* opCtx, StringData opStr, StringData ns, F&& f) {
+ invariant(opCtx);
+ invariant(opCtx->lockState());
+ invariant(opCtx->recoveryUnit());
+
+ // This failpoint disables exception handling for write conflicts. Only allow this exception to
+ // escape user operations. Do not allow exceptions to escape internal threads, which may rely on
+ // this exception handler to avoid crashing.
+ bool userSkipWriteConflictRetry = MONGO_unlikely(skipWriteConflictRetries.shouldFail()) &&
+ opCtx->getClient()->isFromUserConnection();
+ if (opCtx->lockState()->inAWriteUnitOfWork() || userSkipWriteConflictRetry) {
+ try {
+ return f();
+ } catch (TemporarilyUnavailableException const& e) {
+ if (opCtx->inMultiDocumentTransaction()) {
+ handleTemporarilyUnavailableExceptionInTransaction(opCtx, opStr, ns, e);
+ }
+ throw;
+ }
+ }
+
+ int attempts = 0;
+ int attemptsTempUnavailable = 0;
+ while (true) {
+ try {
+ return f();
+ } catch (WriteConflictException const&) {
+ CurOp::get(opCtx)->debug().additiveMetrics.incrementWriteConflicts(1);
+ logWriteConflictAndBackoff(attempts, opStr, ns);
+ ++attempts;
+ opCtx->recoveryUnit()->abandonSnapshot();
+ } catch (TemporarilyUnavailableException const& e) {
+ CurOp::get(opCtx)->debug().additiveMetrics.incrementTemporarilyUnavailableErrors(1);
+ handleTemporarilyUnavailableException(opCtx, ++attemptsTempUnavailable, opStr, ns, e);
+ }
+ }
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/concurrency/write_conflict_exception.idl b/src/mongo/db/concurrency/exception_util.idl
index fe2206cec67..200ce5c75a1 100644
--- a/src/mongo/db/concurrency/write_conflict_exception.idl
+++ b/src/mongo/db/concurrency/exception_util.idl
@@ -28,7 +28,9 @@
global:
cpp_namespace: "mongo"
- cpp_includes: "mongo/db/concurrency/write_conflict_exception.h"
+ cpp_includes:
+ - "mongo/db/concurrency/temporarily_unavailable_exception.h"
+ - "mongo/db/concurrency/write_conflict_exception.h"
server_parameters:
traceWriteConflictExceptions:
diff --git a/src/mongo/db/concurrency/temporarily_unavailable_exception.h b/src/mongo/db/concurrency/temporarily_unavailable_exception.h
index cffd4f518e4..d58f743b04f 100644
--- a/src/mongo/db/concurrency/temporarily_unavailable_exception.h
+++ b/src/mongo/db/concurrency/temporarily_unavailable_exception.h
@@ -29,10 +29,10 @@
#pragma once
-#include <exception>
-
+#include "mongo/base/error_codes.h"
#include "mongo/base/string_data.h"
-#include "mongo/db/operation_context.h"
+#include "mongo/platform/atomic_word.h"
+#include "mongo/util/assert_util.h"
namespace mongo {
@@ -44,27 +44,14 @@ namespace mongo {
*/
class TemporarilyUnavailableException final : public DBException {
public:
- static AtomicWord<long long> maxRetryAttempts;
- static AtomicWord<long long> retryBackoffBaseMs;
-
- TemporarilyUnavailableException(StringData context);
+ // These are initialized by IDL as server parameters.
+ static inline AtomicWord<long long> maxRetryAttempts;
+ static inline AtomicWord<long long> retryBackoffBaseMs;
- /**
- * Handle a TemporarilyUnavailableException.
- */
- static void handle(OperationContext* opCtx,
- int attempts,
- StringData opStr,
- StringData ns,
- const TemporarilyUnavailableException& e);
+ TemporarilyUnavailableException(StringData context)
+ : DBException({ErrorCodes::TemporarilyUnavailable, context}) {}
- /**
- * Handle a TemporarilyUnavailableException inside a multi-document transaction.
- */
- static void handleInTransaction(OperationContext* opCtx,
- StringData opStr,
- StringData ns,
- const TemporarilyUnavailableException& e);
+ TemporarilyUnavailableException(const Status& status) : DBException(status) {}
private:
void defineOnlyInFinalSubclassToPreventSlicing() final {}
diff --git a/src/mongo/db/concurrency/write_conflict_exception.cpp b/src/mongo/db/concurrency/write_conflict_exception.cpp
index f57ed9282bc..d5d40dbc4da 100644
--- a/src/mongo/db/concurrency/write_conflict_exception.cpp
+++ b/src/mongo/db/concurrency/write_conflict_exception.cpp
@@ -27,18 +27,13 @@
* it in the license file.
*/
-#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kWrite
-
#include "mongo/db/concurrency/write_conflict_exception.h"
-#include "mongo/util/log_and_backoff.h"
+
+#include "mongo/base/error_codes.h"
#include "mongo/util/stacktrace.h"
namespace mongo {
-MONGO_FAIL_POINT_DEFINE(skipWriteConflictRetries);
-
-AtomicWord<bool> WriteConflictException::trace(false);
-
WriteConflictException::WriteConflictException()
: DBException(Status(ErrorCodes::WriteConflict,
"WriteConflict error: this operation conflicted with another operation. "
@@ -48,21 +43,4 @@ WriteConflictException::WriteConflictException()
}
}
-WriteConflictException::WriteConflictException(StringData context) : WriteConflictException() {
- // Avoid unnecessary update to embedded Status within DBException.
- if (context.empty()) {
- return;
- }
- addContext(context);
-}
-
-void WriteConflictException::logAndBackoff(int attempt, StringData operation, StringData ns) {
- mongo::logAndBackoff(4640401,
- ::mongo::logv2::LogComponent::kWrite,
- logv2::LogSeverity::Debug(1),
- static_cast<size_t>(attempt),
- "Caught WriteConflictException",
- "operation"_attr = operation,
- logAttrs(NamespaceString(ns)));
-}
} // namespace mongo
diff --git a/src/mongo/db/concurrency/write_conflict_exception.h b/src/mongo/db/concurrency/write_conflict_exception.h
index 892507e21a0..bc95825a131 100644
--- a/src/mongo/db/concurrency/write_conflict_exception.h
+++ b/src/mongo/db/concurrency/write_conflict_exception.h
@@ -29,18 +29,12 @@
#pragma once
-#include <exception>
-
#include "mongo/base/string_data.h"
-#include "mongo/db/concurrency/temporarily_unavailable_exception.h"
-#include "mongo/db/curop.h"
+#include "mongo/platform/atomic_word.h"
#include "mongo/util/assert_util.h"
-#include "mongo/util/fail_point.h"
namespace mongo {
-extern FailPoint skipWriteConflictRetries;
-
/**
* This is thrown if during a write, two or more operations conflict with each other.
* For example if two operations get the same version of a document, and then both try to
@@ -49,83 +43,25 @@ extern FailPoint skipWriteConflictRetries;
class WriteConflictException final : public DBException {
public:
WriteConflictException();
- WriteConflictException(StringData context);
- /**
- * Will log a message if sensible and will do an exponential backoff to make sure
- * we don't hammer the same doc over and over.
- * @param attempt - what attempt is this, 1 based
- * @param operation - e.g. "update"
- */
- static void logAndBackoff(int attempt, StringData operation, StringData ns);
+ WriteConflictException(StringData context) : WriteConflictException() {
+ // Avoid unnecessary update to embedded Status within DBException.
+ if (context.empty()) {
+ return;
+ }
+ addContext(context);
+ }
+
+ WriteConflictException(const Status& status) : DBException(status) {}
/**
* If true, will call printStackTrace on every WriteConflictException created.
* Can be set via setParameter named traceWriteConflictExceptions.
*/
- static AtomicWord<bool> trace;
+ static inline AtomicWord<bool> trace{false};
private:
void defineOnlyInFinalSubclassToPreventSlicing() final {}
};
-/**
- * Runs the argument function f as many times as needed for f to complete or throw an exception
- * other than WriteConflictException or TemporarilyUnavailableException. For each time f throws an
- * one of these exceptions, logs the error, waits a spell, cleans up, and then tries f again.
- * Imposes no upper limit on the number of times to re-try f after a WriteConflictException, so any
- * required timeout behavior must be enforced within f. When retrying a
- * TemporarilyUnavailableException, f is called a finite number of times before we eventually let
- * the error escape.
- *
- * If we are already in a WriteUnitOfWork, we assume that we are being called within a
- * WriteConflictException retry loop up the call stack. Hence, this retry loop is reduced to an
- * invocation of the argument function f without any exception handling and retry logic.
- */
-template <typename F>
-auto writeConflictRetry(OperationContext* opCtx, StringData opStr, StringData ns, F&& f) {
- invariant(opCtx);
- invariant(opCtx->lockState());
- invariant(opCtx->recoveryUnit());
-
- // This failpoint disables exception handling for write conflicts. Only allow this exception to
- // escape user operations. Do not allow exceptions to escape internal threads, which may rely on
- // this exception handler to avoid crashing.
- bool userSkipWriteConflictRetry = MONGO_unlikely(skipWriteConflictRetries.shouldFail()) &&
- opCtx->getClient()->isFromUserConnection();
- if (opCtx->lockState()->inAWriteUnitOfWork() || userSkipWriteConflictRetry) {
- try {
- return f();
- } catch (TemporarilyUnavailableException const& e) {
- if (opCtx->inMultiDocumentTransaction()) {
- TemporarilyUnavailableException::handleInTransaction(opCtx, opStr, ns, e);
- }
- throw;
- }
- }
-
- int attempts = 0;
- int attemptsTempUnavailable = 0;
- // TODO (SERVER-54284) Remove redundant catch for WriteConflictExpection and
- // ExceptionFor<ErrorCodes::WriteConflict>
- while (true) {
- try {
- return f();
- } catch (WriteConflictException const&) {
- CurOp::get(opCtx)->debug().additiveMetrics.incrementWriteConflicts(1);
- WriteConflictException::logAndBackoff(attempts, opStr, ns);
- ++attempts;
- opCtx->recoveryUnit()->abandonSnapshot();
- } catch (ExceptionFor<ErrorCodes::WriteConflict> const&) {
- CurOp::get(opCtx)->debug().additiveMetrics.incrementWriteConflicts(1);
- WriteConflictException::logAndBackoff(attempts, opStr, ns);
- ++attempts;
- opCtx->recoveryUnit()->abandonSnapshot();
- } catch (TemporarilyUnavailableException const& e) {
- CurOp::get(opCtx)->debug().additiveMetrics.incrementTemporarilyUnavailableErrors(1);
- TemporarilyUnavailableException::handle(opCtx, ++attemptsTempUnavailable, opStr, ns, e);
- }
- }
-}
-
} // namespace mongo