return res;
}
+const int ops = 1000000;
+
inline uint64_t listBench(std::string name, int nthreads, int size) {
- int ops = 100000;
auto repFn = [&] {
SWMRListSet<uint64_t> s;
auto init = [&] {
}
inline uint64_t holderBench(std::string name, int nthreads) {
- int ops = 100000;
auto repFn = [&] {
auto init = [] {};
auto fn = [&](int tid) {
return bench(name, ops, repFn);
}
+template <size_t M>
+inline uint64_t arrayBench(std::string name, int nthreads) {
+ auto repFn = [&] {
+ auto init = [] {};
+ auto fn = [&](int tid) {
+ for (int j = tid; j < 10 * ops; j += nthreads) {
+ hazptr_array<M> a;
+ }
+ };
+ auto endFn = [] {};
+ return run_once(nthreads, init, fn, endFn);
+ };
+ return bench(name, ops, repFn);
+}
+
+template <size_t M>
+inline uint64_t localBench(std::string name, int nthreads) {
+ auto repFn = [&] {
+ auto init = [] {};
+ auto fn = [&](int tid) {
+ for (int j = tid; j < 10 * ops; j += nthreads) {
+ hazptr_local<10> a;
+ }
+ };
+ auto endFn = [] {};
+ return run_once(nthreads, init, fn, endFn);
+ };
+ return bench(name, ops, repFn);
+}
+
inline uint64_t retireBench(std::string name, int nthreads) {
struct Foo : hazptr_obj_base<Foo> {
int x;
};
- int ops = 100000;
auto repFn = [&] {
auto init = [] {};
auto fn = [&](int tid) {
inline void benches(std::string name) {
std::cout << "------------------------------------------- " << name << "\n";
for (int i : nthr) {
- std::cout << i << " threads -- construct/destruct 10 hazptr_holder-s"
+ std::cout << i << " threads -- 10x construct/destruct hazptr_holder"
<< std::endl;
holderBench(name + " ", i);
holderBench(name + " - dup ", i);
+ std::cout << i << " threads -- 10x construct/destruct hazptr_array<10>"
+ << std::endl;
+ arrayBench<10>(name + " ", i);
+ arrayBench<10>(name + " - dup ", i);
+ std::cout << i << " threads -- 10x construct/destruct hazptr_array<3>"
+ << std::endl;
+ arrayBench<3>(name + " ", i);
+ arrayBench<3>(name + " - dup ", i);
+ std::cout << i << " threads -- 10x construct/destruct hazptr_local<10>"
+ << std::endl;
+ localBench<10>(name + " ", i);
+ localBench<10>(name + " - dup ", i);
+ std::cout << i << " threads -- 10x construct/destruct hazptr_local<1>"
+ << std::endl;
+ localBench<1>(name + " ", i);
+ localBench<1>(name + " - dup ", i);
std::cout << i << " threads -- allocate/retire/reclaim object" << std::endl;
retireBench(name + " ", i);
retireBench(name + " - dup ", i);
} // namespace folly
/*
+------------------------------------------- amb - tc
+1 threads -- 10x construct/destruct hazptr_holder
+ amb - tc 49 ns 46 ns 44 ns
+ amb - tc - dup 47 ns 45 ns 44 ns
+1 threads -- 10x construct/destruct hazptr_array<10>
+ amb - tc 132 ns 122 ns 117 ns
+ amb - tc - dup 130 ns 122 ns 117 ns
+1 threads -- 10x construct/destruct hazptr_array<3>
+ amb - tc 66 ns 64 ns 63 ns
+ amb - tc - dup 64 ns 64 ns 63 ns
+1 threads -- 10x construct/destruct hazptr_local<10>
+ amb - tc 29 ns 27 ns 27 ns
+ amb - tc - dup 28 ns 27 ns 27 ns
+1 threads -- 10x construct/destruct hazptr_local<1>
+ amb - tc 27 ns 27 ns 27 ns
+ amb - tc - dup 28 ns 28 ns 27 ns
+1 threads -- allocate/retire/reclaim object
+ amb - tc 65 ns 62 ns 60 ns
+ amb - tc - dup 65 ns 60 ns 59 ns
+1 threads -- 10-item list
+ amb - tc 21 ns 21 ns 20 ns
+ amb - tc - dup 22 ns 21 ns 21 ns
+1 threads -- 100-item list
+ amb - tc 229 ns 224 ns 220 ns
+ amb - tc - dup 223 ns 219 ns 216 ns
+10 threads -- 10x construct/destruct hazptr_holder
+ amb - tc 9 ns 8 ns 7 ns
+ amb - tc - dup 9 ns 8 ns 8 ns
+10 threads -- 10x construct/destruct hazptr_array<10>
+ amb - tc 27 ns 23 ns 15 ns
+ amb - tc - dup 26 ns 20 ns 13 ns
+10 threads -- 10x construct/destruct hazptr_array<3>
+ amb - tc 11 ns 11 ns 7 ns
+ amb - tc - dup 11 ns 9 ns 7 ns
+10 threads -- 10x construct/destruct hazptr_local<10>
+ amb - tc 5 ns 3 ns 3 ns
+ amb - tc - dup 3 ns 3 ns 3 ns
+10 threads -- 10x construct/destruct hazptr_local<1>
+ amb - tc 3 ns 3 ns 3 ns
+ amb - tc - dup 5 ns 4 ns 3 ns
+10 threads -- allocate/retire/reclaim object
+ amb - tc 17 ns 15 ns 14 ns
+ amb - tc - dup 17 ns 15 ns 14 ns
+10 threads -- 10-item list
+ amb - tc 4 ns 4 ns 2 ns
+ amb - tc - dup 4 ns 4 ns 3 ns
+10 threads -- 100-item list
+ amb - tc 33 ns 31 ns 24 ns
+ amb - tc - dup 33 ns 32 ns 30 ns
+----------------------------------------------------------
------------------------------------------- no amb - no tc
1 threads -- construct/destruct 10 hazptr_holder-s
no amb - no tc 2518 ns 2461 ns 2431 ns
10 threads -- 100-item list
no amb - tc 215 ns 208 ns 188 ns
no amb - tc - dup 215 ns 209 ns 197 ns
-----------------------------------------------------------
-------------------------------------------- amb - tc
-1 threads -- construct/destruct 10 hazptr_holder-s
- amb - tc 56 ns 54 ns 54 ns
- amb - tc - dup 55 ns 54 ns 53 ns
-1 threads -- allocate/retire/reclaim object
- amb - tc 62 ns 61 ns 61 ns
- amb - tc - dup 62 ns 61 ns 61 ns
-1 threads -- 10-item list
- amb - tc 36 ns 35 ns 33 ns
- amb - tc - dup 37 ns 35 ns 34 ns
-1 threads -- 100-item list
- amb - tc 262 ns 247 ns 230 ns
- amb - tc - dup 249 ns 238 ns 230 ns
-10 threads -- construct/destruct 10 hazptr_holder-s
- amb - tc 14 ns 12 ns 11 ns
- amb - tc - dup 12 ns 11 ns 11 ns
-10 threads -- allocate/retire/reclaim object
- amb - tc 18 ns 17 ns 15 ns
- amb - tc - dup 18 ns 17 ns 15 ns
-10 threads -- 10-item list
- amb - tc 9 ns 8 ns 8 ns
- amb - tc - dup 8 ns 8 ns 7 ns
-10 threads -- 100-item list
- amb - tc 52 ns 42 ns 28 ns
- amb - tc - dup 44 ns 37 ns 28 ns
-----------------------------------------------------------
-------------------------------------------- one domain
-1 threads -- construct/destruct 10 hazptr_holder-s
- one domain 57 ns 56 ns 55 ns
- one domain - dup 56 ns 54 ns 53 ns
-1 threads -- allocate/retire/reclaim object
- one domain 87 ns 71 ns 64 ns
- one domain - dup 69 ns 68 ns 68 ns
-1 threads -- 10-item list
- one domain 32 ns 30 ns 29 ns
- one domain - dup 31 ns 30 ns 29 ns
-1 threads -- 100-item list
- one domain 269 ns 238 ns 226 ns
- one domain - dup 237 ns 232 ns 227 ns
-10 threads -- construct/destruct 10 hazptr_holder-s
- one domain 16 ns 12 ns 10 ns
- one domain - dup 11 ns 10 ns 10 ns
-10 threads -- allocate/retire/reclaim object
- one domain 19 ns 17 ns 16 ns
- one domain - dup 19 ns 17 ns 15 ns
-10 threads -- 10-item list
- one domain 6 ns 5 ns 5 ns
- one domain - dup 6 ns 5 ns 5 ns
-10 threads -- 100-item list
- one domain 40 ns 39 ns 35 ns
- one domain - dup 40 ns 39 ns 35 ns
----------------------------------------------------------
*/
/* Used by readers */
bool contains(const T& val) const {
- /* Acquire two hazard pointers for hand-over-hand traversal. */
- hazptr_holder hptr_prev;
- hazptr_holder hptr_curr;
+ /* Two hazard pointers for hand-over-hand traversal. */
+ hazptr_local<2> hptr;
+ hazptr_holder* hptr_prev = &hptr[0];
+ hazptr_holder* hptr_curr = &hptr[1];
while (true) {
auto prev = &head_;
auto curr = prev->load(std::memory_order_acquire);
while (true) {
if (!curr) { return false; }
- if (!hptr_curr.try_protect(curr, *prev))
+ if (!hptr_curr->try_protect(curr, *prev))
break;
auto next = curr->next_.load(std::memory_order_acquire);
if (prev->load(std::memory_order_acquire) != curr)
}
prev = &(curr->next_);
curr = next;
- /* Swap does not change the values of the owned hazard
- * pointers themselves. After the swap, The hazard pointer
- * owned by hptr_prev continues to protect the node that
- * contains the pointer *prev. The hazard pointer owned by
- * hptr_curr will continue to protect the node that contains
- * the old *prev (unless the old prev was &head), which no
- * longer needs protection, so hptr_curr's hazard pointer is
- * now free to protect *curr in the next iteration (if curr !=
- * null).
- */
- swap(hptr_curr, hptr_prev);
+ std::swap(hptr_curr, hptr_prev);
}
}
- /* The hazard pointers are released automatically. */
}
};
struct hazptr_tc {
hazptr_tc_entry entry_[HAZPTR_TC_SIZE];
- int count_;
+ size_t count_;
+#ifndef NDEBUG
+ bool local_;
+#endif
public:
+ hazptr_tc_entry& operator[](size_t i);
hazptr_rec* get();
bool put(hazptr_rec* hprec);
+ size_t count();
};
static_assert(
std::is_trivial<hazptr_tc>::value,
"hazptr_tc must be trivial to avoid a branch to check initialization");
+hazptr_tc* hazptr_tc_tls();
void hazptr_tc_init();
void hazptr_tc_shutdown();
hazptr_rec* hazptr_tc_try_get();
if (hazptr_ == nullptr) { std::bad_alloc e; throw e; }
}
-FOLLY_ALWAYS_INLINE hazptr_holder::hazptr_holder(std::nullptr_t) {
+FOLLY_ALWAYS_INLINE hazptr_holder::hazptr_holder(std::nullptr_t) noexcept {
domain_ = nullptr;
hazptr_ = nullptr;
DEBUG_PRINT(this << " " << domain_ << " " << hazptr_);
lhs.swap(rhs);
}
+/**
+ * hazptr_array
+ */
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_array<M>::hazptr_array() {
+ auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+ if (HAZPTR_TC) {
+ auto ptc = hazptr_tc_tls();
+ if (LIKELY(ptc != nullptr)) {
+ auto& tc = *ptc;
+ auto count = tc.count();
+ if (M <= count) {
+ size_t offset = count - M;
+ for (size_t i = 0; i < M; ++i) {
+ auto hprec = tc[offset + i].hprec_;
+ DCHECK(hprec != nullptr);
+ DEBUG_PRINT(i << " " << &h[i]);
+ new (&h[i]) hazptr_holder(nullptr);
+ h[i].hazptr_ = hprec;
+ DEBUG_PRINT(
+ i << " " << &h[i] << " " << h[i].domain_ << " " << h[i].hazptr_);
+ }
+ tc.count_ = offset;
+ return;
+ }
+ }
+ }
+ // slow path
+ for (size_t i = 0; i < M; ++i) {
+ new (&h[i]) hazptr_holder;
+ DEBUG_PRINT(
+ i << " " << &h[i] << " " << h[i].domain_ << " " << h[i].hazptr_);
+ }
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_array<M>::hazptr_array(
+ hazptr_array&& other) noexcept {
+ DEBUG_PRINT(this << " " << M << " " << &other);
+ auto h = reinterpret_cast<hazptr_holder*>(&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]);
+ }
+ empty_ = other.empty_;
+ other.empty_ = true;
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_array<M>::hazptr_array(std::nullptr_t) noexcept {
+ DEBUG_PRINT(this << " " << M);
+ auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+ for (size_t i = 0; i < M; ++i) {
+ new (&h[i]) hazptr_holder(nullptr);
+ DEBUG_PRINT(i << " " << &h[i]);
+ }
+ empty_ = true;
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_array<M>::~hazptr_array() {
+ if (empty_) {
+ return;
+ }
+ auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+ if (HAZPTR_TC) {
+ auto ptc = hazptr_tc_tls();
+ if (LIKELY(ptc != nullptr)) {
+ auto& tc = *ptc;
+ auto count = tc.count();
+ if (count + M <= HAZPTR_TC_SIZE) {
+ for (size_t i = 0; i < M; ++i) {
+ tc[count + i].hprec_ = h[i].hazptr_;
+ DEBUG_PRINT(i << " " << &h[i]);
+ new (&h[i]) hazptr_holder(nullptr);
+ DEBUG_PRINT(
+ i << " " << &h[i] << " " << h[i].domain_ << " " << h[i].hazptr_);
+ }
+ tc.count_ = count + M;
+ return;
+ }
+ }
+ }
+ // slow path
+ for (size_t i = 0; i < M; ++i) {
+ h[i].~hazptr_holder();
+ }
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_array<M>& hazptr_array<M>::operator=(
+ hazptr_array&& other) noexcept {
+ DEBUG_PRINT(this << " " << M << " " << &other);
+ auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+ for (size_t i = 0; i < M; ++i) {
+ h[i] = std::move(other[i]);
+ DEBUG_PRINT(i << " " << &h[i] << " " << &other[i]);
+ }
+ return *this;
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_holder& hazptr_array<M>::operator[](
+ size_t i) noexcept {
+ auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+ DCHECK(i < M);
+ return h[i];
+}
+
+/**
+ * hazptr_local
+ */
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_local<M>::hazptr_local() {
+ auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+ if (HAZPTR_TC) {
+ auto ptc = hazptr_tc_tls();
+ if (LIKELY(ptc != nullptr)) {
+ auto& tc = *ptc;
+ auto count = tc.count();
+ if (M <= count) {
+#ifndef NDEBUG
+ DCHECK(!tc.local_);
+ tc.local_ = true;
+#endif
+ // Fast path
+ for (size_t i = 0; i < M; ++i) {
+ auto hprec = tc[i].hprec_;
+ DCHECK(hprec != nullptr);
+ DEBUG_PRINT(i << " " << &h[i]);
+ new (&h[i]) hazptr_holder(nullptr);
+ h[i].hazptr_ = hprec;
+ DEBUG_PRINT(
+ i << " " << &h[i] << " " << h[i].domain_ << " " << h[i].hazptr_);
+ }
+ return;
+ }
+ }
+ }
+ // Slow path
+ need_destruct_ = true;
+ for (size_t i = 0; i < M; ++i) {
+ new (&h[i]) hazptr_holder;
+ DEBUG_PRINT(
+ i << " " << &h[i] << " " << h[i].domain_ << " " << h[i].hazptr_);
+ }
+}
+
+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
+ return;
+ }
+ // Slow path
+ auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+ for (size_t i = 0; i < M; ++i) {
+ h[i].~hazptr_holder();
+ }
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_holder& hazptr_local<M>::operator[](
+ size_t i) noexcept {
+ auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+ DCHECK(i < M);
+ return h[i];
+}
+
////////////////////////////////////////////////////////////////////////////////
// [TODO]:
// - Control of reclamation (when and by whom)
/** hazptr_tc */
+FOLLY_ALWAYS_INLINE hazptr_tc_entry& hazptr_tc::operator[](size_t i) {
+ DCHECK(i <= HAZPTR_TC_SIZE);
+ return entry_[i];
+}
+
FOLLY_ALWAYS_INLINE hazptr_rec* hazptr_tc::get() {
if (LIKELY(count_ != 0)) {
auto hprec = entry_[--count_].get();
return false;
}
+FOLLY_ALWAYS_INLINE size_t hazptr_tc::count() {
+ return count_;
+}
+
/** hazptr_tc free functions */
-FOLLY_ALWAYS_INLINE hazptr_rec* hazptr_tc_try_get() {
- DEBUG_PRINT(TLS_UNINITIALIZED << TLS_ALIVE << TLS_DESTROYED);
+FOLLY_ALWAYS_INLINE hazptr_tc* hazptr_tc_tls() {
DEBUG_PRINT(tls_state_);
if (LIKELY(tls_state_ == TLS_ALIVE)) {
DEBUG_PRINT(tls_state_);
- return tls_tc_data_.get();
+ return &tls_tc_data_;
} else if (tls_state_ == TLS_UNINITIALIZED) {
tls_life_odr_use();
- return tls_tc_data_.get();
+ return &tls_tc_data_;
}
return nullptr;
}
-FOLLY_ALWAYS_INLINE bool hazptr_tc_try_put(hazptr_rec* hprec) {
- DEBUG_PRINT(tls_state_);
- if (LIKELY(tls_state_ == TLS_ALIVE)) {
- DEBUG_PRINT(tls_state_);
- return tls_tc_data_.put(hprec);
- }
- return false;
-}
-
inline void hazptr_tc_init() {
DEBUG_PRINT("");
auto& tc = tls_tc_data_;
DEBUG_PRINT(&tc);
tc.count_ = 0;
- for (int i = 0; i < HAZPTR_TC_SIZE; ++i) {
- tc.entry_[i].hprec_ = nullptr;
- }
+#ifndef NDEBUG
+ tc.local_ = false;
+#endif
}
inline void hazptr_tc_shutdown() {
auto& tc = tls_tc_data_;
DEBUG_PRINT(&tc);
- for (int i = 0; i < tc.count_; ++i) {
+ for (size_t i = 0; i < tc.count_; ++i) {
tc.entry_[i].evict();
}
}
+FOLLY_ALWAYS_INLINE hazptr_rec* hazptr_tc_try_get() {
+ DEBUG_PRINT(TLS_UNINITIALIZED << TLS_ALIVE << TLS_DESTROYED);
+ DEBUG_PRINT(tls_state_);
+ if (LIKELY(tls_state_ == TLS_ALIVE)) {
+ DEBUG_PRINT(tls_state_);
+ return tls_tc_data_.get();
+ } else if (tls_state_ == TLS_UNINITIALIZED) {
+ tls_life_odr_use();
+ return tls_tc_data_.get();
+ }
+ return nullptr;
+}
+
+FOLLY_ALWAYS_INLINE bool hazptr_tc_try_put(hazptr_rec* hprec) {
+ DEBUG_PRINT(tls_state_);
+ if (LIKELY(tls_state_ == TLS_ALIVE)) {
+ DEBUG_PRINT(tls_state_);
+ return tls_tc_data_.put(hprec);
+ }
+ return false;
+}
+
/**
* hazptr_priv
*/
template <typename T, typename Deleter>
class hazptr_obj_base;
+/** hazptr_local: Optimized template for bulk construction and destruction of
+ * hazard pointers */
+template <size_t M>
+class hazptr_array;
+
+/** hazptr_local: Optimized template for locally-used hazard pointers */
+template <size_t M>
+class hazptr_local;
+
/** hazptr_domain: Class of hazard pointer domains. Each domain manages a set
* of hazard pointers and a set of retired objects. */
class hazptr_domain {
/** hazptr_holder: Class for automatic acquisition and release of
* hazard pointers, and interface for hazard pointer operations. */
class hazptr_holder {
+ template <size_t M>
+ friend class hazptr_array;
+ template <size_t M>
+ friend class hazptr_local;
+
public:
/* Constructor automatically acquires a hazard pointer. */
explicit hazptr_holder(hazptr_domain& domain = default_hazptr_domain());
/* Construct an empty hazptr_holder. */
// Note: This diverges from the proposal in P0233R4
- explicit hazptr_holder(std::nullptr_t);
+ explicit hazptr_holder(std::nullptr_t) noexcept;
/* Destructor automatically clears and releases the owned hazard pointer. */
~hazptr_holder();
void swap(hazptr_holder&, hazptr_holder&) noexcept;
+using aligned_hazptr_holder = typename std::
+ aligned_storage<sizeof(hazptr_holder), alignof(hazptr_holder)>::type;
+
+/**
+ * hazptr_array: Optimized for bulk construction and destruction of
+ * hazptr_holder-s.
+ *
+ * WARNING: Do not move from or to individual hazptr_holder-s.
+ * Only move the whole hazptr_array.
+ */
+template <size_t M = 1>
+class hazptr_array {
+ static_assert(M > 0, "M must be a positive integer.");
+
+ public:
+ hazptr_array();
+ explicit hazptr_array(std::nullptr_t) noexcept;
+
+ hazptr_array(const hazptr_array&) = delete;
+ hazptr_array& operator=(const hazptr_array&) = delete;
+ hazptr_array(hazptr_array&& other) noexcept;
+ hazptr_array& operator=(hazptr_array&& other) noexcept;
+
+ ~hazptr_array();
+
+ hazptr_holder& operator[](size_t i) noexcept;
+
+ private:
+ aligned_hazptr_holder raw_[M];
+ bool empty_{false};
+};
+
+/**
+ * hazptr_local: Optimized for construction and destruction of
+ * one or more hazptr_holder-s with local scope.
+ *
+ * WARNING 1: Do not move from or to individual hazptr_holder-s.
+ *
+ * WARNING 2: There can only be one hazptr_local active for the same
+ * thread at any time. This is not tracked and checked by the
+ * implementation because it would negate the performance gains of
+ * this class.
+ */
+template <size_t M = 1>
+class hazptr_local {
+ static_assert(M > 0, "M must be a positive integer.");
+
+ public:
+ hazptr_local();
+ hazptr_local(const hazptr_local&) = delete;
+ hazptr_local& operator=(const hazptr_local&) = delete;
+ hazptr_local(hazptr_local&&) = delete;
+ hazptr_local& operator=(hazptr_local&&) = delete;
+
+ ~hazptr_local();
+
+ hazptr_holder& operator[](size_t i) noexcept;
+
+ private:
+ aligned_hazptr_holder raw_[M];
+ bool need_destruct_{false};
+};
+
} // namespace hazptr
} // namespace folly
hptr2.reset();
}
}
+
+TEST_F(HazptrTest, Array) {
+ struct Foo : hazptr_obj_base<Foo> {
+ int a;
+ };
+ for (int i = 0; i < 100; ++i) {
+ Foo* x = new Foo;
+ x->a = i;
+ hazptr_array<10> hptr;
+ // Protect object
+ hptr[9].reset(x);
+ // Empty array
+ hazptr_array<10> h;
+ // Move assignment
+ h = std::move(hptr);
+ // Retire object
+ x->retire();
+ // Unprotect object - hptr2 is nonempty
+ h[9].reset();
+ }
+ {
+ // Abnormal case
+ hazptr_array<HAZPTR_TC_SIZE + 1> h;
+ }
+}
+
+TEST_F(HazptrTest, Local) {
+ struct Foo : hazptr_obj_base<Foo> {
+ int a;
+ };
+ for (int i = 0; i < 100; ++i) {
+ Foo* x = new Foo;
+ x->a = i;
+ hazptr_local<10> hptr;
+ // Protect object
+ hptr[9].reset(x);
+ // Retire object
+ x->retire();
+ // Unprotect object - hptr2 is nonempty
+ hptr[9].reset();
+ }
+ {
+ // Abnormal case
+ hazptr_local<HAZPTR_TC_SIZE + 1> h;
+ }
+}