From 4fee73e53ecdbfff73d644dd743b66d5e16a1836 Mon Sep 17 00:00:00 2001 From: Amirsaman Memaripour Date: Thu, 28 Apr 2022 15:15:19 +0000 Subject: SERVER-54284 ExceptionFor should resolve to WriteConflictException --- src/mongo/db/concurrency/SConscript | 11 +- src/mongo/db/concurrency/d_concurrency_test.cpp | 2 +- src/mongo/db/concurrency/deferred_writer.cpp | 2 +- src/mongo/db/concurrency/exception_util.cpp | 117 ++++++++++++++++++++ src/mongo/db/concurrency/exception_util.h | 118 +++++++++++++++++++++ src/mongo/db/concurrency/exception_util.idl | 65 ++++++++++++ .../temporarily_unavailable_exception.cpp | 108 ------------------- .../temporarily_unavailable_exception.h | 31 ++---- .../db/concurrency/write_conflict_exception.cpp | 26 +---- .../db/concurrency/write_conflict_exception.h | 86 ++------------- .../db/concurrency/write_conflict_exception.idl | 63 ----------- 11 files changed, 329 insertions(+), 300 deletions(-) create mode 100644 src/mongo/db/concurrency/exception_util.cpp create mode 100644 src/mongo/db/concurrency/exception_util.h create mode 100644 src/mongo/db/concurrency/exception_util.idl delete mode 100644 src/mongo/db/concurrency/temporarily_unavailable_exception.cpp delete mode 100644 src/mongo/db/concurrency/write_conflict_exception.idl (limited to 'src/mongo/db/concurrency') 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 #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/exception_util.cpp b/src/mongo/db/concurrency/exception_util.cpp new file mode 100644 index 00000000000..880291b7da8 --- /dev/null +++ b/src/mongo/db/concurrency/exception_util.cpp @@ -0,0 +1,117 @@ +/** + * 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 + * . + * + * 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. + */ + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kControl + +#include "mongo/db/concurrency/exception_util.h" + +#include "mongo/base/counter.h" +#include "mongo/db/commands/server_status_metric.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 { + +MONGO_FAIL_POINT_DEFINE(skipWriteConflictRetries); + +void logWriteConflictAndBackoff(int attempt, StringData operation, StringData ns) { + logAndBackoff(4640401, + logv2::LogComponent::kWrite, + logv2::LogSeverity::Debug(1), + static_cast(attempt), + "Caught WriteConflictException", + "operation"_attr = operation, + logAttrs(NamespaceString(ns))); +} + +namespace { + +Counter64 temporarilyUnavailableErrors; +Counter64 temporarilyUnavailableErrorsEscaped; +Counter64 temporarilyUnavailableErrorsConvertedToWriteConflict; + +ServerStatusMetricField displayTemporarilyUnavailableErrors( + "operation.temporarilyUnavailableErrors", &temporarilyUnavailableErrors); +ServerStatusMetricField displayTemporarilyUnavailableErrorsEscaped( + "operation.temporarilyUnavailableErrorsEscaped", &temporarilyUnavailableErrorsEscaped); +ServerStatusMetricField displayTemporarilyUnavailableErrorsConverted( + "operation.temporarilyUnavailableErrorsConvertedToWriteConflict", + &temporarilyUnavailableErrorsConvertedToWriteConflict); + +} // namespace + +void handleTemporarilyUnavailableException(OperationContext* opCtx, + int attempts, + StringData opStr, + StringData ns, + const TemporarilyUnavailableException& e) { + opCtx->recoveryUnit()->abandonSnapshot(); + temporarilyUnavailableErrors.increment(1); + if (opCtx->getClient()->isFromUserConnection() && + attempts > TemporarilyUnavailableException::maxRetryAttempts.load()) { + LOGV2_DEBUG(6083901, + 1, + "Too many TemporarilyUnavailableException's, giving up", + "reason"_attr = e.reason(), + "attempts"_attr = attempts, + "operation"_attr = opStr, + logAttrs(NamespaceString(ns))); + temporarilyUnavailableErrorsEscaped.increment(1); + throw e; + } + + // Back off linearly with the retry attempt number. + auto sleepFor = + Milliseconds(TemporarilyUnavailableException::retryBackoffBaseMs.load()) * attempts; + LOGV2_DEBUG(6083900, + 1, + "Caught TemporarilyUnavailableException", + "reason"_attr = e.reason(), + "attempts"_attr = attempts, + "operation"_attr = opStr, + "sleepFor"_attr = sleepFor, + logAttrs(NamespaceString(ns))); + opCtx->sleepFor(sleepFor); +} + +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 + // usual. + temporarilyUnavailableErrorsConvertedToWriteConflict.increment(1); + throw WriteConflictException(e.reason()); +} + +} // namespace mongo 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 + * . + * + * 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 +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/exception_util.idl b/src/mongo/db/concurrency/exception_util.idl new file mode 100644 index 00000000000..200ce5c75a1 --- /dev/null +++ b/src/mongo/db/concurrency/exception_util.idl @@ -0,0 +1,65 @@ +# 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 +# . +# +# 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. +# + +global: + cpp_namespace: "mongo" + cpp_includes: + - "mongo/db/concurrency/temporarily_unavailable_exception.h" + - "mongo/db/concurrency/write_conflict_exception.h" + +server_parameters: + traceWriteConflictExceptions: + description: 'Call printStackTrace on every WriteConflictException created' + set_at: [ startup, runtime ] + cpp_varname: 'WriteConflictException::trace' + + enableTemporarilyUnavailableExceptions: + description: 'Enables the use of TemporarilyUnavailableExceptions. When disabled, reverts to + throwing WriteConflictException.' + set_at: [ startup, runtime ] + cpp_varname: 'gEnableTemporarilyUnavailableExceptions' + cpp_vartype: AtomicWord + default: false + + temporarilyUnavailableMaxRetries: + description: 'The number of times to retry a TemporarilyUnavailable error internally' + set_at: [ startup, runtime ] + cpp_varname: 'TemporarilyUnavailableException::maxRetryAttempts' + default: 10 + validator: + gte: 0 + + temporarilyUnavailableBackoffBaseMs: + description: 'The base period of time to wait between each TemporarilyUnavailable retry + attempt. The backoff time is linear such that the Nth retry waits for N times + the base backoff period.' + set_at: [ startup, runtime ] + cpp_varname: 'TemporarilyUnavailableException::retryBackoffBaseMs' + default: 1000 + validator: + gte: 0 diff --git a/src/mongo/db/concurrency/temporarily_unavailable_exception.cpp b/src/mongo/db/concurrency/temporarily_unavailable_exception.cpp deleted file mode 100644 index fee20c4d268..00000000000 --- a/src/mongo/db/concurrency/temporarily_unavailable_exception.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/** - * 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 - * . - * - * 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. - */ - -#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/commands/server_status_metric.h" -#include "mongo/db/concurrency/write_conflict_exception.h" -#include "mongo/db/server_options_general_gen.h" -#include "mongo/logv2/log.h" -#include "mongo/util/duration.h" - -namespace mongo { - -// These are initialized by IDL as server parameters. -AtomicWord TemporarilyUnavailableException::maxRetryAttempts; -AtomicWord TemporarilyUnavailableException::retryBackoffBaseMs; - -Counter64 temporarilyUnavailableErrors; -Counter64 temporarilyUnavailableErrorsEscaped; -Counter64 temporarilyUnavailableErrorsConvertedToWriteConflict; - -ServerStatusMetricField displayTemporarilyUnavailableErrors( - "operation.temporarilyUnavailableErrors", &temporarilyUnavailableErrors); -ServerStatusMetricField displayTemporarilyUnavailableErrorsEscaped( - "operation.temporarilyUnavailableErrorsEscaped", &temporarilyUnavailableErrorsEscaped); -ServerStatusMetricField displayTemporarilyUnavailableErrorsConverted( - "operation.temporarilyUnavailableErrorsConvertedToWriteConflict", - &temporarilyUnavailableErrorsConvertedToWriteConflict); - -TemporarilyUnavailableException::TemporarilyUnavailableException(StringData context) - : DBException(Status(ErrorCodes::TemporarilyUnavailable, context)) {} - -void TemporarilyUnavailableException::handle(OperationContext* opCtx, - int attempts, - StringData opStr, - StringData ns, - const TemporarilyUnavailableException& e) { - opCtx->recoveryUnit()->abandonSnapshot(); - temporarilyUnavailableErrors.increment(1); - if (opCtx->getClient()->isFromUserConnection() && - attempts > TemporarilyUnavailableException::maxRetryAttempts.load()) { - LOGV2_DEBUG(6083901, - 1, - "Too many TemporarilyUnavailableException's, giving up", - "reason"_attr = e.reason(), - "attempts"_attr = attempts, - "operation"_attr = opStr, - logAttrs(NamespaceString(ns))); - temporarilyUnavailableErrorsEscaped.increment(1); - throw e; - } - - // Back off linearly with the retry attempt number. - auto sleepFor = - Milliseconds(TemporarilyUnavailableException::retryBackoffBaseMs.load()) * attempts; - LOGV2_DEBUG(6083900, - 1, - "Caught TemporarilyUnavailableException", - "reason"_attr = e.reason(), - "attempts"_attr = attempts, - "operation"_attr = opStr, - "sleepFor"_attr = sleepFor, - logAttrs(NamespaceString(ns))); - opCtx->sleepFor(sleepFor); -} - -void TemporarilyUnavailableException::handleInTransaction( - 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 - // usual. - temporarilyUnavailableErrorsConvertedToWriteConflict.increment(1); - throw WriteConflictException(e.reason()); -} - -} // namespace mongo 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 - +#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 maxRetryAttempts; - static AtomicWord retryBackoffBaseMs; - - TemporarilyUnavailableException(StringData context); + // These are initialized by IDL as server parameters. + static inline AtomicWord maxRetryAttempts; + static inline AtomicWord 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 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(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 - #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 trace; + static inline AtomicWord 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 -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 - 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 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 diff --git a/src/mongo/db/concurrency/write_conflict_exception.idl b/src/mongo/db/concurrency/write_conflict_exception.idl deleted file mode 100644 index fe2206cec67..00000000000 --- a/src/mongo/db/concurrency/write_conflict_exception.idl +++ /dev/null @@ -1,63 +0,0 @@ -# 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 -# . -# -# 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. -# - -global: - cpp_namespace: "mongo" - cpp_includes: "mongo/db/concurrency/write_conflict_exception.h" - -server_parameters: - traceWriteConflictExceptions: - description: 'Call printStackTrace on every WriteConflictException created' - set_at: [ startup, runtime ] - cpp_varname: 'WriteConflictException::trace' - - enableTemporarilyUnavailableExceptions: - description: 'Enables the use of TemporarilyUnavailableExceptions. When disabled, reverts to - throwing WriteConflictException.' - set_at: [ startup, runtime ] - cpp_varname: 'gEnableTemporarilyUnavailableExceptions' - cpp_vartype: AtomicWord - default: false - - temporarilyUnavailableMaxRetries: - description: 'The number of times to retry a TemporarilyUnavailable error internally' - set_at: [ startup, runtime ] - cpp_varname: 'TemporarilyUnavailableException::maxRetryAttempts' - default: 10 - validator: - gte: 0 - - temporarilyUnavailableBackoffBaseMs: - description: 'The base period of time to wait between each TemporarilyUnavailable retry - attempt. The backoff time is linear such that the Nth retry waits for N times - the base backoff period.' - set_at: [ startup, runtime ] - cpp_varname: 'TemporarilyUnavailableException::retryBackoffBaseMs' - default: 1000 - validator: - gte: 0 -- cgit v1.2.1