1 // write_conflict_exception.h
2
3
4 /**
5 * Copyright (C) 2018-present MongoDB, Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the Server Side Public License, version 1,
9 * as published by MongoDB, Inc.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * Server Side Public License for more details.
15 *
16 * You should have received a copy of the Server Side Public License
17 * along with this program. If not, see
18 * <http://www.mongodb.com/licensing/server-side-public-license>.
19 *
20 * As a special exception, the copyright holders give permission to link the
21 * code of portions of this program with the OpenSSL library under certain
22 * conditions as described in each individual source file and distribute
23 * linked combinations including the program with the OpenSSL library. You
24 * must comply with the Server Side Public License in all respects for
25 * all of the code used other than as permitted herein. If you modify file(s)
26 * with this exception, you may extend this exception to your version of the
27 * file(s), but you are not obligated to do so. If you do not wish to do so,
28 * delete this exception statement from your version. If you delete this
29 * exception statement from all source files in the program, then also delete
30 * it in the license file.
31 */
32
33 #pragma once
34
35 #include <exception>
36
37 #include "mongo/base/string_data.h"
38 #include "mongo/db/curop.h"
39 #include "mongo/util/assert_util.h"
40
41 namespace mongo {
42
43 /**
44 * This is thrown if during a write, two or more operations conflict with each other.
45 * For example if two operations get the same version of a document, and then both try to
46 * modify that document, this exception will get thrown by one of them.
47 */
48 class WriteConflictException final : public DBException {
49 public:
50 WriteConflictException();
51
52 /**
53 * Will log a message if sensible and will do an exponential backoff to make sure
54 * we don't hammer the same doc over and over.
55 * @param attempt - what attempt is this, 1 based
56 * @param operation - e.g. "update"
57 */
58 static void logAndBackoff(int attempt, StringData operation, StringData ns);
59
60 /**
61 * If true, will call printStackTrace on every WriteConflictException created.
62 * Can be set via setParameter named traceWriteConflictExceptions.
63 */
64 static AtomicBool trace;
65
66 private:
defineOnlyInFinalSubclassToPreventSlicing()67 void defineOnlyInFinalSubclassToPreventSlicing() final {}
68 };
69
70 /**
71 * Runs the argument function f as many times as needed for f to complete or throw an exception
72 * other than WriteConflictException. For each time f throws a WriteConflictException, logs the
73 * error, waits a spell, cleans up, and then tries f again. Imposes no upper limit on the number
74 * of times to re-try f, so any required timeout behavior must be enforced within f.
75 *
76 * If we are already in a WriteUnitOfWork, we assume that we are being called within a
77 * WriteConflictException retry loop up the call stack. Hence, this retry loop is reduced to an
78 * invocation of the argument function f without any exception handling and retry logic.
79 */
80 template <typename F>
writeConflictRetry(OperationContext * opCtx,StringData opStr,StringData ns,F && f)81 auto writeConflictRetry(OperationContext* opCtx, StringData opStr, StringData ns, F&& f) {
82 invariant(opCtx);
83 invariant(opCtx->lockState());
84 invariant(opCtx->recoveryUnit());
85
86 if (opCtx->lockState()->inAWriteUnitOfWork()) {
87 return f();
88 }
89
90 int attempts = 0;
91 while (true) {
92 try {
93 return f();
94 } catch (WriteConflictException const&) {
95 ++CurOp::get(opCtx)->debug().writeConflicts;
96 WriteConflictException::logAndBackoff(attempts, opStr, ns);
97 ++attempts;
98 opCtx->recoveryUnit()->abandonSnapshot();
99 }
100 }
101 }
102
103 } // namespace mongo
104