xenium
hazard_pointer.hpp
1 //
2 // Copyright (c) 2018-2020 Manuel Pöter.
3 // Licensed under the MIT License. See LICENSE file in the project root for full license information.
4 //
5 
6 #ifndef XENIUM_HAZARD_POINTER_HPP
7 #define XENIUM_HAZARD_POINTER_HPP
8 
9 #include <xenium/reclamation/detail/allocation_tracker.hpp>
10 #include <xenium/reclamation/detail/concurrent_ptr.hpp>
11 #include <xenium/reclamation/detail/deletable_object.hpp>
12 #include <xenium/reclamation/detail/guard_ptr.hpp>
13 #include <xenium/reclamation/detail/thread_block_list.hpp>
14 
15 #include <xenium/acquire_guard.hpp>
16 #include <xenium/parameter.hpp>
17 #include <xenium/policy.hpp>
18 
19 #include <memory>
20 #include <stdexcept>
21 
22 namespace xenium::reclamation {
23 
29 class bad_hazard_pointer_alloc : public std::runtime_error {
30  using std::runtime_error::runtime_error;
31 };
32 
33 namespace detail {
34  template <class Strategy, class Derived>
35  struct basic_hp_thread_control_block;
36 
37  template <size_t K_, size_t A, size_t B, template <class> class ThreadControlBlock>
38  struct generic_hp_allocation_strategy {
39  static constexpr size_t K = K_;
40 
41  static size_t retired_nodes_threshold() { return A * number_of_active_hazard_pointers() + B; }
42 
43  static size_t number_of_active_hazard_pointers() { return number_of_active_hps.load(std::memory_order_relaxed); }
44 
45  using thread_control_block = ThreadControlBlock<generic_hp_allocation_strategy>;
46 
47  private:
48  friend thread_control_block;
49  friend basic_hp_thread_control_block<generic_hp_allocation_strategy, thread_control_block>;
50 
51  inline static std::atomic<size_t> number_of_active_hps{0};
52  };
53 
54  template <class Strategy>
55  struct static_hp_thread_control_block;
56 
57  template <class Strategy>
58  struct dynamic_hp_thread_control_block;
59 } // namespace detail
60 
61 namespace hp_allocation {
72  template <size_t K = 2, size_t A = 2, size_t B = 100>
73  struct static_strategy : detail::generic_hp_allocation_strategy<K, A, B, detail::static_hp_thread_control_block> {};
74 
96  template <size_t K = 2, size_t A = 2, size_t B = 100>
97  struct dynamic_strategy : detail::generic_hp_allocation_strategy<K, A, B, detail::dynamic_hp_thread_control_block> {};
98 } // namespace hp_allocation
99 
100 template <class AllocationStrategy = hp_allocation::static_strategy<3>>
101 struct hazard_pointer_traits {
102  using allocation_strategy = AllocationStrategy;
103 
104  template <class... Policies>
105  using with =
106  hazard_pointer_traits<parameter::type_param_t<policy::allocation_strategy, AllocationStrategy, Policies...>>;
107 };
108 
127 template <typename Traits = hazard_pointer_traits<>>
129  using allocation_strategy = typename Traits::allocation_strategy;
130  using thread_control_block = typename allocation_strategy::thread_control_block;
131  friend detail::basic_hp_thread_control_block<allocation_strategy, thread_control_block>;
132 
133  template <class T, class MarkedPtr>
134  class guard_ptr;
135 
136 public:
147  template <class... Policies>
148  using with = hazard_pointer<typename Traits::template with<Policies...>>;
149 
150  template <class T, std::size_t N = 0, class Deleter = std::default_delete<T>>
151  class enable_concurrent_ptr;
152 
153  class region_guard {};
154 
155  template <class T, std::size_t N = T::number_of_mark_bits>
156  using concurrent_ptr = detail::concurrent_ptr<T, N, guard_ptr>;
157 
158  ALLOCATION_TRACKER;
159 
160 private:
161  struct thread_data;
162 
163  inline static detail::thread_block_list<thread_control_block> global_thread_block_list;
164  inline static thread_local thread_data local_thread_data;
165 
166  ALLOCATION_TRACKING_FUNCTIONS;
167 };
168 
169 template <typename Traits>
170 template <class T, std::size_t N, class Deleter>
171 class hazard_pointer<Traits>::enable_concurrent_ptr :
172  private detail::deletable_object_impl<T, Deleter>,
173  private detail::tracked_object<hazard_pointer> {
174 public:
175  static constexpr std::size_t number_of_mark_bits = N;
176 
177 protected:
178  enable_concurrent_ptr() noexcept = default;
179  enable_concurrent_ptr(const enable_concurrent_ptr&) noexcept = default;
180  enable_concurrent_ptr(enable_concurrent_ptr&&) noexcept = default;
181  enable_concurrent_ptr& operator=(const enable_concurrent_ptr&) noexcept = default;
182  enable_concurrent_ptr& operator=(enable_concurrent_ptr&&) noexcept = default;
183  ~enable_concurrent_ptr() noexcept override = default;
184 
185 private:
186  friend detail::deletable_object_impl<T, Deleter>;
187 
188  template <class, class>
189  friend class guard_ptr;
190 };
191 
192 template <typename Traits>
193 template <class T, class MarkedPtr>
194 class hazard_pointer<Traits>::guard_ptr : public detail::guard_ptr<T, MarkedPtr, guard_ptr<T, MarkedPtr>> {
195  using base = detail::guard_ptr<T, MarkedPtr, guard_ptr>;
196  using Deleter = typename T::Deleter;
197 
198 public:
199  guard_ptr() noexcept = default;
200 
201  // Guard a marked ptr.
202  explicit guard_ptr(const MarkedPtr& p);
203  guard_ptr(const guard_ptr& p);
204  guard_ptr(guard_ptr&& p) noexcept;
205 
206  guard_ptr& operator=(const guard_ptr& p);
207  guard_ptr& operator=(guard_ptr&& p) noexcept;
208 
209  // Atomically take snapshot of p, and *if* it points to unreclaimed object, acquire shared ownership of it.
210  void acquire(const concurrent_ptr<T>& p, std::memory_order order = std::memory_order_seq_cst);
211 
212  // Like acquire, but quit early if a snapshot != expected.
213  bool acquire_if_equal(const concurrent_ptr<T>& p,
214  const MarkedPtr& expected,
215  std::memory_order order = std::memory_order_seq_cst);
216 
217  // Release ownership. Postcondition: get() == nullptr.
218  void reset() noexcept;
219 
220  // Reset. Deleter d will be applied some time after all owners release their ownership.
221  void reclaim(Deleter d = Deleter()) noexcept;
222 
223 private:
224  using enable_concurrent_ptr = hazard_pointer::enable_concurrent_ptr<T, MarkedPtr::number_of_mark_bits, Deleter>;
225 
226  friend base;
227  void do_swap(guard_ptr& g) noexcept;
228 
229  typename thread_control_block::hazard_pointer* hp = nullptr;
230 };
231 } // namespace xenium::reclamation
232 
233 #define HAZARD_POINTER_IMPL
234 #include <xenium/reclamation/impl/hazard_pointer.hpp>
235 #undef HAZARD_POINTER_IMPL
236 
237 #endif
xenium::reclamation::hp_allocation::dynamic_strategy
Hazard pointer allocation strategy for a dynamic number of hazard pointers per thread.
Definition: hazard_pointer.hpp:97
xenium::reclamation::detail::concurrent_ptr
T must be derived from enable_concurrent_ptr<T>. D is a deleter.
Definition: concurrent_ptr.hpp:17
xenium::reclamation::hazard_pointer
An implementation of the hazard pointers reclamation scheme as proposed by Michael [Mic04].
Definition: hazard_pointer.hpp:128
xenium::policy::allocation_strategy
Policy to configure the allocation strategy.
Definition: policy.hpp:96
xenium::reclamation::hp_allocation::static_strategy
Hazard pointer allocation strategy for a static number of hazard pointers per thread.
Definition: hazard_pointer.hpp:73
xenium::reclamation::bad_hazard_pointer_alloc
This exception is thrown if a thread tries to allocate a new hazard pointer, but the number of availa...
Definition: hazard_pointer.hpp:29