Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14865025
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
19 KB
Subscribers
None
View Options
diff --git a/src/rcu.h b/src/rcu.h
index 5a67fcae4..fbd425866 100644
--- a/src/rcu.h
+++ b/src/rcu.h
@@ -1,234 +1,234 @@
// Copyright (c) 2018-2019 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_RCU_H
#define BITCOIN_RCU_H
#include <atomic>
#include <cassert>
#include <cstdint>
#include <functional>
#include <map>
#include <ostream>
#include <type_traits>
#include <utility>
class RCUInfos;
class RCUReadLock;
class RCUInfos {
std::atomic<uint64_t> state;
std::atomic<RCUInfos *> next;
std::map<uint64_t, std::function<void()>> cleanups;
// The largest revision possible means unlocked.
static const uint64_t UNLOCKED = -uint64_t(1);
RCUInfos();
~RCUInfos();
void readLock() {
assert(!isLocked());
state.store(revision.load());
}
void readFree() {
assert(isLocked());
state.store(UNLOCKED);
}
bool isLocked() const { return state.load() != UNLOCKED; }
void registerCleanup(const std::function<void()> &f) {
cleanups.emplace(++revision, f);
}
void synchronize();
void runCleanups();
uint64_t hasSyncedTo(uint64_t cutoff = UNLOCKED);
friend class RCULock;
friend struct RCUTest;
static std::atomic<uint64_t> revision;
static thread_local RCUInfos infos;
};
class RCULock {
RCUInfos *infos;
explicit RCULock(RCUInfos *infosIn) : infos(infosIn) { infos->readLock(); }
friend class RCUInfos;
public:
RCULock() : RCULock(&RCUInfos::infos) {}
~RCULock() { infos->readFree(); }
RCULock(const RCULock &) = delete;
RCULock &operator=(const RCULock &) = delete;
static bool isLocked() { return RCUInfos::infos.isLocked(); }
static void registerCleanup(const std::function<void()> &f) {
RCUInfos::infos.registerCleanup(f);
}
static void synchronize() { RCUInfos::infos.synchronize(); }
};
template <typename T> class RCUPtr {
T *ptr;
// Private construction, so factories have to be used.
explicit RCUPtr(T *ptrIn) : ptr(ptrIn) {}
public:
RCUPtr() : ptr(nullptr) {}
~RCUPtr() {
if (ptr != nullptr) {
- ptr->release();
+ ptr->decrementRefCount();
}
}
/**
* Acquire ownership of some pointer.
*/
static RCUPtr acquire(T *&ptrIn) {
RCUPtr ret(ptrIn);
ptrIn = nullptr;
return ret;
}
/**
* Construct a new object that is owned by the pointer.
*/
template <typename... Args> static RCUPtr make(Args &&...args) {
return RCUPtr(new T(std::forward<Args>(args)...));
}
/**
* Construct a new RCUPtr without transferring owership.
*/
static RCUPtr copy(T *ptr) {
if (ptr != nullptr) {
- ptr->acquire();
+ ptr->incrementRefCount();
}
return RCUPtr::acquire(ptr);
}
/**
* Copy semantic.
*/
RCUPtr(const RCUPtr &src) : ptr(src.ptr) {
if (ptr != nullptr) {
- ptr->acquire();
+ ptr->incrementRefCount();
}
}
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;
}
/**
* Get allows to access the undelying pointer. RCUPtr keeps ownership.
*/
T *get() { return ptr; }
const T *get() const { return ptr; }
/**
* Release transfers ownership of the pointer from RCUPtr to the caller.
*/
T *release() {
T *oldPtr = ptr;
ptr = nullptr;
return oldPtr;
}
/**
* Operator overloading for convenience.
*/
T *operator->() { return ptr; }
const T *operator->() const { return ptr; }
T &operator*() { return *ptr; }
const T &operator*() const { return *ptr; }
explicit operator bool() const { return ptr != nullptr; }
/**
* Equality checks.
*/
friend bool operator==(const RCUPtr &lhs, const T *rhs) {
return lhs.get() == rhs;
}
friend bool operator==(const RCUPtr &lhs, const RCUPtr &rhs) {
return lhs == rhs.get();
}
friend bool operator!=(const RCUPtr &lhs, const T *rhs) {
return !(lhs == rhs);
}
friend bool operator!=(const RCUPtr &lhs, const RCUPtr &rhs) {
return !(lhs == rhs);
}
/**
* ostream support.
*/
friend std::ostream &operator<<(std::ostream &stream, const RCUPtr &rhs) {
return stream << rhs.ptr;
}
};
#define IMPLEMENT_RCU_REFCOUNT(T) \
private: \
mutable std::atomic<T> refcount{0}; \
\
- void acquire() const { refcount++; } \
+ void incrementRefCount() const { refcount++; } \
\
bool tryDecrement() const { \
T count = refcount.load(); \
while (count > 0) { \
if (refcount.compare_exchange_weak(count, count - 1)) { \
return true; \
} \
} \
\
return false; \
} \
\
- void release() const { \
+ void decrementRefCount() const { \
if (tryDecrement()) { \
return; \
} \
\
RCULock::registerCleanup([this] { \
if (tryDecrement()) { \
return; \
} \
\
delete this; \
}); \
} \
\
static_assert(std::is_integral<T>::value, "T must be an integral type."); \
static_assert(std::is_unsigned<T>::value, "T must be unsigned."); \
\
template <typename> friend class ::RCUPtr
#endif // BITCOIN_RCU_H
diff --git a/src/test/rcu_tests.cpp b/src/test/rcu_tests.cpp
index 8235deb1b..23a5bd11e 100644
--- a/src/test/rcu_tests.cpp
+++ b/src/test/rcu_tests.cpp
@@ -1,392 +1,392 @@
// Copyright (c) 2018-2019 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <rcu.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
#include <chrono>
struct RCUTest {
static uint64_t getRevision() { return RCUInfos::revision.load(); }
static uint64_t hasSyncedTo(uint64_t syncRev) {
return RCUInfos::infos.hasSyncedTo(syncRev);
}
static std::map<uint64_t, std::function<void()>> &getCleanups() {
return RCUInfos::infos.cleanups;
}
};
BOOST_FIXTURE_TEST_SUITE(rcu_tests, BasicTestingSetup)
enum RCUTestStep {
Init,
Locked,
LockAck,
RCULocked,
Synchronizing,
Synchronized,
};
#define WAIT_FOR_STEP(step) \
do { \
cond.notify_all(); \
} while (!cond.wait_for(lock, std::chrono::milliseconds(1), \
[&] { return otherstep == step; }))
void synchronize(std::atomic<RCUTestStep> &step,
const std::atomic<RCUTestStep> &otherstep, Mutex &cs,
std::condition_variable &cond,
std::atomic<uint64_t> &syncRev) {
assert(step == RCUTestStep::Init);
{
WAIT_LOCK(cs, lock);
step = RCUTestStep::Locked;
// Wait for our lock to be acknowledged.
WAIT_FOR_STEP(RCUTestStep::LockAck);
RCULock rculock;
// Update step.
step = RCUTestStep::RCULocked;
// Wait for master.
WAIT_FOR_STEP(RCUTestStep::RCULocked);
}
// Update step.
syncRev = RCUTest::getRevision() + 1;
step = RCUTestStep::Synchronizing;
assert(!RCUTest::hasSyncedTo(syncRev));
// We wait for readers.
RCULock::synchronize();
// Update step.
step = RCUTestStep::Synchronized;
}
void lockAndWaitForSynchronize(std::atomic<RCUTestStep> &step,
const std::atomic<RCUTestStep> &otherstep,
Mutex &cs, std::condition_variable &cond,
std::atomic<uint64_t> &syncRev) {
assert(step == RCUTestStep::Init);
WAIT_LOCK(cs, lock);
// Wait for th eother thread to be locked.
WAIT_FOR_STEP(RCUTestStep::Locked);
step = RCUTestStep::LockAck;
// Wait for the synchronizing tread to take its RCU lock.
WAIT_FOR_STEP(RCUTestStep::RCULocked);
assert(!RCUTest::hasSyncedTo(syncRev));
{
RCULock rculock;
// Update master step.
step = RCUTestStep::RCULocked;
while (RCUTest::getRevision() < syncRev) {
WAIT_FOR_STEP(RCUTestStep::Synchronizing);
}
assert(RCUTest::getRevision() >= syncRev);
assert(otherstep.load() == RCUTestStep::Synchronizing);
}
assert(RCUTest::hasSyncedTo(syncRev) >= syncRev);
WAIT_FOR_STEP(RCUTestStep::Synchronized);
}
static const int COUNT = 128;
BOOST_AUTO_TEST_CASE(synchronize_test) {
Mutex cs;
std::condition_variable cond;
std::atomic<RCUTestStep> parentstep;
std::atomic<RCUTestStep> childstep;
std::atomic<uint64_t> syncRev;
for (int i = 0; i < COUNT; i++) {
parentstep = RCUTestStep::Init;
childstep = RCUTestStep::Init;
syncRev = RCUTest::getRevision() + 1;
std::thread tlock([&] {
lockAndWaitForSynchronize(parentstep, childstep, cs, cond, syncRev);
});
std::thread tsync(
[&] { synchronize(childstep, parentstep, cs, cond, syncRev); });
tlock.join();
tsync.join();
}
// Needed to suppress "Test case [...] did not check any assertions"
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_CASE(cleanup_test) {
RCULock::synchronize();
BOOST_CHECK(RCUTest::getCleanups().empty());
bool isClean1 = false;
RCULock::registerCleanup([&] { isClean1 = true; });
BOOST_CHECK(!isClean1);
BOOST_CHECK_EQUAL(RCUTest::getCleanups().size(), 1);
BOOST_CHECK_EQUAL(RCUTest::getRevision(),
RCUTest::getCleanups().begin()->first);
// Synchronize runs the cleanups.
RCULock::synchronize();
BOOST_CHECK(RCUTest::getCleanups().empty());
BOOST_CHECK(isClean1);
// Check multiple callbacks.
isClean1 = false;
bool isClean2 = false;
bool isClean3 = false;
RCULock::registerCleanup([&] { isClean1 = true; });
RCULock::registerCleanup([&] { isClean2 = true; });
RCULock::registerCleanup([&] { isClean3 = true; });
BOOST_CHECK_EQUAL(RCUTest::getCleanups().size(), 3);
RCULock::synchronize();
BOOST_CHECK(RCUTest::getCleanups().empty());
BOOST_CHECK(isClean1);
BOOST_CHECK(isClean2);
BOOST_CHECK(isClean3);
// Check callbacks adding each others.
isClean1 = false;
isClean2 = false;
isClean3 = false;
RCULock::registerCleanup([&] {
isClean1 = true;
RCULock::registerCleanup([&] {
isClean2 = true;
RCULock::registerCleanup([&] { isClean3 = true; });
});
});
BOOST_CHECK_EQUAL(RCUTest::getCleanups().size(), 1);
RCULock::synchronize();
BOOST_CHECK(RCUTest::getCleanups().empty());
BOOST_CHECK(isClean1);
BOOST_CHECK(isClean2);
BOOST_CHECK(isClean3);
}
class RCURefTestItem {
IMPLEMENT_RCU_REFCOUNT(uint32_t);
const std::function<void()> cleanupfun;
public:
explicit RCURefTestItem(const std::function<void()> &fun)
: cleanupfun(fun) {}
~RCURefTestItem() { cleanupfun(); }
uint32_t getRefCount() const { return refcount.load(); }
};
BOOST_AUTO_TEST_CASE(rcuptr_test) {
// Make sure it works for null.
{
RCURefTestItem *ptr = nullptr;
RCUPtr<RCURefTestItem>::copy(ptr);
RCUPtr<RCURefTestItem>::acquire(ptr);
}
// Check the destruction mechanism.
bool isDestroyed = false;
{
auto rcuptr = RCUPtr<RCURefTestItem>::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<RCURefTestItem> gptr;
{
auto rcuptr = RCUPtr<RCURefTestItem>::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<RCURefTestItem>();
BOOST_CHECK(!isDestroyed);
RCULock::synchronize();
BOOST_CHECK(isDestroyed);
}
BOOST_AUTO_TEST_CASE(rcuptr_operator_test) {
auto gptr = RCUPtr<RCURefTestItem>();
auto ptr = new RCURefTestItem([] {});
auto oldPtr = ptr;
auto altptr = RCUPtr<RCURefTestItem>::make([] {});
// Check various operators.
BOOST_CHECK_EQUAL(gptr.get(), NULLPTR(RCURefTestItem));
BOOST_CHECK_EQUAL(gptr, NULLPTR(RCURefTestItem));
BOOST_CHECK(!gptr);
auto copyptr = gptr;
BOOST_CHECK(gptr == nullptr);
BOOST_CHECK(gptr != oldPtr);
BOOST_CHECK(gptr == copyptr);
BOOST_CHECK(gptr != altptr);
gptr = RCUPtr<RCURefTestItem>::acquire(ptr);
BOOST_CHECK_EQUAL(ptr, NULLPTR(RCURefTestItem));
BOOST_CHECK_EQUAL(gptr.get(), oldPtr);
BOOST_CHECK_EQUAL(&*gptr, oldPtr);
BOOST_CHECK_EQUAL(gptr, oldPtr);
BOOST_CHECK(gptr);
copyptr = gptr;
BOOST_CHECK(gptr != nullptr);
BOOST_CHECK(gptr == oldPtr);
BOOST_CHECK(gptr == copyptr);
BOOST_CHECK(gptr != altptr);
}
BOOST_AUTO_TEST_CASE(const_rcuptr_test) {
bool isDestroyed = false;
auto ptr = RCUPtr<const RCURefTestItem>::make([&] { isDestroyed = true; });
// Now let's destroy it.
ptr = RCUPtr<const RCURefTestItem>();
BOOST_CHECK(!isDestroyed);
RCULock::synchronize();
BOOST_CHECK(isDestroyed);
}
class RCURefMoveTestItem {
const std::function<void()> cleanupfun;
public:
explicit RCURefMoveTestItem(const std::function<void()> &fun)
: cleanupfun(fun) {}
~RCURefMoveTestItem() { cleanupfun(); }
- void acquire() {
+ void incrementRefCount() {
throw std::runtime_error("RCUPtr incremented the refcount");
}
- void release() {
+ void decrementRefCount() {
RCULock::registerCleanup([this] { delete this; });
}
};
BOOST_AUTO_TEST_CASE(move_rcuptr_test) {
bool isDestroyed = false;
// Check tat copy is failing.
auto rcuptr1 =
RCUPtr<RCURefMoveTestItem>::make([&] { isDestroyed = true; });
- BOOST_CHECK_THROW(rcuptr1->acquire(), std::runtime_error);
+ BOOST_CHECK_THROW(rcuptr1->incrementRefCount(), 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<RCURefMoveTestItem>::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);
// Acquire/release workflow.
isDestroyed = false;
auto ptr = new RCURefMoveTestItem([&] { isDestroyed = true; });
auto ptrCopy = ptr;
BOOST_CHECK_THROW(RCUPtr<RCURefMoveTestItem>::copy(ptr),
std::runtime_error);
rcuptr1 = RCUPtr<RCURefMoveTestItem>::acquire(ptr);
BOOST_CHECK_EQUAL(rcuptr1, ptrCopy);
BOOST_CHECK_EQUAL(ptr, NULLPTR(RCURefMoveTestItem));
ptr = rcuptr1.release();
BOOST_CHECK_EQUAL(rcuptr1, NULLPTR(RCURefMoveTestItem));
BOOST_CHECK_EQUAL(ptr, ptrCopy);
RCULock::synchronize();
BOOST_CHECK(!isDestroyed);
RCUPtr<RCURefMoveTestItem>::acquire(ptr);
BOOST_CHECK_EQUAL(ptr, NULLPTR(RCURefMoveTestItem));
BOOST_CHECK(!isDestroyed);
RCULock::synchronize();
BOOST_CHECK(isDestroyed);
}
BOOST_AUTO_TEST_SUITE_END()
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, May 22, 00:29 (14 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5866216
Default Alt Text
(19 KB)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment