You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

sequence_lock.h 9.3 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. //
  2. // Copyright 2020 The Abseil Authors.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // https://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. #ifndef ABSL_FLAGS_INTERNAL_SEQUENCE_LOCK_H_
  16. #define ABSL_FLAGS_INTERNAL_SEQUENCE_LOCK_H_
  17. #include <stddef.h>
  18. #include <stdint.h>
  19. #include <atomic>
  20. #include <cassert>
  21. #include <cstring>
  22. #include "absl/base/optimization.h"
  23. namespace absl
  24. {
  25. ABSL_NAMESPACE_BEGIN
  26. namespace flags_internal
  27. {
  28. // Align 'x' up to the nearest 'align' bytes.
  29. inline constexpr size_t AlignUp(size_t x, size_t align)
  30. {
  31. return align * ((x + align - 1) / align);
  32. }
  33. // A SequenceLock implements lock-free reads. A sequence counter is incremented
  34. // before and after each write, and readers access the counter before and after
  35. // accessing the protected data. If the counter is verified to not change during
  36. // the access, and the sequence counter value was even, then the reader knows
  37. // that the read was race-free and valid. Otherwise, the reader must fall back
  38. // to a Mutex-based code path.
  39. //
  40. // This particular SequenceLock starts in an "uninitialized" state in which
  41. // TryRead() returns false. It must be enabled by calling MarkInitialized().
  42. // This serves as a marker that the associated flag value has not yet been
  43. // initialized and a slow path needs to be taken.
  44. //
  45. // The memory reads and writes protected by this lock must use the provided
  46. // `TryRead()` and `Write()` functions. These functions behave similarly to
  47. // `memcpy()`, with one oddity: the protected data must be an array of
  48. // `std::atomic<uint64>`. This is to comply with the C++ standard, which
  49. // considers data races on non-atomic objects to be undefined behavior. See "Can
  50. // Seqlocks Get Along With Programming Language Memory Models?"[1] by Hans J.
  51. // Boehm for more details.
  52. //
  53. // [1] https://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf
  54. class SequenceLock
  55. {
  56. public:
  57. constexpr SequenceLock() :
  58. lock_(kUninitialized)
  59. {
  60. }
  61. // Mark that this lock is ready for use.
  62. void MarkInitialized()
  63. {
  64. assert(lock_.load(std::memory_order_relaxed) == kUninitialized);
  65. lock_.store(0, std::memory_order_release);
  66. }
  67. // Copy "size" bytes of data from "src" to "dst", protected as a read-side
  68. // critical section of the sequence lock.
  69. //
  70. // Unlike traditional sequence lock implementations which loop until getting a
  71. // clean read, this implementation returns false in the case of concurrent
  72. // calls to `Write`. In such a case, the caller should fall back to a
  73. // locking-based slow path.
  74. //
  75. // Returns false if the sequence lock was not yet marked as initialized.
  76. //
  77. // NOTE: If this returns false, "dst" may be overwritten with undefined
  78. // (potentially uninitialized) data.
  79. bool TryRead(void* dst, const std::atomic<uint64_t>* src, size_t size) const
  80. {
  81. // Acquire barrier ensures that no loads done by f() are reordered
  82. // above the first load of the sequence counter.
  83. int64_t seq_before = lock_.load(std::memory_order_acquire);
  84. if (ABSL_PREDICT_FALSE(seq_before & 1) == 1)
  85. return false;
  86. RelaxedCopyFromAtomic(dst, src, size);
  87. // Another acquire fence ensures that the load of 'lock_' below is
  88. // strictly ordered after the RelaxedCopyToAtomic call above.
  89. std::atomic_thread_fence(std::memory_order_acquire);
  90. int64_t seq_after = lock_.load(std::memory_order_relaxed);
  91. return ABSL_PREDICT_TRUE(seq_before == seq_after);
  92. }
  93. // Copy "size" bytes from "src" to "dst" as a write-side critical section
  94. // of the sequence lock. Any concurrent readers will be forced to retry
  95. // until they get a read that does not conflict with this write.
  96. //
  97. // This call must be externally synchronized against other calls to Write,
  98. // but may proceed concurrently with reads.
  99. void Write(std::atomic<uint64_t>* dst, const void* src, size_t size)
  100. {
  101. // We can use relaxed instructions to increment the counter since we
  102. // are extenally synchronized. The std::atomic_thread_fence below
  103. // ensures that the counter updates don't get interleaved with the
  104. // copy to the data.
  105. int64_t orig_seq = lock_.load(std::memory_order_relaxed);
  106. assert((orig_seq & 1) == 0); // Must be initially unlocked.
  107. lock_.store(orig_seq + 1, std::memory_order_relaxed);
  108. // We put a release fence between update to lock_ and writes to shared data.
  109. // Thus all stores to shared data are effectively release operations and
  110. // update to lock_ above cannot be re-ordered past any of them. Note that
  111. // this barrier is not for the fetch_add above. A release barrier for the
  112. // fetch_add would be before it, not after.
  113. std::atomic_thread_fence(std::memory_order_release);
  114. RelaxedCopyToAtomic(dst, src, size);
  115. // "Release" semantics ensure that none of the writes done by
  116. // RelaxedCopyToAtomic() can be reordered after the following modification.
  117. lock_.store(orig_seq + 2, std::memory_order_release);
  118. }
  119. // Return the number of times that Write() has been called.
  120. //
  121. // REQUIRES: This must be externally synchronized against concurrent calls to
  122. // `Write()` or `IncrementModificationCount()`.
  123. // REQUIRES: `MarkInitialized()` must have been previously called.
  124. int64_t ModificationCount() const
  125. {
  126. int64_t val = lock_.load(std::memory_order_relaxed);
  127. assert(val != kUninitialized && (val & 1) == 0);
  128. return val / 2;
  129. }
  130. // REQUIRES: This must be externally synchronized against concurrent calls to
  131. // `Write()` or `ModificationCount()`.
  132. // REQUIRES: `MarkInitialized()` must have been previously called.
  133. void IncrementModificationCount()
  134. {
  135. int64_t val = lock_.load(std::memory_order_relaxed);
  136. assert(val != kUninitialized);
  137. lock_.store(val + 2, std::memory_order_relaxed);
  138. }
  139. private:
  140. // Perform the equivalent of "memcpy(dst, src, size)", but using relaxed
  141. // atomics.
  142. static void RelaxedCopyFromAtomic(void* dst, const std::atomic<uint64_t>* src, size_t size)
  143. {
  144. char* dst_byte = static_cast<char*>(dst);
  145. while (size >= sizeof(uint64_t))
  146. {
  147. uint64_t word = src->load(std::memory_order_relaxed);
  148. std::memcpy(dst_byte, &word, sizeof(word));
  149. dst_byte += sizeof(word);
  150. src++;
  151. size -= sizeof(word);
  152. }
  153. if (size > 0)
  154. {
  155. uint64_t word = src->load(std::memory_order_relaxed);
  156. std::memcpy(dst_byte, &word, size);
  157. }
  158. }
  159. // Perform the equivalent of "memcpy(dst, src, size)", but using relaxed
  160. // atomics.
  161. static void RelaxedCopyToAtomic(std::atomic<uint64_t>* dst, const void* src, size_t size)
  162. {
  163. const char* src_byte = static_cast<const char*>(src);
  164. while (size >= sizeof(uint64_t))
  165. {
  166. uint64_t word;
  167. std::memcpy(&word, src_byte, sizeof(word));
  168. dst->store(word, std::memory_order_relaxed);
  169. src_byte += sizeof(word);
  170. dst++;
  171. size -= sizeof(word);
  172. }
  173. if (size > 0)
  174. {
  175. uint64_t word = 0;
  176. std::memcpy(&word, src_byte, size);
  177. dst->store(word, std::memory_order_relaxed);
  178. }
  179. }
  180. static constexpr int64_t kUninitialized = -1;
  181. std::atomic<int64_t> lock_;
  182. };
  183. } // namespace flags_internal
  184. ABSL_NAMESPACE_END
  185. } // namespace absl
  186. #endif // ABSL_FLAGS_INTERNAL_SEQUENCE_LOCK_H_