/* * Copyright (c) Facebook, Inc. and its affiliates. * * This software may be used and distributed according to the terms of the * GNU General Public License version 2. */ #pragma once #include namespace facebook::eden { /** * Smart pointer to automatically manage RCU resources. * * For details about RCU: https://en.wikipedia.org/wiki/Read-copy-update */ template < typename T, typename RcuTag = folly::RcuTag, typename Deleter = std::default_delete> class RcuPtr { public: using RcuDomain = folly::rcu_domain; /** * Smart pointer that ensures the proper use of the rcu_reader guard. * * The managed resource is guaranteed to be valid as long as this object is * alive. It is expected that an RcuLockedPtr is short lived, as live * RcuLockedPtr would prevent the RCU domain to synchronize, potentially * leading to memory from other RcuPtr from being reclaimed. */ class RcuLockedPtr { public: ~RcuLockedPtr() = default; RcuLockedPtr(const RcuLockedPtr&) = delete; RcuLockedPtr& operator=(const RcuLockedPtr&) = delete; RcuLockedPtr(RcuLockedPtr&& other) = default; RcuLockedPtr& operator=(RcuLockedPtr&&) = default; /** * Return a pointer to the inner resource. * * The lifetime of the returned value is the same as the RcuLockedPtr. */ T* get() const noexcept { return inner_; } T& operator*() const noexcept { return *inner_; } T* operator->() const noexcept { return inner_; } explicit operator bool() const noexcept { return inner_; } private: friend RcuPtr; /** * Construct the smart pointer. * * The RCU section needs to be created first to ensure that the pointer * isn't dangling. */ explicit RcuLockedPtr(RcuPtr& self) : guard_(&self.domain_), inner_(self.inner_.load(std::memory_order_acquire)) {} folly::rcu_reader_domain guard_; T* inner_; }; template explicit RcuPtr(RcuDomain& rcuDomain, Args&&... args) : domain_(rcuDomain), inner_(new T(std::forward(args)...)) {} RcuPtr(const RcuPtr&) = delete; RcuPtr& operator=(const RcuPtr&) = delete; RcuPtr(RcuPtr&&) = delete; RcuPtr& operator=(RcuPtr&&) = delete; /** * Destroy this RcuPtr. * * The underlying resource will be asynchronously freed. */ ~RcuPtr() { reset(); } void reset() { update_inner(nullptr); } /** * Obtain a reference to the inner resource. */ RcuLockedPtr rlock() noexcept { return RcuLockedPtr{*this}; } /** * Build a new resource in place. * * Returns the old resource. As concurrent threads may be holding a * RcuLockedPtr with the returned pointer, care must be taken to not free it * until they all RcuLockedPtr are destroyed. The use of RcuPtr::synchronize * after this can be used to that effect. */ template T* exchange(Args&&... args) noexcept { return exchange_inner(new T(std::forward(args)...)); } /** * Swap the inner resource and release it. * * The resource is freed asynchronously. */ template void update(Args&&... args) noexcept { update_inner(new T(std::forward(args)...)); } /** * Blocks until no RcuLockedPtr are live. * * This only waits until all the live RcuLockedPtr at the time the function * is called are destroyed. */ void synchronize() noexcept { domain_.synchronize(); } private: friend class RcuPtr::RcuLockedPtr; T* exchange_inner(T* inner) { return inner_.exchange(inner, std::memory_order_acq_rel); } void update_inner(T* inner) { auto old = exchange_inner(inner); domain_.call([old] { Deleter()(old); }); } RcuDomain& domain_; std::atomic inner_{}; }; } // namespace facebook::eden