diff options
author | Amirsaman Memaripour <amirsaman.memaripour@mongodb.com> | 2022-04-28 15:15:19 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-04-28 17:35:39 +0000 |
commit | 4fee73e53ecdbfff73d644dd743b66d5e16a1836 (patch) | |
tree | 296c1b4e97d71f6f07e48693ec0be31a4fc96fb5 /src/mongo/db/concurrency | |
parent | 2255f824d44caf48f9c8b0e23ffaf8483f4e0afe (diff) | |
download | mongo-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/SConscript | 11 | ||||
-rw-r--r-- | src/mongo/db/concurrency/d_concurrency_test.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/concurrency/deferred_writer.cpp | 2 | ||||
-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.h | 118 | ||||
-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.h | 31 | ||||
-rw-r--r-- | src/mongo/db/concurrency/write_conflict_exception.cpp | 26 | ||||
-rw-r--r-- | src/mongo/db/concurrency/write_conflict_exception.h | 86 |
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 |