cycle_ptr
hazard.h
1 #ifndef CYCLE_PTR_DETAIL_HAZARD_H
2 #define CYCLE_PTR_DETAIL_HAZARD_H
3 
4 #include <array>
5 #include <atomic>
6 #include <cassert>
7 #include <cstddef>
8 #include <functional>
9 #include <new>
10 #include <type_traits>
11 #include <utility>
12 #include <cycle_ptr/detail/intrusive_ptr.h>
13 
14 namespace cycle_ptr::detail {
15 
16 
27 template<typename T>
28 class hazard {
29  private:
38 #if __cpp_lib_hardware_interference_size >= 201703
39  struct alignas(std::hardware_destructive_interference_size) data {
40  static_assert(sizeof(std::atomic<T*>) < std::hardware_destructive_interference_size,
41  "Cycle_ptr did not expect a platform where cache line is less than or equal to a pointer.");
42 
43  std::atomic<T*> ptr = nullptr;
44  [[maybe_unused]] char pad_[std::hardware_destructive_interference_size - sizeof(std::atomic<T*>)];
45  };
46 #elif defined(__amd64__) || defined(__x86_64__)
47  struct alignas(64) data {
48  static_assert(sizeof(std::atomic<T*>) < 64,
49  "Cycle_ptr did not expect a platform where cache line is less than or equal to a pointer.");
50 
51  std::atomic<T*> ptr = nullptr;
52  [[maybe_unused]] char pad_[64 - sizeof(std::atomic<T*>)];
53  };
54 #else
55 # error No fallback for your architecture, sorry.
56 #endif
57 
66  using ptr_set = std::array<data, 4096u / sizeof(data)>;
67 
68  public:
71 
72  hazard(const hazard&) = delete;
73 
76  explicit hazard() noexcept
77  : d_(allocate_())
78  {}
79 
82  [[nodiscard]]
83  auto operator()(const std::atomic<T*>& ptr)
84  noexcept
85  -> pointer {
86  return (*this)(ptr, ptr.load(std::memory_order_relaxed));
87  }
88 
89  private:
92  [[nodiscard]]
93  auto operator()(const std::atomic<T*>& ptr, T* target)
94  noexcept
95  -> pointer {
96  for (;;) {
97  // Nullptr case is trivial.
98  if (target == nullptr) return nullptr;
99 
100  // Publish intent to acquire 'target'.
101  for (;;) {
102  T* expect = nullptr;
103  if (d_.ptr.compare_exchange_strong(
104  expect,
105  target,
106  std::memory_order_acquire,
107  std::memory_order_relaxed)) [[likely]] {
108  break;
109  }
110  }
111 
112  // Check that ptr (still or again) holds 'target'.
113  {
114  T*const tmp = ptr.load(std::memory_order_acquire);
115  if (tmp != target) [[unlikely]] {
116  // Clear published value.
117  T* expect = target;
118  if (!d_.ptr.compare_exchange_strong(
119  expect,
120  nullptr,
121  std::memory_order_acq_rel,
122  std::memory_order_acquire)) {
123  // ABA problem:
124  // Since we don't know if the granted reference is the
125  // pointer that was originally assigned to ptr, or a newly
126  // allocated value at the same address, we
127  // have no option but to discard it.
128  if (ptr.load(std::memory_order_relaxed) == target) [[unlikely]] {
129  return pointer(target, false);
130  }
131 
132  // Another thread granted us a reference.
133  release_(target);
134  }
135 
136  target = tmp; // Update target to newly found value.
137  continue; // Restart after cancellation.
138  }
139  }
140 
141  // Current state:
142  // 1. intent published
143  // 2. intent is valid
144  // Thus, 'target' will have life time guarantee.
145  acquire_(target);
146 
147  T* expect = target;
148  if (!d_.ptr.compare_exchange_strong(
149  expect,
150  nullptr,
151  std::memory_order_acq_rel,
152  std::memory_order_acquire)) {
153  // Another thread granted us a reference, meaning we have 2.
154  // We want only 1.
155  release_(target);
156  }
157  return pointer(target, false);
158  }
159  }
160 
161  public:
179  static auto release(T*&& ptr)
180  noexcept
181  -> void {
182  if (ptr == nullptr) return; // Nullptr case is trivial.
183 
184  bool two_refs = false;
185  for (data& d : ptr_set_()) {
186  if (!std::exchange(two_refs, true))
187  acquire_(ptr);
188 
189  T* expect = ptr;
190  if (d.ptr.compare_exchange_strong(
191  expect,
192  nullptr,
193  std::memory_order_release,
194  std::memory_order_relaxed)) {
195  // Granted one reference to active hazard.
196  two_refs = false;
197  }
198  }
199 
200  if (std::exchange(two_refs, false)) release_(ptr);
201  release_(ptr);
202  ptr = nullptr;
203  }
204 
213  static auto reset(std::atomic<T*>& ptr)
214  noexcept
215  -> void {
216  release(ptr.exchange(nullptr, std::memory_order_release));
217  }
218 
231  static auto reset(std::atomic<T*>& ptr, pointer&& new_value)
232  noexcept
233  -> void {
234  release(ptr.exchange(new_value.detach(), std::memory_order_release));
235  }
236 
248  static auto reset(std::atomic<T*>& ptr, const pointer& new_value)
249  noexcept
250  -> void {
251  reset(ptr, pointer(new_value));
252  }
253 
259  static auto exchange(std::atomic<T*>& ptr, std::nullptr_t new_value)
260  noexcept
261  -> pointer {
262  T*const rv = ptr.exchange(nullptr, std::memory_order_acq_rel);
263  release(acquire_(rv)); // Must grant old value to active hazards.
264  return pointer(rv, false);
265  }
266 
272  static auto exchange(std::atomic<T*>& ptr, pointer&& new_value)
273  noexcept
274  -> pointer {
275  T*const rv = ptr.exchange(new_value.detach(), std::memory_order_acq_rel);
276  release(acquire_(rv)); // Must grant old value to active hazards.
277  return pointer(rv, false);
278  }
279 
285  static auto exchange(std::atomic<T*>& ptr, const pointer& new_value)
286  noexcept
287  -> pointer {
288  return exchange(ptr, pointer(new_value));
289  }
290 
303  static auto compare_exchange_weak(std::atomic<T*>& ptr, pointer& expected, pointer desired)
304  noexcept
305  -> bool {
306  T* expect = expected.get();
307  if (ptr.compare_exchange_weak(
308  expect,
309  desired.get(),
310  std::memory_order_acq_rel,
311  std::memory_order_relaxed)) {
312  desired.detach();
313  release(expected.get());
314  return true;
315  }
316 
317  expected = hazard()(ptr, expect);
318  return false;
319  }
320 
331  static auto compare_exchange_strong(std::atomic<T*>& ptr, pointer& expected, pointer desired)
332  noexcept
333  -> bool {
334  hazard hz;
335 
336  for (;;) {
337  T* expect = expected.get();
338  if (ptr.compare_exchange_strong(
339  expect,
340  desired.get(),
341  std::memory_order_acq_rel,
342  std::memory_order_relaxed)) {
343  desired.detach();
344  release(expected.get());
345  return true;
346  }
347 
348  auto actual = hz(ptr, expect);
349  if (expected != actual) {
350  expected = std::move(actual);
351  return false;
352  }
353  }
354 
355  /* unreachable */
356  }
357 
358  private:
359  // Hazard data structure; aligned to not cross a page boundary,
360  // thus limiting the number of TLB entries required for this to one.
361  alignas(sizeof(ptr_set)) static inline ptr_set ptr_set_impl_;
362 
364  static auto ptr_set_()
365  noexcept
366  -> ptr_set& {
367  return ptr_set_impl_;
368  }
369 
371  static auto allocate_()
372  noexcept
373  -> data& {
374  static std::atomic<unsigned int> seq_{ 0u };
375 
376  ptr_set& ps = ptr_set_();
377  return ps[seq_.fetch_add(1u, std::memory_order_relaxed) % ps.size()];
378  }
379 
381  static auto acquire_(T* ptr)
382  noexcept
383  -> T* {
384  // ADL
385  if (ptr != nullptr) intrusive_ptr_add_ref(ptr);
386  return ptr;
387  }
388 
390  static auto release_(T* ptr)
391  noexcept
392  -> void {
393  // ADL
394  if (ptr != nullptr) intrusive_ptr_release(ptr);
395  }
396 
398  data& d_;
399 };
400 
412 template<typename T>
413 class hazard_ptr {
414  private:
416  using hazard_t = hazard<T>;
417 
418  public:
420  using element_type = T;
422  using pointer = typename hazard_t::pointer;
425 
426 #if __cplusplus >= 201703
427  static constexpr bool is_always_lock_free = std::atomic<T*>::is_always_lock_free;
429 #endif
430 
432  auto is_lock_free() const
433  noexcept
434  -> bool {
435  return ptr_.is_lock_free();
436  }
437 
439  auto is_lock_free() const volatile
440  noexcept
441  -> bool {
442  return ptr_.is_lock_free();
443  }
444 
446  hazard_ptr() noexcept = default;
447 
449  hazard_ptr(const hazard_ptr& p) noexcept
450  : hazard_ptr(hazard_t()(p.ptr_))
451  {}
452 
454  hazard_ptr(hazard_ptr&& p) noexcept
455  : ptr_(p.ptr_.exchange(nullptr, std::memory_order_acq_rel))
456  {}
457 
466  hazard_ptr(pointer&& p) noexcept
467  : ptr_(p.detach())
468  {}
469 
475  hazard_ptr(const pointer& p) noexcept
476  : hazard_ptr(pointer(p))
477  {}
478 
481  ~hazard_ptr() noexcept {
482  reset();
483  }
484 
491  auto operator=(const hazard_ptr& p)
492  noexcept
493  -> pointer {
494  return *this = p.get();
495  }
496 
507  noexcept
508  -> pointer {
509  return *this = p.exchange(nullptr);
510  }
511 
518  auto operator=(const pointer& p)
519  noexcept
520  -> pointer {
521  reset(p);
522  return p;
523  }
524 
534  auto operator=(pointer&& p)
535  noexcept
536  -> pointer {
537  reset(p);
538  return std::move(p);
539  }
540 
547  auto operator=([[maybe_unused]] const std::nullptr_t nil)
548  noexcept
549  -> pointer {
550  reset();
551  return nullptr;
552  }
553 
559  auto reset()
560  noexcept
561  -> void {
562  hazard_t::reset(ptr_);
563  }
564 
570  auto reset([[maybe_unused]] std::nullptr_t nil)
571  noexcept
572  -> void {
573  hazard_t::reset(ptr_);
574  }
575 
584  auto reset(pointer&& p)
585  noexcept
586  -> void {
587  store(std::move(p));
588  }
589 
595  auto reset(const pointer& p)
596  noexcept
597  -> void {
598  store(p);
599  }
600 
602  operator pointer() const noexcept {
603  return get();
604  }
605 
612  [[nodiscard]]
613  auto get() const
614  noexcept
615  -> pointer {
616  return load();
617  }
618 
625  [[nodiscard]]
626  auto load() const
627  noexcept
628  -> pointer {
629  return hazard_t()(ptr_);
630  }
631 
640  auto store(pointer&& p)
641  noexcept
642  -> void {
643  hazard_t::reset(ptr_, std::move(p));
644  }
645 
651  auto store(const pointer& p)
652  noexcept
653  -> void {
654  hazard_t::reset(ptr_, p);
655  }
656 
667  [[nodiscard]]
668  auto exchange(pointer&& p)
669  noexcept
670  -> pointer {
671  return hazard_t::exchange(ptr_, std::move(p));
672  }
673 
681  [[nodiscard]]
682  auto exchange(const pointer& p)
683  noexcept
684  -> pointer {
685  return hazard_t::exchange(ptr_, p);
686  }
687 
689  auto compare_exchange_weak(pointer& expected, pointer desired)
690  noexcept
691  -> bool {
692  return hazard_t::compare_exchange_weak(expected, std::move(desired));
693  }
694 
696  auto compare_exchange_strong(pointer& expected, pointer desired)
697  noexcept
698  -> bool {
699  return hazard_t::compare_exchange_strong(expected, std::move(desired));
700  }
701 
703  friend auto operator==(const hazard_ptr& x, std::nullptr_t y)
704  noexcept
705  -> bool {
706  return x.ptr_.load(std::memory_order_acquire) == y;
707  }
708 
710  friend auto operator==(const hazard_ptr& x, std::add_const_t<T>* y)
711  noexcept
712  -> bool {
713  return x.ptr_.load(std::memory_order_acquire) == y;
714  }
715 
717  template<typename U>
718  friend auto operator==(const hazard_ptr& x, const intrusive_ptr<U>& y)
719  noexcept
720  -> std::enable_if_t<std::is_convertible_v<U*, T*>, bool> {
721  return x == y.get();
722  }
723 
725  friend auto operator==(std::nullptr_t x, const hazard_ptr& y)
726  noexcept
727  -> bool {
728  return y == x;
729  }
730 
732  friend auto operator==(std::add_const_t<T>* x, const hazard_ptr& y)
733  noexcept
734  -> bool {
735  return y == x;
736  }
737 
739  template<typename U>
740  friend auto operator==(const intrusive_ptr<U>& x, const hazard_ptr& y)
741  noexcept
742  -> std::enable_if_t<std::is_convertible_v<U*, T*>, bool> {
743  return y == x;
744  }
745 
747  friend auto operator!=(const hazard_ptr& x, std::nullptr_t y)
748  noexcept
749  -> bool {
750  return !(x == y);
751  }
752 
754  friend auto operator!=(const hazard_ptr& x, std::add_const_t<T>* y)
755  noexcept
756  -> bool {
757  return !(x == y);
758  }
759 
761  template<typename U>
762  friend auto operator!=(const hazard_ptr& x, const intrusive_ptr<U>& y)
763  noexcept
764  -> std::enable_if_t<std::is_convertible_v<U*, T*>, bool> {
765  return !(x == y);
766  }
767 
769  friend auto operator!=(std::nullptr_t x, const hazard_ptr& y)
770  noexcept
771  -> bool {
772  return !(x == y);
773  }
774 
776  friend auto operator!=(std::add_const_t<T>* x, const hazard_ptr& y)
777  noexcept
778  -> bool {
779  return !(x == y);
780  }
781 
783  template<typename U>
784  friend auto operator!=(const intrusive_ptr<U>& x, const hazard_ptr& y)
785  noexcept
786  -> std::enable_if_t<std::is_convertible_v<U*, T*>, bool> {
787  return !(x == y);
788  }
789 
790  private:
792  std::atomic<T*> ptr_ = nullptr;
793 };
794 
795 
796 } /* namespace cycle_ptr::detail */
797 
798 #endif /* CYCLE_PTR_DETAIL_HAZARD_H */
~hazard_ptr() noexcept
Destructor.
Definition: hazard.h:481
auto operator=(hazard_ptr &&p) noexcept -> pointer
Move assignment.
Definition: hazard.h:506
Definition: generation.h:19
auto load() const noexcept -> pointer
Read the value of this.
Definition: hazard.h:626
hazard_ptr() noexcept=default
Default constructor initializes to nullptr.
auto compare_exchange_strong(pointer &expected, pointer desired) noexcept -> bool
Strong compare-exchange operation.
Definition: hazard.h:696
static auto exchange(std::atomic< T * > &ptr, const pointer &new_value) noexcept -> pointer
Exchange the pointer.
Definition: hazard.h:285
friend auto operator!=(std::nullptr_t x, const hazard_ptr &y) noexcept -> bool
Inequality comparison.
Definition: hazard.h:769
friend auto operator==(const hazard_ptr &x, const intrusive_ptr< U > &y) noexcept -> std::enable_if_t< std::is_convertible_v< U *, T * >, bool >
Equality comparison.
Definition: hazard.h:718
auto operator()(const std::atomic< T * > &ptr) noexcept -> pointer
Load value in ptr.
Definition: hazard.h:83
friend auto operator!=(const hazard_ptr &x, const intrusive_ptr< U > &y) noexcept -> std::enable_if_t< std::is_convertible_v< U *, T * >, bool >
Inequality comparison.
Definition: hazard.h:762
friend auto operator==(std::nullptr_t x, const hazard_ptr &y) noexcept -> bool
Equality comparison.
Definition: hazard.h:725
Intrusive pointer.
Definition: intrusive_ptr.h:21
auto is_lock_free() const volatile noexcept -> bool
Test if this instance is lock free.
Definition: hazard.h:439
friend auto operator==(std::add_const_t< T > *x, const hazard_ptr &y) noexcept -> bool
Equality comparison.
Definition: hazard.h:732
friend auto operator==(const intrusive_ptr< U > &x, const hazard_ptr &y) noexcept -> std::enable_if_t< std::is_convertible_v< U *, T * >, bool >
Equality comparison.
Definition: hazard.h:740
friend auto operator==(const hazard_ptr &x, std::add_const_t< T > *y) noexcept -> bool
Equality comparison.
Definition: hazard.h:710
friend auto operator==(const hazard_ptr &x, std::nullptr_t y) noexcept -> bool
Equality comparison.
Definition: hazard.h:703
static auto reset(std::atomic< T * > &ptr) noexcept -> void
Reset the pointer.
Definition: hazard.h:213
Hazard pointer.
Definition: hazard.h:413
friend auto operator!=(const hazard_ptr &x, std::nullptr_t y) noexcept -> bool
Inequality comparison.
Definition: hazard.h:747
friend auto operator!=(const intrusive_ptr< U > &x, const hazard_ptr &y) noexcept -> std::enable_if_t< std::is_convertible_v< U *, T * >, bool >
Inequality comparison.
Definition: hazard.h:784
hazard_ptr(pointer &&p) noexcept
Initializing constructor.
Definition: hazard.h:466
static auto compare_exchange_strong(std::atomic< T * > &ptr, pointer &expected, pointer desired) noexcept -> bool
Compare-exchange operation.
Definition: hazard.h:331
auto get() const noexcept -> pointer
Read the value of this.
Definition: hazard.h:613
auto store(const pointer &p) noexcept -> void
Assignment.
Definition: hazard.h:651
auto operator=(pointer &&p) noexcept -> pointer
Move assignment.
Definition: hazard.h:534
static auto exchange(std::atomic< T * > &ptr, pointer &&new_value) noexcept -> pointer
Exchange the pointer.
Definition: hazard.h:272
auto is_lock_free() const noexcept -> bool
Test if this instance is lock free.
Definition: hazard.h:432
auto operator=(const pointer &p) noexcept -> pointer
Copy assignment.
Definition: hazard.h:518
auto reset() noexcept -> void
Reset this.
Definition: hazard.h:559
auto reset([[maybe_unused]] std::nullptr_t nil) noexcept -> void
Reset this.
Definition: hazard.h:570
auto reset(pointer &&p) noexcept -> void
Assignment.
Definition: hazard.h:584
auto store(pointer &&p) noexcept -> void
Assignment.
Definition: hazard.h:640
auto operator=([[maybe_unused]] const std::nullptr_t nil) noexcept -> pointer
nullptr assignment.
Definition: hazard.h:547
static auto reset(std::atomic< T * > &ptr, const pointer &new_value) noexcept -> void
Reset the pointer to the given new value.
Definition: hazard.h:248
auto reset(const pointer &p) noexcept -> void
Assignment.
Definition: hazard.h:595
friend auto operator!=(std::add_const_t< T > *x, const hazard_ptr &y) noexcept -> bool
Inequality comparison.
Definition: hazard.h:776
auto compare_exchange_weak(pointer &expected, pointer desired) noexcept -> bool
Weak compare-exchange operation.
Definition: hazard.h:689
typename hazard_t::pointer pointer
Smart pointer equivalent.
Definition: hazard.h:422
hazard_ptr(const pointer &p) noexcept
Initializing constructor.
Definition: hazard.h:475
static auto exchange(std::atomic< T * > &ptr, std::nullptr_t new_value) noexcept -> pointer
Exchange the pointer.
Definition: hazard.h:259
auto exchange(const pointer &p) noexcept -> pointer
Exchange operation.
Definition: hazard.h:682
Hazard pointer algorithm.
Definition: hazard.h:28
static auto release(T *&&ptr) noexcept -> void
Release pointer, granting it to a hazard operation, if possible.
Definition: hazard.h:179
static auto compare_exchange_weak(std::atomic< T * > &ptr, pointer &expected, pointer desired) noexcept -> bool
Compare-exchange operation.
Definition: hazard.h:303
hazard() noexcept
Create hazard context.
Definition: hazard.h:76
auto exchange(pointer &&p) noexcept -> pointer
Exchange operation.
Definition: hazard.h:668
friend auto operator!=(const hazard_ptr &x, std::add_const_t< T > *y) noexcept -> bool
Inequality comparison.
Definition: hazard.h:754
intrusive_ptr< T > pointer
Pointer used by this algorithm.
Definition: hazard.h:70
hazard_ptr(hazard_ptr &&p) noexcept
Move construction.
Definition: hazard.h:454
pointer value_type
Type held in this atomic.
Definition: hazard.h:424
static auto reset(std::atomic< T * > &ptr, pointer &&new_value) noexcept -> void
Reset the pointer to the given new value.
Definition: hazard.h:231
auto operator=(const hazard_ptr &p) noexcept -> pointer
Copy assignment.
Definition: hazard.h:491