diff --git a/src/rcu.h b/src/rcu.h --- a/src/rcu.h +++ b/src/rcu.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include class RCUInfos; class RCUReadLock; @@ -72,4 +74,90 @@ static void synchronize() { RCUInfos::infos.synchronize(); } }; +template class RCUPtr { + T *ptr; + +public: + RCUPtr() : ptr(nullptr) {} + explicit RCUPtr(T *ptrIn) : ptr(ptrIn) {} + + ~RCUPtr() { + if (ptr != nullptr) { + ptr->release(); + } + } + + /** + * Construct a new object that is owned by the pointer. + */ + template static RCUPtr make(Args &&... args) { + return RCUPtr(new T(std::forward(args)...)); + } + + /** + * Copy semantic. + */ + RCUPtr(const RCUPtr &src) : ptr(src.ptr) { + if (ptr != nullptr) { + ptr->acquire(); + } + } + + RCUPtr &operator=(const RCUPtr &rhs) { + RCUPtr tmp(rhs); + std::swap(ptr, tmp.ptr); + return *this; + } + + /** + * Move semantic. + */ + RCUPtr(RCUPtr &&src) : RCUPtr() { std::swap(ptr, src.ptr); } + RCUPtr &operator=(RCUPtr &&rhs) { + std::swap(ptr, rhs.ptr); + return *this; + } + + /** + * Accessors + */ + T *operator->() { return ptr; } +}; + +#define IMPLEMENT_RCU_REFCOUNT(T) \ +private: \ + std::atomic refcount{0}; \ + \ + void acquire() { refcount++; } \ + \ + bool tryDecrement() { \ + T count = refcount.load(); \ + while (count > 0) { \ + if (refcount.compare_exchange_weak(count, count - 1)) { \ + return true; \ + } \ + } \ + \ + return false; \ + } \ + \ + void release() { \ + if (tryDecrement()) { \ + return; \ + } \ + \ + RCULock::registerCleanup([this] { \ + if (tryDecrement()) { \ + return; \ + } \ + \ + delete this; \ + }); \ + } \ + \ + static_assert(std::is_integral::value, "T must be an integral type."); \ + static_assert(std::is_unsigned::value, "T must be unsigned."); \ + \ + template friend class ::RCUPtr + #endif // BITCOIN_RCU_H diff --git a/src/test/rcu_tests.cpp b/src/test/rcu_tests.cpp --- a/src/test/rcu_tests.cpp +++ b/src/test/rcu_tests.cpp @@ -186,4 +186,132 @@ BOOST_CHECK(isClean3); } +class RCURefTestItem { + IMPLEMENT_RCU_REFCOUNT(uint32_t); + const std::function cleanupfun; + +public: + explicit RCURefTestItem(const std::function &fun) + : cleanupfun(fun) {} + ~RCURefTestItem() { cleanupfun(); } + + uint32_t getRefCount() const { return refcount.load(); } +}; + +BOOST_AUTO_TEST_CASE(rcuref_test) { + // Make sure it works for null. + { RCUPtr rcuptr(nullptr); } + + // Check the destruction mechanism. + bool isDestroyed = false; + + { + auto rcuptr = RCUPtr::make([&] { isDestroyed = true; }); + BOOST_CHECK_EQUAL(rcuptr->getRefCount(), 0); + } + + // rcuptr waits for synchronization to destroy. + BOOST_CHECK(!isDestroyed); + RCULock::synchronize(); + BOOST_CHECK(isDestroyed); + + // Check that copy behaves properly. + isDestroyed = false; + RCUPtr gptr; + + { + auto rcuptr = RCUPtr::make([&] { isDestroyed = true; }); + BOOST_CHECK_EQUAL(rcuptr->getRefCount(), 0); + + gptr = rcuptr; + BOOST_CHECK_EQUAL(rcuptr->getRefCount(), 1); + BOOST_CHECK_EQUAL(gptr->getRefCount(), 1); + + auto rcuptrcopy = rcuptr; + BOOST_CHECK_EQUAL(rcuptrcopy->getRefCount(), 2); + BOOST_CHECK_EQUAL(rcuptr->getRefCount(), 2); + BOOST_CHECK_EQUAL(gptr->getRefCount(), 2); + } + + BOOST_CHECK_EQUAL(gptr->getRefCount(), 0); + RCULock::synchronize(); + BOOST_CHECK(!isDestroyed); + + gptr = RCUPtr(); + BOOST_CHECK(!isDestroyed); + RCULock::synchronize(); + BOOST_CHECK(isDestroyed); +} + +class RCURefMoveTestItem { + const std::function cleanupfun; + +public: + explicit RCURefMoveTestItem(const std::function &fun) + : cleanupfun(fun) {} + ~RCURefMoveTestItem() { cleanupfun(); } + + void acquire() { + throw std::runtime_error("RCUPtr incremented the refcount"); + } + void release() { + RCULock::registerCleanup([this] { delete this; }); + } +}; + +BOOST_AUTO_TEST_CASE(move_rcuref_test) { + bool isDestroyed = false; + + // Check tat copy is failing. + auto rcuptr1 = + RCUPtr::make([&] { isDestroyed = true; }); + BOOST_CHECK_THROW(rcuptr1->acquire(), std::runtime_error); + BOOST_CHECK_THROW(auto rcuptrcopy = rcuptr1;, std::runtime_error); + + // Try to move. + auto rcuptr2 = std::move(rcuptr1); + RCULock::synchronize(); + BOOST_CHECK(!isDestroyed); + + // Move to a local and check proper destruction. + { auto rcuptr3 = std::move(rcuptr2); } + + BOOST_CHECK(!isDestroyed); + RCULock::synchronize(); + BOOST_CHECK(isDestroyed); + + // Let's try to swap. + isDestroyed = false; + rcuptr1 = RCUPtr::make([&] { isDestroyed = true; }); + std::swap(rcuptr1, rcuptr2); + + RCULock::synchronize(); + BOOST_CHECK(!isDestroyed); + + // Chain moves to make sure there are no double free. + { + auto rcuptr3 = std::move(rcuptr2); + auto rcuptr4 = std::move(rcuptr3); + std::swap(rcuptr1, rcuptr4); + } + + RCULock::synchronize(); + BOOST_CHECK(!isDestroyed); + + // Check we can return from a function. + { + auto r = ([&] { + auto moved = std::move(rcuptr1); + return moved; + })(); + + RCULock::synchronize(); + BOOST_CHECK(!isDestroyed); + } + + BOOST_CHECK(!isDestroyed); + RCULock::synchronize(); + BOOST_CHECK(isDestroyed); +} + BOOST_AUTO_TEST_SUITE_END()