Add threshold for thread local retired objects
[folly.git] / folly / experimental / hazptr / hazptr-impl.h
index 82734d6f21c6092dc80a0db960a1a225ad40d567..5c343c5c7a6dc1058a0403f49131e611deb968c2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 Facebook, Inc.
+ * Copyright 2017-present Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 /* override-include-guard */
 #ifndef HAZPTR_H
 #error "This should only be included by hazptr.h"
 #define HAZPTR_PRIV true
 #endif
 
+#ifndef HAZPTR_PRIV_THRESHOLD
+#define HAZPTR_PRIV_THRESHOLD 20
+#endif
+
 #ifndef HAZPTR_ONE_DOMAIN
 #define HAZPTR_ONE_DOMAIN false
 #endif
@@ -59,8 +62,8 @@
 #endif
 
 #include <folly/concurrency/CacheLocality.h>
-#include <folly/experimental/AsymmetricMemoryBarrier.h>
 #include <folly/experimental/hazptr/debug.h>
+#include <folly/synchronization/AsymmetricMemoryBarrier.h>
 
 #include <mutex> // for thread caching
 #include <unordered_set> // for hash set in bulk reclamation
@@ -118,9 +121,7 @@ static_assert(
 struct hazptr_tc {
   hazptr_tc_entry entry_[HAZPTR_TC_SIZE];
   size_t count_;
-#ifndef NDEBUG
-  bool local_;
-#endif
+  bool local_; // for debug mode only
 
  public:
   hazptr_tc_entry& operator[](size_t i);
@@ -206,16 +207,72 @@ inline void hazptr_obj_base<T, D>::retire(hazptr_domain& domain, D deleter) {
   domain.objRetire(this);
 }
 
+/**
+ *  hazptr_obj_base_refcounted
+ */
+
+template <typename T, typename D>
+inline void hazptr_obj_base_refcounted<T, D>::retire(
+    hazptr_domain& domain,
+    D deleter) {
+  DEBUG_PRINT(this << " " << &domain);
+  deleter_ = std::move(deleter);
+  reclaim_ = [](hazptr_obj* p) {
+    auto hrobp = static_cast<hazptr_obj_base_refcounted*>(p);
+    if (hrobp->release_ref()) {
+      auto obj = static_cast<T*>(hrobp);
+      hrobp->deleter_(obj);
+    }
+  };
+  if (HAZPTR_PRIV &&
+      (HAZPTR_ONE_DOMAIN || (&domain == &default_hazptr_domain()))) {
+    if (hazptr_priv_try_retire(this)) {
+      return;
+    }
+  }
+  domain.objRetire(this);
+}
+
+template <typename T, typename D>
+inline void hazptr_obj_base_refcounted<T, D>::acquire_ref() {
+  DEBUG_PRINT(this);
+  auto oldval = refcount_.fetch_add(1);
+  DCHECK(oldval >= 0);
+}
+
+template <typename T, typename D>
+inline void hazptr_obj_base_refcounted<T, D>::acquire_ref_safe() {
+  DEBUG_PRINT(this);
+  auto oldval = refcount_.load(std::memory_order_acquire);
+  DCHECK(oldval >= 0);
+  refcount_.store(oldval + 1, std::memory_order_release);
+}
+
+template <typename T, typename D>
+inline bool hazptr_obj_base_refcounted<T, D>::release_ref() {
+  DEBUG_PRINT(this);
+  auto oldval = refcount_.load(std::memory_order_acquire);
+  if (oldval > 0) {
+    oldval = refcount_.fetch_sub(1);
+  } else {
+    if (kIsDebug) {
+      refcount_.store(-1);
+    }
+  }
+  DEBUG_PRINT(this << " " << oldval);
+  DCHECK(oldval >= 0);
+  return oldval == 0;
+}
+
 /**
  *  hazptr_rec
  */
 
-class hazptr_rec {
+class alignas(hardware_destructive_interference_size) hazptr_rec {
   friend class hazptr_domain;
   friend class hazptr_holder;
   friend struct hazptr_tc_entry;
 
-  FOLLY_ALIGN_TO_AVOID_FALSE_SHARING
   std::atomic<const void*> hazptr_{nullptr};
   hazptr_rec* next_{nullptr};
   std::atomic<bool> active_{false};
@@ -397,9 +454,10 @@ FOLLY_ALWAYS_INLINE hazptr_array<M>::hazptr_array(
     hazptr_array&& other) noexcept {
   DEBUG_PRINT(this << " " << M << " " << &other);
   auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+  auto hother = reinterpret_cast<hazptr_holder*>(&other.raw_);
   for (size_t i = 0; i < M; ++i) {
-    new (&h[i]) hazptr_holder(std::move(other.h_[i]));
-    DEBUG_PRINT(i << " " << &h[i] << " " << &other.h_[i]);
+    new (&h[i]) hazptr_holder(std::move(hother[i]));
+    DEBUG_PRINT(i << " " << &h[i] << " " << &hother[i]);
   }
   empty_ = other.empty_;
   other.empty_ = true;
@@ -481,10 +539,10 @@ FOLLY_ALWAYS_INLINE hazptr_local<M>::hazptr_local() {
       auto& tc = *ptc;
       auto count = tc.count();
       if (M <= count) {
-#ifndef NDEBUG
-        DCHECK(!tc.local_);
-        tc.local_ = true;
-#endif
+        if (kIsDebug) {
+          DCHECK(!tc.local_);
+          tc.local_ = true;
+        }
         // Fast path
         for (size_t i = 0; i < M; ++i) {
           auto hprec = tc[i].hprec_;
@@ -511,13 +569,13 @@ FOLLY_ALWAYS_INLINE hazptr_local<M>::hazptr_local() {
 template <size_t M>
 FOLLY_ALWAYS_INLINE hazptr_local<M>::~hazptr_local() {
   if (LIKELY(!need_destruct_)) {
-#ifndef NDEBUG
-    auto ptc = hazptr_tc_tls();
-    DCHECK(ptc != nullptr);
-    auto& tc = *ptc;
-    DCHECK(tc.local_);
-    tc.local_ = false;
-#endif
+    if (kIsDebug) {
+      auto ptc = hazptr_tc_tls();
+      DCHECK(ptc != nullptr);
+      auto& tc = *ptc;
+      DCHECK(tc.local_);
+      tc.local_ = false;
+    }
     return;
   }
   // Slow path
@@ -547,6 +605,11 @@ FOLLY_ALWAYS_INLINE hazptr_domain& default_hazptr_domain() {
   return default_domain_;
 }
 
+template <typename T, typename D>
+FOLLY_ALWAYS_INLINE void hazptr_retire(T* obj, D reclaim) {
+  default_hazptr_domain().retire(obj, std::move(reclaim));
+}
+
 /** hazptr_rec */
 
 FOLLY_ALWAYS_INLINE void hazptr_rec::set(const void* p) noexcept {
@@ -594,6 +657,21 @@ inline const void* hazptr_obj::getObjPtr() const {
 
 /** hazptr_domain */
 
+template <typename T, typename D>
+void hazptr_domain::retire(T* obj, D reclaim) {
+  struct hazptr_retire_node : hazptr_obj {
+    std::unique_ptr<T, D> obj_;
+
+    hazptr_retire_node(T* obj, D reclaim) : obj_{obj, std::move(reclaim)} {}
+  };
+
+  auto node = new hazptr_retire_node(obj, std::move(reclaim));
+  node->reclaim_ = [](hazptr_obj* p) {
+    delete static_cast<hazptr_retire_node*>(p);
+  };
+  objRetire(node);
+}
+
 inline hazptr_domain::~hazptr_domain() {
   DEBUG_PRINT(this);
   { /* reclaim all remaining retired objects */
@@ -602,6 +680,7 @@ inline hazptr_domain::~hazptr_domain() {
     while (retired) {
       for (auto p = retired; p; p = next) {
         next = p->next_;
+        DEBUG_PRINT(this << " " << p << " " << p->reclaim_);
         (*(p->reclaim_))(p);
       }
       retired = retired_.exchange(nullptr);
@@ -866,9 +945,9 @@ inline void hazptr_tc_init() {
   auto& tc = tls_tc_data_;
   DEBUG_PRINT(&tc);
   tc.count_ = 0;
-#ifndef NDEBUG
-  tc.local_ = false;
-#endif
+  if (kIsDebug) {
+    tc.local_ = false;
+  }
 }
 
 inline void hazptr_tc_shutdown() {
@@ -918,8 +997,7 @@ inline void hazptr_priv::push(hazptr_obj* obj) {
     head_ = obj;
   }
   tail_ = obj;
-  ++rcount_;
-  if (domain.reachedThreshold(rcount_)) {
+  if (++rcount_ >= HAZPTR_PRIV_THRESHOLD) {
     pushAllToDomain();
   }
 }