From: Maged Michael Date: Tue, 20 Sep 2016 20:47:17 +0000 (-0700) Subject: Draft prototype of hazard pointers C++ template library X-Git-Tag: v2016.09.26.00~10 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=6a5f4337df0cc9faad63b1e02753e8c6d98655c5;p=folly.git Draft prototype of hazard pointers C++ template library Summary: Make draft of hazard pointers prototype public Reviewed By: djwatson Differential Revision: D3870280 fbshipit-source-id: e029efa336585055f67687059e10ae11766f8d7f --- diff --git a/folly/experimental/hazptr/debug.h b/folly/experimental/hazptr/debug.h new file mode 100644 index 00000000..a2278943 --- /dev/null +++ b/folly/experimental/hazptr/debug.h @@ -0,0 +1,27 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#define DO_DEBUG true + +#define DEBUG_PRINT(...) \ + do { \ + if (DO_DEBUG) { \ + VLOG(2) << __func__ << " --- " << __VA_ARGS__; \ + } \ + } while (false) diff --git a/folly/experimental/hazptr/example/LockFreeLIFO.h b/folly/experimental/hazptr/example/LockFreeLIFO.h new file mode 100644 index 00000000..397bdcc7 --- /dev/null +++ b/folly/experimental/hazptr/example/LockFreeLIFO.h @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +namespace folly { +namespace hazptr { + +template +class LockFreeLIFO { + class Node : public hazptr_obj_base { + friend LockFreeLIFO; + public: + ~Node() { + DEBUG_PRINT(this); + } + private: + Node(T v, Node* n) : value_(v), next_(n) { + DEBUG_PRINT(this); + } + T value_; + Node* next_; + }; + + public: + LockFreeLIFO() { + DEBUG_PRINT(this); + } + + ~LockFreeLIFO() { + DEBUG_PRINT(this); + } + + void push(T val) { + DEBUG_PRINT(this); + auto pnode = new Node(val, head_.load()); + while (!head_.compare_exchange_weak(pnode->next_, pnode)); + } + + bool pop(T& val) { + DEBUG_PRINT(this); + hazptr_owner hptr; + Node* pnode; + while (true) { + if ((pnode = head_.load()) == nullptr) return false; + if (!hptr.protect(pnode, head_)) continue; + auto next = pnode->next_; + if (head_.compare_exchange_weak(pnode, next)) break; + } + hptr.clear(); + val = pnode->value_; + pnode->retire(); + return true; + } + + private: + std::atomic head_ = {nullptr}; +}; + +} // namespace folly { +} // namespace hazptr { diff --git a/folly/experimental/hazptr/example/SWMRList.h b/folly/experimental/hazptr/example/SWMRList.h new file mode 100644 index 00000000..344786b2 --- /dev/null +++ b/folly/experimental/hazptr/example/SWMRList.h @@ -0,0 +1,138 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +namespace folly { +namespace hazptr { + +/** Set implemented as an ordered singly-linked list. + * + * A single writer thread may add or remove elements. Multiple reader + * threads may search the set concurrently with each other and with + * the writer's operations. + */ +template +class SWMRListSet { + class Node : public hazptr_obj_base { + friend SWMRListSet; + T elem_; + std::atomic next_; + + Node(T e, Node* n) : elem_(e), next_(n) { + DEBUG_PRINT(this << " " << e << " " << n); + } + + public: + ~Node() { + DEBUG_PRINT(this); + } + }; + + std::atomic head_ = {nullptr}; + hazptr_domain* domain_; + hazptr_obj_reclaim reclaim_ = [](Node* p) { reclaim(p); }; + + static void reclaim(Node* p) { + DEBUG_PRINT(p << " " << sizeof(Node)); + delete p; + }; + + /* Used by the single writer */ + void locate_lower_bound(T v, std::atomic*& prev) { + auto curr = prev->load(); + while (curr) { + if (curr->elem_ >= v) break; + prev = &(curr->next_); + curr = curr->next_.load(); + } + return; + } + + public: + explicit SWMRListSet(hazptr_domain* domain = default_hazptr_domain()) + : domain_(domain) {} + + ~SWMRListSet() { + Node* next; + for (auto p = head_.load(); p; p = next) { + next = p->next_.load(); + delete p; + } + domain_->flush(&reclaim_); /* avoid destruction order fiasco */ + } + + bool add(T v) { + auto prev = &head_; + locate_lower_bound(v, prev); + auto curr = prev->load(); + if (curr && curr->elem_ == v) return false; + prev->store(new Node(v, curr)); + return true; + } + + bool remove(T v) { + auto prev = &head_; + locate_lower_bound(v, prev); + auto curr = prev->load(); + if (!curr || curr->elem_ != v) return false; + prev->store(curr->next_.load()); + curr->retire(domain_, &reclaim_); + return true; + } + /* Used by readers */ + bool contains(T val) { + /* Acquire two hazard pointers for hand-over-hand traversal. */ + hazptr_owner hptr_prev(domain_); + hazptr_owner hptr_curr(domain_); + T elem; + bool done = false; + while (!done) { + auto prev = &head_; + auto curr = prev->load(); + while (true) { + if (!curr) { done = true; break; } + if (!hptr_curr.protect(curr, *prev)) break; + auto next = curr->next_.load(); + elem = curr->elem_; + // Load-load order + std::atomic_thread_fence(std::memory_order_acquire); + if (prev->load() != curr) break; + if (elem >= val) { done = true; break; } + 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); + } + } + return elem == val; + /* The hazard pointers are released automatically. */ + } +}; + +} // namespace folly { +} // namespace hazptr { diff --git a/folly/experimental/hazptr/example/WideCAS.h b/folly/experimental/hazptr/example/WideCAS.h new file mode 100644 index 00000000..dd6461b7 --- /dev/null +++ b/folly/experimental/hazptr/example/WideCAS.h @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +#include + +namespace folly { +namespace hazptr { + +/** Wide CAS. + */ +class WideCAS { + using T = std::string; + class Node : public hazptr_obj_base { + friend WideCAS; + T val_; + Node() : val_(T()) { DEBUG_PRINT(this << " " << val_); } + explicit Node(T v) : val_(v) { DEBUG_PRINT(this << " " << v); } + public: + ~Node() { DEBUG_PRINT(this); } + }; + + std::atomic p_ = {new Node()}; + + public: + WideCAS() = default; + ~WideCAS() { + DEBUG_PRINT(this << " " << p_.load()); + delete p_.load(); + } + + bool cas(T& u, T& v) { + DEBUG_PRINT(this << " " << u << " " << v); + Node* n = new Node(v); + hazptr_owner hptr; + Node* p = p_.load(); + do { + if (!hptr.protect(p, p_)) continue; + if (p->val_ != u) { delete n; return false; } + if (p_.compare_exchange_weak(p, n)) break; + } while (true); + hptr.clear(); + p->retire(); + DEBUG_PRINT(this << " " << p << " " << u << " " << n << " " << v); + return true; + } +}; + +} // namespace folly { +} // namespace hazptr { diff --git a/folly/experimental/hazptr/hazptr-impl.h b/folly/experimental/hazptr/hazptr-impl.h new file mode 100644 index 00000000..2c64e9c5 --- /dev/null +++ b/folly/experimental/hazptr/hazptr-impl.h @@ -0,0 +1,335 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * 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" +#endif + +#include + +#include + +namespace folly { +namespace hazptr { + +/** hazptr_domain */ + +constexpr hazptr_domain::hazptr_domain(memory_resource* mr) noexcept + : mr_(mr) {} + +template +void hazptr_domain::flush(const hazptr_obj_reclaim* reclaim) { + DEBUG_PRINT(this << " " << reclaim); + flush(reinterpret_cast*>(reclaim)); +} + +template +inline void hazptr_domain::objRetire(hazptr_obj_base* p) { + DEBUG_PRINT(this << " " << p); + objRetire(reinterpret_cast*>(p)); +} + +/** hazptr_obj_base */ + +template +inline void hazptr_obj_base::retire( + hazptr_domain* domain, + const hazptr_obj_reclaim* reclaim, + const storage_policy /* policy */) { + DEBUG_PRINT(this << " " << reclaim << " " << &domain); + reclaim_ = reclaim; + domain->objRetire(this); +} + +/* Definition of default_hazptr_obj_reclaim */ + +template +inline hazptr_obj_reclaim* default_hazptr_obj_reclaim() { + static hazptr_obj_reclaim fn = [](T* p) { + DEBUG_PRINT("default_hazptr_obj_reclaim " << p << " " << sizeof(T)); + delete p; + }; + DEBUG_PRINT(&fn); + return &fn; +} + +/** hazptr_rec */ + +class hazptr_rec { + friend class hazptr_domain; + template friend class hazptr_owner; + + std::atomic hazptr_ = {nullptr}; + hazptr_rec* next_ = {nullptr}; + std::atomic active_ = {false}; + + void set(const void* p) noexcept; + const void* get() const noexcept; + void clear() noexcept; + void release() noexcept; +}; + +/** hazptr_owner */ + +template +inline hazptr_owner::hazptr_owner( + hazptr_domain* domain, + const cache_policy /* policy */) { + domain_ = domain; + hazptr_ = domain_->hazptrAcquire(); + DEBUG_PRINT(this << " " << domain_ << " " << hazptr_); + if (hazptr_ == nullptr) { std::bad_alloc e; throw e; } +} + +template +hazptr_owner::~hazptr_owner() noexcept { + DEBUG_PRINT(this); + domain_->hazptrRelease(hazptr_); +} + +template +inline bool hazptr_owner::protect(const T* ptr, const std::atomic& src) + const noexcept { + DEBUG_PRINT(this << " " << ptr << " " << &src); + hazptr_->set(ptr); + // ORDER: store-load + return (src.load() == ptr); +} + +template +inline void hazptr_owner::set(const T* ptr) const noexcept { + DEBUG_PRINT(this << " " << ptr); + hazptr_->set(ptr); +} + +template +inline void hazptr_owner::clear() const noexcept { + DEBUG_PRINT(this); + hazptr_->clear(); +} + +template +inline void swap(hazptr_owner& lhs, hazptr_owner& rhs) noexcept { + DEBUG_PRINT( + &lhs << " " << lhs.hazptr_ << " " << lhs.domain_ << " -- " + << &rhs << " " << rhs.hazptr_ << " " << rhs.domain_); + std::swap(lhs.domain_, rhs.domain_); + std::swap(lhs.hazptr_, rhs.hazptr_); +} + +//////////////////////////////////////////////////////////////////////////////// +// Non-template part of implementation +//////////////////////////////////////////////////////////////////////////////// +// [TODO]: +// - Thread caching of hazptr_rec-s +// - Private storage of retired objects +// - Optimized memory order + +/** Definition of default_hazptr_domain() */ +inline hazptr_domain* default_hazptr_domain() { + static hazptr_domain d; + return &d; +} + +/** hazptr_rec */ + +inline void hazptr_rec::set(const void* p) noexcept { + DEBUG_PRINT(this << " " << p); + hazptr_.store(p); +} + +inline const void* hazptr_rec::get() const noexcept { + DEBUG_PRINT(this << " " << hazptr_.load()); + return hazptr_.load(); +} + +inline void hazptr_rec::clear() noexcept { + DEBUG_PRINT(this); + // ORDER: release + hazptr_.store(nullptr); +} + +inline void hazptr_rec::release() noexcept { + DEBUG_PRINT(this); + clear(); + // ORDER: release + active_.store(false); +} + +/** hazptr_domain */ + +inline hazptr_domain::~hazptr_domain() { + DEBUG_PRINT(this); + { /* free all hazptr_rec-s */ + hazptr_rec* next; + for (auto p = hazptrs_.load(); p; p = next) { + next = p->next_; + mr_->deallocate(static_cast(p), sizeof(hazptr_rec)); + } + } + { /* reclaim all remaining retired objects */ + hazptr_obj* next; + for (auto p = retired_.load(); p; p = next) { + next = p->next_; + (*(p->reclaim_))(p); + } + } +} + +inline void hazptr_domain::flush() { + DEBUG_PRINT(this); + auto rcount = rcount_.exchange(0); + auto p = retired_.exchange(nullptr); + hazptr_obj* next; + for (; p; p = next) { + next = p->next_; + (*(p->reclaim_))(p); + --rcount; + } + rcount_.fetch_add(rcount); +} + +inline hazptr_rec* hazptr_domain::hazptrAcquire() { + hazptr_rec* p; + hazptr_rec* next; + for (p = hazptrs_.load(); p; p = next) { + next = p->next_; + bool active = p->active_.load(); + if (!active) { + if (p->active_.compare_exchange_weak(active, true)) { + DEBUG_PRINT(this << " " << p); + return p; + } + } + } + p = static_cast(mr_->allocate(sizeof(hazptr_rec))); + if (p == nullptr) { + return nullptr; + } + p->active_.store(true); + do { + p->next_ = hazptrs_.load(); + if (hazptrs_.compare_exchange_weak(p->next_, p)) { + break; + } + } while (true); + auto hcount = hcount_.fetch_add(1); + DEBUG_PRINT(this << " " << p << " " << sizeof(hazptr_rec) << " " << hcount); + return p; +} + +inline void hazptr_domain::hazptrRelease(hazptr_rec* p) const noexcept { + DEBUG_PRINT(this << " " << p); + p->release(); +} + +inline int +hazptr_domain::pushRetired(hazptr_obj* head, hazptr_obj* tail, int count) { + tail->next_ = retired_.load(); + // ORDER: store-store order + while (!retired_.compare_exchange_weak(tail->next_, head)) {} + return rcount_.fetch_add(count); +} + +inline void hazptr_domain::objRetire(hazptr_obj* p) { + auto rcount = pushRetired(p, p, 1) + 1; + if (rcount >= kScanThreshold * hcount_.load()) { + bulkReclaim(); + } +} + +inline void hazptr_domain::bulkReclaim() { + DEBUG_PRINT(this); + auto h = hazptrs_.load(); + auto hcount = hcount_.load(); + auto rcount = rcount_.load(); + do { + if (rcount < kScanThreshold * hcount) { + return; + } + if (rcount_.compare_exchange_weak(rcount, 0)) { + break; + } + } while (true); + /* ORDER: store-load order between removing each object and scanning + * the hazard pointers -- can be combined in one fence */ + std::unordered_set hs; + for (; h; h = h->next_) { + hs.insert(h->hazptr_.load()); + } + rcount = 0; + hazptr_obj* retired = nullptr; + hazptr_obj* tail = nullptr; + auto p = retired_.exchange(nullptr); + hazptr_obj* next; + for (; p; p = next) { + next = p->next_; + if (hs.count(p) == 0) { + (*(p->reclaim_))(p); + } else { + p->next_ = retired; + retired = p; + if (tail == nullptr) { + tail = p; + } + ++rcount; + } + } + if (tail) { + pushRetired(retired, tail, rcount); + } +} + +inline void hazptr_domain::flush(const hazptr_obj_reclaim* reclaim) { + DEBUG_PRINT(this << " " << reclaim); + auto rcount = rcount_.exchange(0); + auto p = retired_.exchange(nullptr); + hazptr_obj* retired = nullptr; + hazptr_obj* tail = nullptr; + hazptr_obj* next; + for (; p; p = next) { + next = p->next_; + if (p->reclaim_ == reclaim) { + (*reclaim)(p); + } else { + p->next_ = retired; + retired = p; + if (tail == nullptr) { + tail = p; + } + ++rcount; + } + } + if (tail) { + pushRetired(retired, tail, rcount); + } +} + +/** hazptr_user */ + +inline void hazptr_user::flush() { + DEBUG_PRINT(""); +} + +/** hazptr_remover */ + +inline void hazptr_remover::flush() { + DEBUG_PRINT(""); +} + +} // namespace folly +} // namespace hazptr diff --git a/folly/experimental/hazptr/hazptr.h b/folly/experimental/hazptr/hazptr.h new file mode 100644 index 00000000..329dbe64 --- /dev/null +++ b/folly/experimental/hazptr/hazptr.h @@ -0,0 +1,196 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#define HAZPTR_H + +#include +#include +#include + +/* Stand-in for std::pmr::memory_resource */ +#include + +namespace folly { +namespace hazptr { + +/** hazptr_rec: Private class that contains hazard pointers. */ +class hazptr_rec; + +/** hazptr_obj_base: Base template for objects protected by hazard pointers. */ +template class hazptr_obj_base; + +/** Alias for object reclamation function template */ +template using hazptr_obj_reclaim = std::function; + +/** hazptr_domain: Class of hazard pointer domains. Each domain manages a set + * of hazard pointers and a set of retired objects. */ +class hazptr_domain { + public: + constexpr explicit hazptr_domain( + memory_resource* = get_default_resource()) noexcept; + ~hazptr_domain(); + + hazptr_domain(const hazptr_domain&) = delete; + hazptr_domain(hazptr_domain&&) = delete; + hazptr_domain& operator=(const hazptr_domain&) = delete; + hazptr_domain& operator=(hazptr_domain&&) = delete; + + /* Reclaim all retired objects with a specific reclamation + * function currently stored by this domain */ + template void flush(const hazptr_obj_reclaim* reclaim); + /* Reclaim all retired objects currently stored by this domain */ + void flush(); + + private: + template friend class hazptr_obj_base; + template friend class hazptr_owner; + + using hazptr_obj = hazptr_obj_base; + + /** Constant -- May be changed to parameter in the future */ + enum { kScanThreshold = 3 }; + + memory_resource* mr_; + std::atomic hazptrs_ = {nullptr}; + std::atomic retired_ = {nullptr}; + std::atomic hcount_ = {0}; + std::atomic rcount_ = {0}; + + template void objRetire(hazptr_obj_base*); + hazptr_rec* hazptrAcquire(); + void hazptrRelease(hazptr_rec*) const noexcept; + void objRetire(hazptr_obj*); + int pushRetired(hazptr_obj* head, hazptr_obj* tail, int count); + void bulkReclaim(); + void flush(const hazptr_obj_reclaim* reclaim); +}; + +/** Get the default hazptr_domain */ +hazptr_domain* default_hazptr_domain(); + +/** Declaration of default reclamation function template */ +template hazptr_obj_reclaim* default_hazptr_obj_reclaim(); + +/** Definition of hazptr_obj_base */ +template class hazptr_obj_base { + public: + /* Policy for storing retired objects */ + enum class storage_policy { priv, shared }; + + /* Retire a removed object and pass the responsibility for + * reclaiming it to the hazptr library */ + void retire( + hazptr_domain* domain = default_hazptr_domain(), + const hazptr_obj_reclaim* reclaim = default_hazptr_obj_reclaim(), + const storage_policy policy = storage_policy::shared); + + private: + friend class hazptr_domain; + template friend class hazptr_owner; + + const hazptr_obj_reclaim* reclaim_; + hazptr_obj_base* next_; +}; + +/** hazptr_owner: Template for automatic acquisition and release of + * hazard pointers, and interface for hazard pointer operations. */ +template class hazptr_owner; + +/* Swap ownership of hazard ponters between hazptr_owner-s. */ +/* Note: The owned hazard pointers remain unmodified during the swap + * and continue to protect the respective objects that they were + * protecting before the swap, if any. */ +template +void swap(hazptr_owner&, hazptr_owner&) noexcept; + +template class hazptr_owner { + public: + /* Policy for caching hazard pointers */ + enum class cache_policy { cache, nocache }; + + /* Constructor automatically acquires a hazard pointer. */ + explicit hazptr_owner( + hazptr_domain* domain = default_hazptr_domain(), + const cache_policy policy = cache_policy::nocache); + + /* Destructor automatically clears and releases the owned hazard pointer. */ + ~hazptr_owner() noexcept; + + /* Copy and move constructors and assignment operators are + * disallowed because: + * - Each hazptr_owner owns exactly one hazard pointer at any time. + * - Each hazard pointer may have up to one owner at any time. */ + hazptr_owner(const hazptr_owner&) = delete; + hazptr_owner(hazptr_owner&&) = delete; + hazptr_owner& operator=(const hazptr_owner&) = delete; + hazptr_owner& operator=(hazptr_owner&&) = delete; + + /** Hazard pointer operations */ + /* Return true if successful in protecting the object */ + bool protect(const T* ptr, const std::atomic& src) const noexcept; + /* Set the hazard pointer to ptr */ + void set(const T* ptr) const noexcept; + /* Clear the hazard pointer */ + void clear() const noexcept; + + private: + friend void swap(hazptr_owner&, hazptr_owner&) noexcept; + + hazptr_domain* domain_; + hazptr_rec* hazptr_; +}; + +/** hazptr_user: Thread-specific interface for users of hazard + * pointers (i.e., threads that own hazard pointers by using + * hazptr_owner. */ +class hazptr_user { + public: + /* Release all hazptr_rec-s cached by this thread */ + static void flush(); +}; + +/** hazptr_remover: Thread-specific interface for removers of objects + * protected by hazard pointersd, i.e., threads that call the retire + * member function of hazptr_obj_base. */ +class hazptr_remover { + public: + /* Pass responsibility of reclaiming any retired objects stored + * privately by this thread to the hazptr_domain to which they + * belong. */ + static void flush(); +}; + +} // namespace hazptr +} // namespace folly + +//////////////////////////////////////////////////////////////////////////////// +/// Notes +//////////////////////////////////////////////////////////////////////////////// + +/* The hazptr_obj_base template uses a reclamation function as a + * parameter for the retire() member function instead of taking an + * allocator template as an extra template parameter because objects + * of the same type may need different reclamation functions. */ + +/* The hazptr interface supports reclamation by one domain at a + * time. If an abject belongs to multiple domains simultaneously, a + * workaround may be to design reclamation functions to form a series + * of retirements from one domain to the next until it reaches the + * final domain in the series that finally reclaims the object. */ + +//////////////////////////////////////////////////////////////////////////////// + +#include "hazptr-impl.h" diff --git a/folly/experimental/hazptr/memory_resource.h b/folly/experimental/hazptr/memory_resource.h new file mode 100644 index 00000000..d24abf69 --- /dev/null +++ b/folly/experimental/hazptr/memory_resource.h @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +namespace folly { +namespace hazptr { + +//////////////////////////////////////////////////////////////////////////////// +/// Disclaimer: This is intended only as a partial stand-in for +/// std::pmr::memory_resource (C++17) as needed for developing a +/// hazptr prototype. +//////////////////////////////////////////////////////////////////////////////// +#include + +class memory_resource { + public: + virtual ~memory_resource() = default; + virtual void* allocate( + const size_t bytes, + const size_t alignment = alignof(std::max_align_t)) = 0; + virtual void deallocate( + void* p, + const size_t bytes, + const size_t alignment = alignof(std::max_align_t)) = 0; +}; + +memory_resource* get_default_resource(); +void set_default_resource(memory_resource*); +memory_resource* new_delete_resource(); + +//////////////////////////////////////////////////////////////////////////////// +/// Implementation +//////////////////////////////////////////////////////////////////////////////// +#include + +inline memory_resource** default_mr_ptr() { + static memory_resource* default_mr = new_delete_resource(); + DEBUG_PRINT(&default_mr << " " << default_mr); + return &default_mr; +} + +inline memory_resource* get_default_resource() { + DEBUG_PRINT(""); + return *default_mr_ptr(); +} + +inline void set_default_resource(memory_resource* mr) { + DEBUG_PRINT(""); + *default_mr_ptr() = mr; +} + +inline memory_resource* new_delete_resource() { + class new_delete : public memory_resource { + public: + void* allocate( + const size_t bytes, + const size_t alignment = alignof(std::max_align_t)) { + (void)alignment; + void* p = static_cast(new char[bytes]); + DEBUG_PRINT(this << " " << p << " " << bytes); + return p; + } + void deallocate( + void* p, + const size_t bytes, + const size_t alignment = alignof(std::max_align_t)) { + (void)alignment; + (void)bytes; + DEBUG_PRINT(p << " " << bytes); + delete[] static_cast(p); + } + }; + static new_delete mr; + return &mr; +} + +} // namespace folly { +} // namespace hazptr { diff --git a/folly/experimental/hazptr/test/HazptrTest.cpp b/folly/experimental/hazptr/test/HazptrTest.cpp new file mode 100644 index 00000000..11da402e --- /dev/null +++ b/folly/experimental/hazptr/test/HazptrTest.cpp @@ -0,0 +1,264 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace folly::hazptr; + +static hazptr_obj_reclaim myReclaim_ = [](Node1* p) { + myReclaimFn(p); +}; + +static hazptr_obj_reclaim mineReclaim_ = [](Node2* p) { + mineReclaimFn(p); +}; + +TEST(Hazptr, Test1) { + DEBUG_PRINT("========== start of scope"); + DEBUG_PRINT(""); + Node1* node0 = new Node1; + DEBUG_PRINT("=== new node0 " << node0 << " " << sizeof(*node0)); + Node1* node1 = (Node1*)malloc(sizeof(Node1)); + DEBUG_PRINT("=== malloc node1 " << node1 << " " << sizeof(*node1)); + Node1* node2 = (Node1*)malloc(sizeof(Node1)); + DEBUG_PRINT("=== malloc node2 " << node2 << " " << sizeof(*node2)); + Node1* node3 = (Node1*)malloc(sizeof(Node1)); + DEBUG_PRINT("=== malloc node3 " << node3 << " " << sizeof(*node3)); + + DEBUG_PRINT(""); + + std::atomic shared0 = {node0}; + std::atomic shared1 = {node1}; + std::atomic shared2 = {node2}; + std::atomic shared3 = {node3}; + + MyMemoryResource myMr; + DEBUG_PRINT("=== myMr " << &myMr); + hazptr_domain myDomain0; + DEBUG_PRINT("=== myDomain0 " << &myDomain0); + hazptr_domain myDomain1(&myMr); + DEBUG_PRINT("=== myDomain1 " << &myDomain1); + + DEBUG_PRINT(""); + + DEBUG_PRINT("=== hptr0"); + hazptr_owner hptr0; + DEBUG_PRINT("=== hptr1"); + hazptr_owner hptr1(&myDomain0); + DEBUG_PRINT("=== hptr2"); + hazptr_owner hptr2(&myDomain1); + DEBUG_PRINT("=== hptr3"); + hazptr_owner hptr3; + + DEBUG_PRINT(""); + + Node1* n0 = shared0.load(); + Node1* n1 = shared1.load(); + Node1* n2 = shared2.load(); + Node1* n3 = shared3.load(); + + if (hptr0.protect(n0, shared0)) {} + if (hptr1.protect(n1, shared1)) {} + hptr1.clear(); + hptr1.set(n2); + if (hptr2.protect(n3, shared3)) {} + swap(hptr1, hptr2); + hptr3.clear(); + + DEBUG_PRINT(""); + + DEBUG_PRINT("=== retire n0 " << n0); + n0->retire(); + DEBUG_PRINT("=== retire n1 " << n1); + + n1->retire(default_hazptr_domain(), &myReclaim_); + DEBUG_PRINT("=== retire n2 " << n2); + n2->retire(&myDomain0, &myReclaim_); + DEBUG_PRINT("=== retire n3 " << n3); + n3->retire(&myDomain1, &myReclaim_); + + DEBUG_PRINT("========== end of scope"); +} + +TEST(Hazptr, Test2) { + DEBUG_PRINT("========== start of scope"); + Node2* node0 = new Node2; + DEBUG_PRINT("=== new node0 " << node0 << " " << sizeof(*node0)); + Node2* node1 = (Node2*)malloc(sizeof(Node2)); + DEBUG_PRINT("=== malloc node1 " << node1 << " " << sizeof(*node1)); + Node2* node2 = (Node2*)malloc(sizeof(Node2)); + DEBUG_PRINT("=== malloc node2 " << node2 << " " << sizeof(*node2)); + Node2* node3 = (Node2*)malloc(sizeof(Node2)); + DEBUG_PRINT("=== malloc node3 " << node3 << " " << sizeof(*node3)); + + DEBUG_PRINT(""); + + std::atomic shared0 = {node0}; + std::atomic shared1 = {node1}; + std::atomic shared2 = {node2}; + std::atomic shared3 = {node3}; + + MineMemoryResource mineMr; + DEBUG_PRINT("=== mineMr " << &mineMr); + hazptr_domain mineDomain0; + DEBUG_PRINT("=== mineDomain0 " << &mineDomain0); + hazptr_domain mineDomain1(&mineMr); + DEBUG_PRINT("=== mineDomain1 " << &mineDomain1); + + DEBUG_PRINT(""); + + DEBUG_PRINT("=== hptr0"); + hazptr_owner hptr0; + DEBUG_PRINT("=== hptr1"); + hazptr_owner hptr1(&mineDomain0); + DEBUG_PRINT("=== hptr2"); + hazptr_owner hptr2(&mineDomain1); + DEBUG_PRINT("=== hptr3"); + hazptr_owner hptr3; + + DEBUG_PRINT(""); + + Node2* n0 = shared0.load(); + Node2* n1 = shared1.load(); + Node2* n2 = shared2.load(); + Node2* n3 = shared3.load(); + + if (hptr0.protect(n0, shared0)) {} + if (hptr1.protect(n1, shared1)) {} + hptr1.clear(); + hptr1.set(n2); + if (hptr2.protect(n3, shared3)) {} + swap(hptr1, hptr2); + hptr3.clear(); + + DEBUG_PRINT(""); + + DEBUG_PRINT("=== retire n0 " << n0); + n0->retire(); + DEBUG_PRINT("=== retire n1 " << n1); + + n1->retire(default_hazptr_domain(), &mineReclaim_); + DEBUG_PRINT("=== retire n2 " << n2); + n2->retire(&mineDomain0, &mineReclaim_); + DEBUG_PRINT("=== retire n3 " << n3); + n3->retire(&mineDomain1, &mineReclaim_); + + DEBUG_PRINT("========== end of scope"); +} + +DEFINE_int32(num_threads, 1, "Number of threads"); +DEFINE_int64(num_reps, 1, "Number of test reps"); +DEFINE_int64(num_ops, 10, "Number of ops or pairs of ops per rep"); + +TEST(Hazptr, LIFO) { + using T = uint32_t; + DEBUG_PRINT("========== start of test scope"); + CHECK_GT(FLAGS_num_threads, 0); + for (int i = 0; i < FLAGS_num_reps; ++i) { + DEBUG_PRINT("========== start of rep scope"); + LockFreeLIFO s; + std::vector threads(FLAGS_num_threads); + for (int tid = 0; tid < FLAGS_num_threads; ++tid) { + threads[tid] = std::thread([&s, tid]() { + for (int j = tid; j < FLAGS_num_ops; j += FLAGS_num_threads) { + s.push(j); + T res; + while (!s.pop(res)) {} + } + }); + } + for (auto& t : threads) { + t.join(); + } + DEBUG_PRINT("========== end of rep scope"); + } + DEBUG_PRINT("========== end of test scope"); +} + +TEST(Hazptr, SWMRLIST) { + using T = uint64_t; + DEBUG_PRINT("========== start of test scope"); + hazptr_domain custom_domain; + + CHECK_GT(FLAGS_num_threads, 0); + for (int i = 0; i < FLAGS_num_reps; ++i) { + DEBUG_PRINT("========== start of rep scope"); + SWMRListSet s(&custom_domain); + std::vector threads(FLAGS_num_threads); + for (int tid = 0; tid < FLAGS_num_threads; ++tid) { + threads[tid] = std::thread([&s, tid]() { + for (int j = tid; j < FLAGS_num_ops; j += FLAGS_num_threads) { + s.contains(j); + } + }); + } + for (int j = 0; j < 10; ++j) { + s.add(j); + } + for (int j = 0; j < 10; ++j) { + s.remove(j); + } + for (auto& t : threads) { + t.join(); + } + DEBUG_PRINT("========== end of rep scope"); + } + DEBUG_PRINT("========== end of test scope"); +} + +TEST(Hazptr, WIDECAS) { + DEBUG_PRINT("========== start of test scope"); + + WideCAS s; + std::string u = ""; + std::string v = "11112222"; + auto ret = s.cas(u, v); + CHECK(ret); + u = ""; + v = "11112222"; + ret = s.cas(u, v); + CHECK(!ret); + u = "11112222"; + v = "22223333"; + ret = s.cas(u, v); + CHECK(ret); + u = "22223333"; + v = "333344445555"; + ret = s.cas(u, v); + CHECK(ret); + + DEBUG_PRINT("========== end of test scope"); +} + +int main(int argc, char** argv) { + DEBUG_PRINT("=================================================== start main"); + testing::InitGoogleTest(&argc, argv); + google::ParseCommandLineFlags(&argc, &argv, true); + auto ret = RUN_ALL_TESTS(); + default_hazptr_domain()->flush(); + DEBUG_PRINT("===================================================== end main"); + return ret; +} diff --git a/folly/experimental/hazptr/test/HazptrUse1.h b/folly/experimental/hazptr/test/HazptrUse1.h new file mode 100644 index 00000000..c633d10a --- /dev/null +++ b/folly/experimental/hazptr/test/HazptrUse1.h @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +namespace folly { +namespace hazptr { + +class MyMemoryResource : public memory_resource { + public: + void* allocate(const size_t sz, const size_t /* align */) { + void* p = malloc(sz); + DEBUG_PRINT(p << " " << sz); + return p; + } + + void deallocate(void* p, const size_t sz, const size_t /* align */) { + DEBUG_PRINT(p << " " << sz); + free(p); + } +}; + +class Node1 : public hazptr_obj_base { + char a[100]; +}; + +inline void myReclaimFn(Node1* p) { + DEBUG_PRINT(p << " " << sizeof(Node1)); + free(p); +} + +} // namespace folly { +} // namespace hazptr { diff --git a/folly/experimental/hazptr/test/HazptrUse2.h b/folly/experimental/hazptr/test/HazptrUse2.h new file mode 100644 index 00000000..d010c966 --- /dev/null +++ b/folly/experimental/hazptr/test/HazptrUse2.h @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +namespace folly { +namespace hazptr { + +class MineMemoryResource : public memory_resource { + public: + void* allocate(const size_t sz, const size_t /* align */) { + void* p = malloc(sz); + DEBUG_PRINT(p << " " << sz); + return p; + } + + void deallocate(void* p, const size_t sz, const size_t /* align */) { + DEBUG_PRINT(p << " " << sz); + free(p); + } +}; + +class Node2 : public hazptr_obj_base { + char a[200]; +}; + +inline void mineReclaimFn(Node2* p) { + DEBUG_PRINT(p << " " << sizeof(Node2)); + free(p); +} + +} // namespace folly { +} // namespace hazptr {