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