sequence_lock.h 7.5 KB

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