File: ut0seq_lock.h

package info (click to toggle)
mysql-8.0 8.0.44-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,272,892 kB
  • sloc: cpp: 4,685,345; ansic: 412,712; pascal: 108,395; java: 83,641; perl: 30,221; cs: 27,067; sql: 26,594; python: 21,816; sh: 17,285; yacc: 17,169; php: 11,522; xml: 7,388; javascript: 7,083; makefile: 1,793; lex: 1,075; awk: 670; asm: 520; objc: 183; ruby: 97; lisp: 86
file content (133 lines) | stat: -rw-r--r-- 5,968 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*****************************************************************************

Copyright (c) 2021, 2025, Oracle and/or its affiliates.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License, version 2.0, as published by the
Free Software Foundation.

This program is designed to work with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation.  The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have either included with
the program or referenced in the documentation.

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 GNU General Public License, version 2.0,
for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

*****************************************************************************/

/** @file include/ut0seq_lock.h
 Implements a sequential lock structure for non-locking atomic read/write
 operations on a complex structure.

 *******************************************************************/

#ifndef ut0seq_lock_h
#define ut0seq_lock_h

#include <atomic>
#include <thread>

#include "ut0class_life_cycle.h"

namespace ut {
/** A class that allows to read value of variable of some type T atomically and
allows the value to be changed, all using lock-free operations. The type T has
to be composed of std::atomic fields only. That is because read(op_r) might read
it in parallel to write(op_w). Other than that you are allowed to use any type.
Inspired by https://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf Figure 6. */
template <typename T>
class Seq_lock : private Non_copyable {
 public:
  Seq_lock() = default;

  template <typename... Args>
  Seq_lock(Args... args) : m_value{std::forward<Args>(args)...} {}
  /* This class can be made copyable, but this requires additional constructors.
   */

  /** Writes a new value for the variable of type T. The op can use
  memory_order_relaxed stores.
  NOTE: The user needs to synchronize all calls to this method. */
  template <typename Op>
  void write(Op &&op) {
    const auto old = m_seq.load(std::memory_order_relaxed);
    /* The odd value means someone else is executing the write operation
    concurrently, and this is not allowed. */
    ut_ad((old & 1) == 0);
    m_seq.store(old + 1, std::memory_order_relaxed);
    /* This fence is meant to synchronize with the fence in read(), whenever
    op() in read() happens to load-from any of the values stored by our op().
    Then it would follow that load to seq_after will happen-after our
    first fetch_add(1), which is all we need. See 32.9.1 of C++17 draft. */
    std::atomic_thread_fence(std::memory_order_release);
    op(m_value);
    m_seq.store(old + 2, std::memory_order_release);
  }
  /* Reads the value of the stored value of type T using operation op(). The
  op() can use memory_order_relaxed loads. The op() can't assume the data stored
  inside T is logically consistent. Calls to this method don't need to be
  synchronized. */
  template <typename Op>
  auto read(Op &&op) const {
    int try_count = 0;
    while (true) {
      const auto seq_before = m_seq.load(std::memory_order_acquire);
      if ((seq_before & 1) == 1) {
        /* Someone is currently writing to the stored value. Try a few times to
        read the seq value, if this not help, try to yield execution. */
        if ((++try_count & 7) == 0) {
          std::this_thread::yield();
        }
        continue;
      }
      auto res = op(m_value);
      std::atomic_thread_fence(std::memory_order_acquire);
      const auto seq_after = m_seq.load(std::memory_order_relaxed);
      if (seq_before == seq_after) {
        return res;
      }
      /* The begin and end seq number is different, so the value read from T may
      be invalid. Let's just try again to perform the read. We do not want to
      yield here, as the new seq value is already set. If it is an odd value, we
      will detect it in the next loop and possibly yield. If it is even, we will
      just try to read new value. */
    }
    /* If we got here, then op() has seen a single coherent picture of value.
    We have seq_before == seq_after and it is even.
    Suppose that one of the loads inside op() saw value written by some writer w
    and thus our fence synchronizes-with w's fence, and thus seq_after
    assignment happens-after w's first increment of seq.
    Since seq_after is even, it must mean it comes from some later store in
    seq's modification order, not earlier than w's final increment of seq.
    And as seq_before is equal to it, it comes from the same later store.
    But, seq_before was assigned using acquire load, thus it
    synchronizes-with the store from which it loads, meaning that op() has
    to happen-after w's final increment of seq (one might need to follow
    the mutex sequence if there were other writers in between).
    Thus we see that if op() sees any part of the value written by w, then
    it all the other parts of value it sees are at least as fresh.
    Since writers are sequenced, it is meaningful to focus on "the latest"
    writer whose change our op() detected, and use it as w in above reasoning to
    conclude all fragments seen by op() come from it. */
  }

 private:
  /** Stored value. */
  T m_value;
  /** Sequence count. Even when the value is ready for read, odd when the value
  is being written to. */
  std::atomic<uint64_t> m_seq{0};
};
}  // namespace ut

#endif /* ut0seq_lock_h */