1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
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 * http://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 */
16
17 #pragma once
18
19 #include <folly/Traits.h>
20 #include <folly/synchronization/AsymmetricMemoryBarrier.h>
21 #include <folly/synchronization/Hazptr-fwd.h>
22 #include <folly/synchronization/HazptrDomain.h>
23 #include <folly/synchronization/HazptrRec.h>
24 #include <folly/synchronization/HazptrThrLocal.h>
25
26 namespace folly {
27
28 ///
29 /// Classes related to hazard pointer holders.
30 ///
31
32 /**
33 * hazptr_holder
34 *
35 * Class for automatic acquisition and release of hazard pointers,
36 * and interface for hazard pointer operations.
37 *
38 * Usage example:
39 * T* ptr;
40 * {
41 * hazptr_holder h = make_hazard_pointer();
42 * ptr = h.protect(src);
43 * // ... *ptr is protected ...
44 * h.reset_protection();
45 * // ... *ptr is not protected ...
46 * ptr = src.load();
47 * while (!h.try_protect(ptr, src)) {}
48 * // ... *ptr is protected ...
49 * }
50 * // ... *ptr is not protected
51 */
52 template <template <typename> class Atom>
53 class hazptr_holder {
54 hazptr_rec<Atom>* hprec_;
55
56 template <uint8_t M, template <typename> class A>
57 friend class hazptr_local;
58 friend hazptr_holder<Atom> make_hazard_pointer<Atom>(hazptr_domain<Atom>&);
59 template <uint8_t M, template <typename> class A>
60 friend hazptr_array<M, A> make_hazard_pointer_array();
61
62 /** Private constructor used by make_hazard_pointer and
63 make_hazard_pointer_array */
hazptr_holder(hazptr_rec<Atom> * hprec)64 FOLLY_ALWAYS_INLINE explicit hazptr_holder(hazptr_rec<Atom>* hprec)
65 : hprec_(hprec) {}
66
67 public:
68 /** Default empty constructor */
hazptr_holder()69 FOLLY_ALWAYS_INLINE hazptr_holder() noexcept : hprec_(nullptr) {}
70
71 /** For nonempty construction use make_hazard_pointer. */
72
73 /** Move constructor */
hazptr_holder(hazptr_holder && rhs)74 FOLLY_ALWAYS_INLINE hazptr_holder(hazptr_holder&& rhs) noexcept
75 : hprec_(std::exchange(rhs.hprec_, nullptr)) {}
76
77 hazptr_holder(const hazptr_holder&) = delete;
78 hazptr_holder& operator=(const hazptr_holder&) = delete;
79
80 /** Destructor */
~hazptr_holder()81 FOLLY_ALWAYS_INLINE ~hazptr_holder() {
82 if (LIKELY(hprec_ != nullptr)) {
83 hprec_->reset_hazptr();
84 auto domain = hprec_->domain();
85 #if FOLLY_HAZPTR_THR_LOCAL
86 if (LIKELY(domain == &default_hazptr_domain<Atom>())) {
87 if (LIKELY(hazptr_tc_tls<Atom>().try_put(hprec_))) {
88 return;
89 }
90 }
91 #endif
92 domain->release_hprec(hprec_);
93 }
94 }
95
96 /** Move operator */
97 FOLLY_ALWAYS_INLINE hazptr_holder& operator=(hazptr_holder&& rhs) noexcept {
98 /* Self-move is a no-op. */
99 if (LIKELY(this != &rhs)) {
100 this->~hazptr_holder();
101 new (this) hazptr_holder(rhs.hprec_);
102 rhs.hprec_ = nullptr;
103 }
104 return *this;
105 }
106
107 /** Hazard pointer operations */
108
109 /** try_protect */
110 template <typename T>
try_protect(T * & ptr,const Atom<T * > & src)111 FOLLY_ALWAYS_INLINE bool try_protect(T*& ptr, const Atom<T*>& src) noexcept {
112 return try_protect(ptr, src, [](T* t) { return t; });
113 }
114
115 template <typename T, typename Func>
try_protect(T * & ptr,const Atom<T * > & src,Func f)116 FOLLY_ALWAYS_INLINE bool try_protect(
117 T*& ptr, const Atom<T*>& src, Func f) noexcept {
118 /* Filtering the protected pointer through function Func is useful
119 for stealing bits of the pointer word */
120 auto p = ptr;
121 reset_protection(f(p));
122 /*** Full fence ***/ folly::asymmetricLightBarrier();
123 ptr = src.load(std::memory_order_acquire);
124 if (UNLIKELY(p != ptr)) {
125 reset_protection();
126 return false;
127 }
128 return true;
129 }
130
131 /** protect */
132 template <typename T>
protect(const Atom<T * > & src)133 FOLLY_ALWAYS_INLINE T* protect(const Atom<T*>& src) noexcept {
134 return protect(src, [](T* t) { return t; });
135 }
136
137 template <typename T, typename Func>
protect(const Atom<T * > & src,Func f)138 FOLLY_ALWAYS_INLINE T* protect(const Atom<T*>& src, Func f) noexcept {
139 T* ptr = src.load(std::memory_order_relaxed);
140 while (!try_protect(ptr, src, f)) {
141 /* Keep trying */;
142 }
143 return ptr;
144 }
145
146 /** reset_protection */
147 template <typename T>
reset_protection(const T * ptr)148 FOLLY_ALWAYS_INLINE void reset_protection(const T* ptr) noexcept {
149 auto p = static_cast<hazptr_obj<Atom>*>(const_cast<T*>(ptr));
150 DCHECK(hprec_); // UB if *this is empty
151 hprec_->reset_hazptr(p);
152 }
153
154 FOLLY_ALWAYS_INLINE void reset_protection(std::nullptr_t = nullptr) noexcept {
155 DCHECK(hprec_); // UB if *this is empty
156 hprec_->reset_hazptr();
157 }
158
159 /* Swap ownership of hazard pointers between hazptr_holder-s. */
160 /* Note: The owned hazard pointers remain unmodified during the swap
161 * and continue to protect the respective objects that they were
162 * protecting before the swap, if any. */
swap(hazptr_holder<Atom> & rhs)163 FOLLY_ALWAYS_INLINE void swap(hazptr_holder<Atom>& rhs) noexcept {
164 std::swap(this->hprec_, rhs.hprec_);
165 }
166
167 /** Returns a pointer to the owned hazptr_rec */
hprec()168 FOLLY_ALWAYS_INLINE hazptr_rec<Atom>* hprec() const noexcept {
169 return hprec_;
170 }
171
172 /** Set the pointer to the owned hazptr_rec */
set_hprec(hazptr_rec<Atom> * hprec)173 FOLLY_ALWAYS_INLINE void set_hprec(hazptr_rec<Atom>* hprec) noexcept {
174 hprec_ = hprec;
175 }
176 }; // hazptr_holder
177
178 /**
179 * Free function make_hazard_pointer constructs nonempty holder
180 */
181 template <template <typename> class Atom>
make_hazard_pointer(hazptr_domain<Atom> & domain)182 FOLLY_ALWAYS_INLINE hazptr_holder<Atom> make_hazard_pointer(
183 hazptr_domain<Atom>& domain) {
184 #if FOLLY_HAZPTR_THR_LOCAL
185 if (LIKELY(&domain == &default_hazptr_domain<Atom>())) {
186 auto hprec = hazptr_tc_tls<Atom>().try_get();
187 if (LIKELY(hprec != nullptr)) {
188 return hazptr_holder<Atom>(hprec);
189 }
190 }
191 #endif
192 auto hprec = domain.acquire_hprecs(1);
193 DCHECK(hprec);
194 DCHECK(hprec->next_avail() == nullptr);
195 return hazptr_holder<Atom>(hprec);
196 }
197
198 /**
199 * Free function. Swaps hazptr_holder-s.
200 */
201 template <template <typename> class Atom>
swap(hazptr_holder<Atom> & lhs,hazptr_holder<Atom> & rhs)202 FOLLY_ALWAYS_INLINE void swap(
203 hazptr_holder<Atom>& lhs, hazptr_holder<Atom>& rhs) noexcept {
204 lhs.swap(rhs);
205 }
206
207 /**
208 * Type used by hazptr_array and hazptr_local.
209 */
210 template <template <typename> class Atom>
211 using aligned_hazptr_holder = aligned_storage_for_t<hazptr_holder<Atom>>;
212
213 /**
214 * hazptr_array
215 *
216 * Optimized template for bulk construction and destruction of hazard
217 * pointers.
218 *
219 * WARNING: Do not move from or to individual hazptr_holder-s.
220 * Only move the whole hazptr_array.
221 *
222 * NOTE: It is allowed to swap an individual hazptr_holder that
223 * belongs to hazptr_array with (a) a hazptr_holder object, or (b) a
224 * hazptr_holder that is part of hazptr_array, under the conditions:
225 * (i) both hazptr_holder-s are either both empty or both nonempty
226 * and (ii) both belong to the same domain.
227 */
228 template <uint8_t M, template <typename> class Atom>
229 class hazptr_array {
230 static_assert(M > 0, "M must be a positive integer.");
231
232 aligned_hazptr_holder<Atom> raw_[M];
233 bool empty_;
234
235 friend hazptr_array<M, Atom> make_hazard_pointer_array<M, Atom>();
236
237 /** Private constructor used by make_hazard_pointer_array */
hazptr_array(std::nullptr_t)238 FOLLY_ALWAYS_INLINE explicit hazptr_array(std::nullptr_t) noexcept {}
239
240 public:
241 /** Default empty constructor */
hazptr_array()242 FOLLY_ALWAYS_INLINE hazptr_array() noexcept : empty_(true) {
243 auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
244 for (uint8_t i = 0; i < M; ++i) {
245 new (&h[i]) hazptr_holder<Atom>();
246 }
247 }
248
249 /** For nonempty construction use make_hazard_pointer_array. */
250
251 /** Move constructor */
hazptr_array(hazptr_array && other)252 FOLLY_ALWAYS_INLINE hazptr_array(hazptr_array&& other) noexcept {
253 auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
254 auto hother = reinterpret_cast<hazptr_holder<Atom>*>(&other.raw_);
255 for (uint8_t i = 0; i < M; ++i) {
256 new (&h[i]) hazptr_holder<Atom>(std::move(hother[i]));
257 }
258 empty_ = other.empty_;
259 other.empty_ = true;
260 }
261
262 hazptr_array(const hazptr_array&) = delete;
263 hazptr_array& operator=(const hazptr_array&) = delete;
264
265 /** Destructor */
~hazptr_array()266 FOLLY_ALWAYS_INLINE ~hazptr_array() {
267 if (empty_) {
268 return;
269 }
270 auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
271 #if FOLLY_HAZPTR_THR_LOCAL
272 auto& tc = hazptr_tc_tls<Atom>();
273 auto count = tc.count();
274 auto cap = hazptr_tc<Atom>::capacity();
275 if (UNLIKELY((M + count) > cap)) {
276 tc.evict((M + count) - cap);
277 count = cap - M;
278 }
279 for (uint8_t i = 0; i < M; ++i) {
280 h[i].reset_protection();
281 tc[count + i].fill(h[i].hprec());
282 h[i].set_hprec(nullptr);
283 }
284 tc.set_count(count + M);
285 #else
286 for (uint8_t i = 0; i < M; ++i) {
287 h[i].~hazptr_holder();
288 }
289 #endif
290 }
291
292 /** Move operator */
293 FOLLY_ALWAYS_INLINE hazptr_array& operator=(hazptr_array&& other) noexcept {
294 auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
295 for (uint8_t i = 0; i < M; ++i) {
296 h[i] = std::move(other[i]);
297 }
298 empty_ = other.empty_;
299 other.empty_ = true;
300 return *this;
301 }
302
303 /** [] operator */
304 FOLLY_ALWAYS_INLINE hazptr_holder<Atom>& operator[](uint8_t i) noexcept {
305 auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
306 DCHECK(i < M);
307 return h[i];
308 }
309 }; // hazptr_array
310
311 /**
312 * Free function make_hazard_pointer_array constructs nonempty array
313 */
314 template <uint8_t M, template <typename> class Atom>
make_hazard_pointer_array()315 FOLLY_ALWAYS_INLINE hazptr_array<M, Atom> make_hazard_pointer_array() {
316 hazptr_array<M, Atom> a(nullptr);
317 auto h = reinterpret_cast<hazptr_holder<Atom>*>(&a.raw_);
318 #if FOLLY_HAZPTR_THR_LOCAL
319 static_assert(
320 M <= hazptr_tc<Atom>::capacity(),
321 "M must be within the thread cache capacity.");
322 auto& tc = hazptr_tc_tls<Atom>();
323 auto count = tc.count();
324 if (UNLIKELY(M > count)) {
325 tc.fill(M - count);
326 count = M;
327 }
328 uint8_t offset = count - M;
329 for (uint8_t i = 0; i < M; ++i) {
330 auto hprec = tc[offset + i].get();
331 DCHECK(hprec != nullptr);
332 new (&h[i]) hazptr_holder<Atom>(hprec);
333 }
334 tc.set_count(offset);
335 #else
336 auto hprec = hazard_pointer_default_domain<Atom>().acquire_hprecs(M);
337 for (uint8_t i = 0; i < M; ++i) {
338 DCHECK(hprec);
339 auto next = hprec->next_avail();
340 hprec->set_next_avail(nullptr);
341 new (&h[i]) hazptr_holder<Atom>(hprec);
342 hprec = next;
343 }
344 DCHECK(hprec == nullptr);
345 #endif
346 a.empty_ = false;
347 return a;
348 }
349
350 /**
351 * hazptr_local
352 *
353 * Optimized for construction and destruction of one or more
354 * nonempty hazptr_holder-s with local scope.
355 *
356 * WARNING 1: Do not move from or to individual hazptr_holder-s.
357 *
358 * WARNING 2: There can only be one hazptr_local active for the same
359 * thread at any time. This is not tracked and checked by the
360 * implementation (except in debug mode) because it would negate the
361 * performance gains of this class.
362 */
363 template <uint8_t M, template <typename> class Atom>
364 class hazptr_local {
365 static_assert(M > 0, "M must be a positive integer.");
366
367 aligned_hazptr_holder<Atom> raw_[M];
368
369 public:
370 /** Constructor */
hazptr_local()371 FOLLY_ALWAYS_INLINE hazptr_local() {
372 auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
373 #if FOLLY_HAZPTR_THR_LOCAL
374 static_assert(
375 M <= hazptr_tc<Atom>::capacity(),
376 "M must be <= hazptr_tc::capacity().");
377 auto& tc = hazptr_tc_tls<Atom>();
378 auto count = tc.count();
379 if (UNLIKELY(M > count)) {
380 tc.fill(M - count);
381 }
382 if (kIsDebug) {
383 DCHECK(!tc.local());
384 tc.set_local(true);
385 }
386 for (uint8_t i = 0; i < M; ++i) {
387 auto hprec = tc[i].get();
388 DCHECK(hprec != nullptr);
389 new (&h[i]) hazptr_holder<Atom>(hprec);
390 }
391 #else
392 for (uint8_t i = 0; i < M; ++i) {
393 new (&h[i]) hazptr_holder<Atom>(make_hazard_pointer<Atom>());
394 }
395 #endif
396 }
397
398 hazptr_local(const hazptr_local&) = delete;
399 hazptr_local& operator=(const hazptr_local&) = delete;
400 hazptr_local(hazptr_local&&) = delete;
401 hazptr_local& operator=(hazptr_local&&) = delete;
402
403 /** Destructor */
~hazptr_local()404 FOLLY_ALWAYS_INLINE ~hazptr_local() {
405 auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
406 #if FOLLY_HAZPTR_THR_LOCAL
407 if (kIsDebug) {
408 auto& tc = hazptr_tc_tls<Atom>();
409 DCHECK(tc.local());
410 tc.set_local(false);
411 }
412 for (uint8_t i = 0; i < M; ++i) {
413 h[i].reset_protection();
414 }
415 #else
416 for (uint8_t i = 0; i < M; ++i) {
417 h[i].~hazptr_holder();
418 }
419 #endif
420 }
421
422 /** [] operator */
423 FOLLY_ALWAYS_INLINE hazptr_holder<Atom>& operator[](uint8_t i) noexcept {
424 auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
425 DCHECK(i < M);
426 return h[i];
427 }
428 }; // hazptr_local
429
430 } // namespace folly
431