MySQL 9.1.0
Source Code Documentation
ut0seq_lock.h
Go to the documentation of this file.
1/*****************************************************************************
2
3Copyright (c) 2021, 2024, Oracle and/or its affiliates.
4
5This program is free software; you can redistribute it and/or modify it under
6the terms of the GNU General Public License, version 2.0, as published by the
7Free Software Foundation.
8
9This program is designed to work with certain software (including
10but not limited to OpenSSL) that is licensed under separate terms,
11as designated in a particular file or component or in included license
12documentation. The authors of MySQL hereby grant you an additional
13permission to link the program and your derivative works with the
14separately licensed software that they have either included with
15the program or referenced in the documentation.
16
17This program is distributed in the hope that it will be useful, but WITHOUT
18ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
20for more details.
21
22You should have received a copy of the GNU General Public License along with
23this program; if not, write to the Free Software Foundation, Inc.,
2451 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25
26*****************************************************************************/
27
28/** @file include/ut0seq_lock.h
29 Implements a sequential lock structure for non-locking atomic read/write
30 operations on a complex structure.
31
32 *******************************************************************/
33
34#ifndef ut0seq_lock_h
35#define ut0seq_lock_h
36
37#include <atomic>
38#include <thread>
39
40#include "ut0class_life_cycle.h"
41
42namespace ut {
43/** A class that allows to read value of variable of some type T atomically and
44allows the value to be changed, all using lock-free operations. The type T has
45to be composed of std::atomic fields only. That is because read(op_r) might read
46it in parallel to write(op_w). Other than that you are allowed to use any type.
47Inspired by https://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf Figure 6. */
48template <typename T>
49class Seq_lock : private Non_copyable {
50 public:
51 Seq_lock() = default;
52
53 template <typename... Args>
54 Seq_lock(Args... args) : m_value{std::forward<Args>(args)...} {}
55 /* This class can be made copyable, but this requires additional constructors.
56 */
57
58 /** Writes a new value for the variable of type T. The op can use
59 memory_order_relaxed stores.
60 NOTE: The user needs to synchronize all calls to this method. */
61 template <typename Op>
62 void write(Op &&op) {
63 const auto old = m_seq.load(std::memory_order_relaxed);
64 /* The odd value means someone else is executing the write operation
65 concurrently, and this is not allowed. */
66 ut_ad((old & 1) == 0);
67 m_seq.store(old + 1, std::memory_order_relaxed);
68 /* This fence is meant to synchronize with the fence in read(), whenever
69 op() in read() happens to load-from any of the values stored by our op().
70 Then it would follow that load to seq_after will happen-after our
71 first fetch_add(1), which is all we need. See 32.9.1 of C++17 draft. */
72 std::atomic_thread_fence(std::memory_order_release);
73 op(m_value);
74 m_seq.store(old + 2, std::memory_order_release);
75 }
76 /* Reads the value of the stored value of type T using operation op(). The
77 op() can use memory_order_relaxed loads. The op() can't assume the data stored
78 inside T is logically consistent. Calls to this method don't need to be
79 synchronized. */
80 template <typename Op>
81 auto read(Op &&op) const {
82 int try_count = 0;
83 while (true) {
84 const auto seq_before = m_seq.load(std::memory_order_acquire);
85 if ((seq_before & 1) == 1) {
86 /* Someone is currently writing to the stored value. Try a few times to
87 read the seq value, if this not help, try to yield execution. */
88 if ((++try_count & 7) == 0) {
89 std::this_thread::yield();
90 }
91 continue;
92 }
93 auto res = op(m_value);
94 std::atomic_thread_fence(std::memory_order_acquire);
95 const auto seq_after = m_seq.load(std::memory_order_relaxed);
96 if (seq_before == seq_after) {
97 return res;
98 }
99 /* The begin and end seq number is different, so the value read from T may
100 be invalid. Let's just try again to perform the read. We do not want to
101 yield here, as the new seq value is already set. If it is an odd value, we
102 will detect it in the next loop and possibly yield. If it is even, we will
103 just try to read new value. */
104 }
105 /* If we got here, then op() has seen a single coherent picture of value.
106 We have seq_before == seq_after and it is even.
107 Suppose that one of the loads inside op() saw value written by some writer w
108 and thus our fence synchronizes-with w's fence, and thus seq_after
109 assignment happens-after w's first increment of seq.
110 Since seq_after is even, it must mean it comes from some later store in
111 seq's modification order, not earlier than w's final increment of seq.
112 And as seq_before is equal to it, it comes from the same later store.
113 But, seq_before was assigned using acquire load, thus it
114 synchronizes-with the store from which it loads, meaning that op() has
115 to happen-after w's final increment of seq (one might need to follow
116 the mutex sequence if there were other writers in between).
117 Thus we see that if op() sees any part of the value written by w, then
118 it all the other parts of value it sees are at least as fresh.
119 Since writers are sequenced, it is meaningful to focus on "the latest"
120 writer whose change our op() detected, and use it as w in above reasoning to
121 conclude all fragments seen by op() come from it. */
122 }
123
124 private:
125 /** Stored value. */
127 /** Sequence count. Even when the value is ready for read, odd when the value
128 is being written to. */
129 std::atomic<uint64_t> m_seq{0};
130};
131} // namespace ut
132
133#endif /* ut0seq_lock_h */
A utility class which, if inherited from, prevents the descendant class from being copied,...
Definition: ut0class_life_cycle.h:41
A class that allows to read value of variable of some type T atomically and allows the value to be ch...
Definition: ut0seq_lock.h:49
void write(Op &&op)
Writes a new value for the variable of type T.
Definition: ut0seq_lock.h:62
std::atomic< uint64_t > m_seq
Sequence count.
Definition: ut0seq_lock.h:129
T m_value
Stored value.
Definition: ut0seq_lock.h:126
auto read(Op &&op) const
Definition: ut0seq_lock.h:81
Seq_lock()=default
Seq_lock(Args... args)
Definition: ut0seq_lock.h:54
Definition: gcs_xcom_synode.h:64
This file contains a set of libraries providing overloads for regular dynamic allocation routines whi...
Definition: aligned_alloc.h:48
Utilities related to class lifecycle.
#define ut_ad(EXPR)
Debug assertion.
Definition: ut0dbg.h:105