2 * Copyright 2017 Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 /* override-include-guard */
19 #error "This should only be included by hazptr.h"
22 /* quality of implementation switches */
24 // NOTE: The #ifndef pattern is prone to ODR violation. Its use for
25 // quality of implementation options is temporary. Eventually these
26 // options should be added to the API in future API extensions.
29 #define HAZPTR_AMB true
33 #define HAZPTR_TC true
36 #ifndef HAZPTR_TC_SIZE
37 #define HAZPTR_TC_SIZE 10
41 #define HAZPTR_PRIV true
44 #ifndef HAZPTR_ONE_DOMAIN
45 #define HAZPTR_ONE_DOMAIN false
48 #ifndef HAZPTR_SCAN_MULT
49 #define HAZPTR_SCAN_MULT 2
52 #ifndef HAZPTR_SCAN_THRESHOLD
53 #define HAZPTR_SCAN_THRESHOLD 1000
58 #define HAZPTR_STATS false
61 #include <folly/concurrency/CacheLocality.h>
62 #include <folly/experimental/AsymmetricMemoryBarrier.h>
63 #include <folly/experimental/hazptr/debug.h>
65 #include <mutex> // for thread caching
66 #include <unordered_set> // for hash set in bulk reclamation
72 * helper classes and functions
78 hazptr_stats& hazptr_stats();
81 #define INC_HAZPTR_STATS(x) hazptr_stats().x()
83 #define INC_HAZPTR_STATS(x)
95 * Thread caching of hazptr_rec-s that belong to the default domain.
98 class hazptr_tc_entry {
99 friend class hazptr_tc;
100 hazptr_rec* hprec_{nullptr};
103 void fill(hazptr_rec* hprec);
109 hazptr_tc_entry tc_[HAZPTR_TC_SIZE];
116 bool put(hazptr_rec* hprec);
119 hazptr_tc& hazptr_tc();
122 * Thread private lists of retired objects that belong to the default domain.
126 hazptr_domain* domain_{&default_hazptr_domain()};
127 hazptr_obj* head_{nullptr};
128 hazptr_obj* tail_{nullptr};
134 void push(hazptr_obj* obj);
137 void pushAllToDomain();
140 hazptr_priv& hazptr_priv();
148 constexpr hazptr_domain::hazptr_domain(memory_resource* mr) noexcept
151 /** hazptr_obj_base */
153 template <typename T, typename D>
154 inline void hazptr_obj_base<T, D>::retire(hazptr_domain& domain, D deleter) {
155 DEBUG_PRINT(this << " " << &domain);
156 deleter_ = std::move(deleter);
157 reclaim_ = [](hazptr_obj* p) {
158 auto hobp = static_cast<hazptr_obj_base*>(p);
159 auto obj = static_cast<T*>(hobp);
163 (HAZPTR_ONE_DOMAIN || (&domain == &default_hazptr_domain()))) {
164 hazptr_priv().push(this);
166 domain.objRetire(this);
173 friend class hazptr_domain;
174 friend class hazptr_holder;
175 friend class hazptr_tc_entry;
177 FOLLY_ALIGN_TO_AVOID_FALSE_SHARING
178 std::atomic<const void*> hazptr_{nullptr};
179 hazptr_rec* next_{nullptr};
180 std::atomic<bool> active_{false};
182 void set(const void* p) noexcept;
183 const void* get() const noexcept;
184 void clear() noexcept;
185 bool isActive() noexcept;
186 bool tryAcquire() noexcept;
187 void release() noexcept;
192 FOLLY_ALWAYS_INLINE hazptr_holder::hazptr_holder(hazptr_domain& domain) {
194 hazptr_ = domain_->hazptrAcquire();
195 DEBUG_PRINT(this << " " << domain_ << " " << hazptr_);
196 if (hazptr_ == nullptr) { std::bad_alloc e; throw e; }
199 inline hazptr_holder::hazptr_holder(std::nullptr_t) {
202 DEBUG_PRINT(this << " " << domain_ << " " << hazptr_);
205 FOLLY_ALWAYS_INLINE hazptr_holder::~hazptr_holder() {
209 domain_->hazptrRelease(hazptr_);
213 inline hazptr_holder::hazptr_holder(hazptr_holder&& rhs) noexcept {
214 domain_ = rhs.domain_;
215 hazptr_ = rhs.hazptr_;
216 rhs.domain_ = nullptr;
217 rhs.hazptr_ = nullptr;
220 inline hazptr_holder& hazptr_holder::operator=(hazptr_holder&& rhs) noexcept {
221 /* Self-move is a no-op. */
223 this->~hazptr_holder();
224 new (this) hazptr_holder(std::move(rhs));
229 template <typename T>
230 FOLLY_ALWAYS_INLINE bool hazptr_holder::try_protect(
232 const std::atomic<T*>& src) noexcept {
233 DEBUG_PRINT(this << " " << ptr << " " << &src);
235 /*** Full fence ***/ hazptr_mb::light();
236 T* p = src.load(std::memory_order_acquire);
245 template <typename T>
246 FOLLY_ALWAYS_INLINE T* hazptr_holder::get_protected(
247 const std::atomic<T*>& src) noexcept {
248 T* p = src.load(std::memory_order_relaxed);
249 while (!try_protect(p, src)) {}
250 DEBUG_PRINT(this << " " << p << " " << &src);
254 template <typename T>
255 FOLLY_ALWAYS_INLINE void hazptr_holder::reset(const T* ptr) noexcept {
256 auto p = static_cast<hazptr_obj*>(const_cast<T*>(ptr));
257 DEBUG_PRINT(this << " " << ptr << " p:" << p);
258 DCHECK(hazptr_); // UB if *this is empty
262 FOLLY_ALWAYS_INLINE void hazptr_holder::reset(std::nullptr_t) noexcept {
264 DCHECK(hazptr_); // UB if *this is empty
268 FOLLY_ALWAYS_INLINE void hazptr_holder::swap(hazptr_holder& rhs) noexcept {
270 this << " " << this->hazptr_ << " " << this->domain_ << " -- "
271 << &rhs << " " << rhs.hazptr_ << " " << rhs.domain_);
272 if (!HAZPTR_ONE_DOMAIN) {
273 std::swap(this->domain_, rhs.domain_);
275 std::swap(this->hazptr_, rhs.hazptr_);
278 FOLLY_ALWAYS_INLINE void swap(hazptr_holder& lhs, hazptr_holder& rhs) noexcept {
282 ////////////////////////////////////////////////////////////////////////////////
284 // - Control of reclamation (when and by whom)
285 // - End-to-end lock-free implementation
287 /** Definition of default_hazptr_domain() */
289 inline hazptr_domain& default_hazptr_domain() {
290 static hazptr_domain d;
297 inline void hazptr_rec::set(const void* p) noexcept {
298 DEBUG_PRINT(this << " " << p);
299 hazptr_.store(p, std::memory_order_release);
302 inline const void* hazptr_rec::get() const noexcept {
303 auto p = hazptr_.load(std::memory_order_acquire);
304 DEBUG_PRINT(this << " " << p);
308 inline void hazptr_rec::clear() noexcept {
310 hazptr_.store(nullptr, std::memory_order_release);
313 inline bool hazptr_rec::isActive() noexcept {
314 return active_.load(std::memory_order_acquire);
317 inline bool hazptr_rec::tryAcquire() noexcept {
318 bool active = isActive();
320 active_.compare_exchange_strong(
321 active, true, std::memory_order_release, std::memory_order_relaxed)) {
328 inline void hazptr_rec::release() noexcept {
330 active_.store(false, std::memory_order_release);
335 inline const void* hazptr_obj::getObjPtr() const {
342 inline hazptr_domain::~hazptr_domain() {
344 { /* reclaim all remaining retired objects */
346 auto retired = retired_.exchange(nullptr);
348 for (auto p = retired; p; p = next) {
352 retired = retired_.exchange(nullptr);
355 { /* free all hazptr_rec-s */
357 for (auto p = hazptrs_.load(std::memory_order_acquire); p; p = next) {
359 DCHECK(!p->isActive());
360 mr_->deallocate(static_cast<void*>(p), sizeof(hazptr_rec));
365 FOLLY_ALWAYS_INLINE hazptr_rec* hazptr_domain::hazptrAcquire() {
366 if (HAZPTR_TC && (HAZPTR_ONE_DOMAIN || this == &default_hazptr_domain())) {
367 auto hprec = hazptr_tc().get();
374 for (p = hazptrs_.load(std::memory_order_acquire); p; p = next) {
376 if (p->tryAcquire()) {
380 p = static_cast<hazptr_rec*>(mr_->allocate(sizeof(hazptr_rec)));
384 p->active_.store(true, std::memory_order_relaxed);
385 p->next_ = hazptrs_.load(std::memory_order_acquire);
386 while (!hazptrs_.compare_exchange_weak(
387 p->next_, p, std::memory_order_release, std::memory_order_acquire))
389 auto hcount = hcount_.fetch_add(1);
390 DEBUG_PRINT(this << " " << p << " " << sizeof(hazptr_rec) << " " << hcount);
394 FOLLY_ALWAYS_INLINE void hazptr_domain::hazptrRelease(hazptr_rec* p) noexcept {
395 if (HAZPTR_TC && (HAZPTR_ONE_DOMAIN || this == &default_hazptr_domain()) &&
396 hazptr_tc().put(p)) {
399 DEBUG_PRINT(this << " " << p);
404 hazptr_domain::pushRetired(hazptr_obj* head, hazptr_obj* tail, int count) {
405 /*** Full fence ***/ hazptr_mb::light();
406 tail->next_ = retired_.load(std::memory_order_acquire);
407 while (!retired_.compare_exchange_weak(
410 std::memory_order_release,
411 std::memory_order_acquire)) {
413 return rcount_.fetch_add(count) + count;
416 inline bool hazptr_domain::reachedThreshold(int rcount) {
418 rcount >= HAZPTR_SCAN_THRESHOLD &&
419 rcount >= HAZPTR_SCAN_MULT * hcount_.load(std::memory_order_acquire));
422 inline void hazptr_domain::objRetire(hazptr_obj* p) {
423 auto rcount = pushRetired(p, p, 1);
424 if (reachedThreshold(rcount)) {
429 inline void hazptr_domain::tryBulkReclaim() {
432 auto hcount = hcount_.load(std::memory_order_acquire);
433 auto rcount = rcount_.load(std::memory_order_acquire);
434 if (rcount < HAZPTR_SCAN_THRESHOLD || rcount < HAZPTR_SCAN_MULT * hcount) {
437 if (rcount_.compare_exchange_weak(
438 rcount, 0, std::memory_order_release, std::memory_order_relaxed)) {
445 inline void hazptr_domain::bulkReclaim() {
447 /*** Full fence ***/ hazptr_mb::heavy();
448 auto p = retired_.exchange(nullptr, std::memory_order_acquire);
449 auto h = hazptrs_.load(std::memory_order_acquire);
450 std::unordered_set<const void*> hs; // TODO lock-free alternative
451 for (; h; h = h->next_) {
455 hazptr_obj* retired = nullptr;
456 hazptr_obj* tail = nullptr;
458 for (; p; p = next) {
460 if (hs.count(p->getObjPtr()) == 0) {
461 DEBUG_PRINT(this << " " << p << " " << p->reclaim_);
466 if (tail == nullptr) {
473 pushRetired(retired, tail, rcount);
487 std::atomic<uint64_t> light_{0};
488 std::atomic<uint64_t> heavy_{0};
489 std::atomic<uint64_t> seq_cst_{0};
492 inline hazptr_stats::~hazptr_stats() {
493 DEBUG_PRINT(this << " light " << light_.load());
494 DEBUG_PRINT(this << " heavy " << heavy_.load());
495 DEBUG_PRINT(this << " seq_cst " << seq_cst_.load());
498 FOLLY_ALWAYS_INLINE void hazptr_stats::light() {
500 /* atomic */ ++light_;
504 inline void hazptr_stats::heavy() {
506 /* atomic */ ++heavy_;
510 inline void hazptr_stats::seq_cst() {
512 /* atomic */ ++seq_cst_;
516 inline class hazptr_stats& hazptr_stats() {
517 static class hazptr_stats stats_;
518 DEBUG_PRINT(&stats_);
524 inline void hazptr_mb::light() {
527 folly::asymmetricLightBarrier();
528 INC_HAZPTR_STATS(light);
530 atomic_thread_fence(std::memory_order_seq_cst);
531 INC_HAZPTR_STATS(seq_cst);
535 inline void hazptr_mb::heavy() {
538 folly::asymmetricHeavyBarrier(AMBFlags::EXPEDITED);
539 INC_HAZPTR_STATS(heavy);
541 atomic_thread_fence(std::memory_order_seq_cst);
542 INC_HAZPTR_STATS(seq_cst);
546 /** hazptr_tc - functions */
548 inline void hazptr_tc_entry::fill(hazptr_rec* hprec) {
550 DEBUG_PRINT(this << " " << hprec);
553 inline hazptr_rec* hazptr_tc_entry::get() {
556 DEBUG_PRINT(this << " " << hprec);
560 inline void hazptr_tc_entry::evict() {
564 DEBUG_PRINT(this << " " << hprec);
567 inline hazptr_tc::hazptr_tc() {
571 inline hazptr_tc::~hazptr_tc() {
573 for (int i = 0; i < count_; ++i) {
578 inline hazptr_rec* hazptr_tc::get() {
580 auto hprec = tc_[--count_].get();
581 DEBUG_PRINT(this << " " << hprec);
584 DEBUG_PRINT(this << " nullptr");
588 inline bool hazptr_tc::put(hazptr_rec* hprec) {
589 if (count_ < HAZPTR_TC_SIZE) {
590 tc_[count_++].fill(hprec);
591 DEBUG_PRINT(this << " " << count_ - 1);
597 inline class hazptr_tc& hazptr_tc() {
598 static thread_local class hazptr_tc tc;
603 /** hazptr_priv - functions */
605 inline hazptr_priv::hazptr_priv() {
609 inline hazptr_priv::~hazptr_priv() {
616 inline void hazptr_priv::push(hazptr_obj* obj) {
617 obj->next_ = nullptr;
625 if (domain_->reachedThreshold(rcount_)) {
630 inline void hazptr_priv::pushAllToDomain() {
631 domain_->pushRetired(head_, tail_, rcount_);
635 domain_->tryBulkReclaim();
638 inline class hazptr_priv& hazptr_priv() {
639 static thread_local class hazptr_priv priv;
645 } // namespace hazptr