Changeset View
Changeset View
Standalone View
Standalone View
src/test/rcu_tests.cpp
// Copyright (c) 2018 The Bitcoin developers | // Copyright (c) 2018 The Bitcoin developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include "rcu.h" | #include "rcu.h" | ||||
#include "test/test_bitcoin.h" | #include "test/test_bitcoin.h" | ||||
#include <boost/test/unit_test.hpp> | #include <boost/test/unit_test.hpp> | ||||
#include <chrono> | #include <chrono> | ||||
struct RCUTest { | struct RCUTest { | ||||
static uint64_t getRevision() { return RCUInfos::revision.load(); } | static uint64_t getRevision() { return RCUInfos::revision.load(); } | ||||
static uint64_t hasSynced(uint64_t syncRev) { | static uint64_t hasSyncedTo(uint64_t syncRev) { | ||||
return RCUInfos::infos.hasSynced(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) | BOOST_FIXTURE_TEST_SUITE(rcu_tests, BasicTestingSetup) | ||||
enum RCUTestStep { | enum RCUTestStep { | ||||
Init, | Init, | ||||
Locked, | Locked, | ||||
Show All 30 Lines | assert(step == RCUTestStep::Init); | ||||
// Wait for master. | // Wait for master. | ||||
WAIT_FOR_STEP(RCUTestStep::RCULocked); | WAIT_FOR_STEP(RCUTestStep::RCULocked); | ||||
} | } | ||||
// Update step. | // Update step. | ||||
syncRev = RCUTest::getRevision() + 1; | syncRev = RCUTest::getRevision() + 1; | ||||
step = RCUTestStep::Synchronizing; | step = RCUTestStep::Synchronizing; | ||||
assert(!RCUTest::hasSynced(syncRev)); | assert(!RCUTest::hasSyncedTo(syncRev)); | ||||
// We wait for readers. | // We wait for readers. | ||||
RCULock::synchronize(); | RCULock::synchronize(); | ||||
// Update step. | // Update step. | ||||
step = RCUTestStep::Synchronized; | step = RCUTestStep::Synchronized; | ||||
} | } | ||||
void lockAndWaitForSynchronize(std::atomic<RCUTestStep> &step, | void lockAndWaitForSynchronize(std::atomic<RCUTestStep> &step, | ||||
const std::atomic<RCUTestStep> &otherstep, | const std::atomic<RCUTestStep> &otherstep, | ||||
CWaitableCriticalSection &cs, | CWaitableCriticalSection &cs, | ||||
std::condition_variable &cond, | std::condition_variable &cond, | ||||
std::atomic<uint64_t> &syncRev) { | std::atomic<uint64_t> &syncRev) { | ||||
assert(step == RCUTestStep::Init); | assert(step == RCUTestStep::Init); | ||||
WAIT_LOCK(cs, lock); | WAIT_LOCK(cs, lock); | ||||
// Wait for th eother thread to be locked. | // Wait for th eother thread to be locked. | ||||
WAIT_FOR_STEP(RCUTestStep::Locked); | WAIT_FOR_STEP(RCUTestStep::Locked); | ||||
step = RCUTestStep::LockAck; | step = RCUTestStep::LockAck; | ||||
// Wait for the synchronizing tread to take its RCU lock. | // Wait for the synchronizing tread to take its RCU lock. | ||||
WAIT_FOR_STEP(RCUTestStep::RCULocked); | WAIT_FOR_STEP(RCUTestStep::RCULocked); | ||||
assert(!RCUTest::hasSynced(syncRev)); | assert(!RCUTest::hasSyncedTo(syncRev)); | ||||
{ | { | ||||
RCULock rculock; | RCULock rculock; | ||||
// Update master step. | // Update master step. | ||||
step = RCUTestStep::RCULocked; | step = RCUTestStep::RCULocked; | ||||
while (RCUTest::getRevision() < syncRev) { | while (RCUTest::getRevision() < syncRev) { | ||||
WAIT_FOR_STEP(RCUTestStep::Synchronizing); | WAIT_FOR_STEP(RCUTestStep::Synchronizing); | ||||
} | } | ||||
assert(RCUTest::getRevision() >= syncRev); | assert(RCUTest::getRevision() >= syncRev); | ||||
assert(otherstep.load() == RCUTestStep::Synchronizing); | assert(otherstep.load() == RCUTestStep::Synchronizing); | ||||
} | } | ||||
assert(RCUTest::hasSynced(syncRev)); | assert(RCUTest::hasSyncedTo(syncRev) >= syncRev); | ||||
WAIT_FOR_STEP(RCUTestStep::Synchronized); | WAIT_FOR_STEP(RCUTestStep::Synchronized); | ||||
} | } | ||||
static const int COUNT = 128; | static const int COUNT = 128; | ||||
BOOST_AUTO_TEST_CASE(synchronize_test) { | BOOST_AUTO_TEST_CASE(synchronize_test) { | ||||
CWaitableCriticalSection cs; | CWaitableCriticalSection cs; | ||||
std::condition_variable cond; | std::condition_variable cond; | ||||
Show All 13 Lines | for (int i = 0; i < COUNT; i++) { | ||||
std::thread tsync( | std::thread tsync( | ||||
[&] { synchronize(childstep, parentstep, cs, cond, syncRev); }); | [&] { synchronize(childstep, parentstep, cs, cond, syncRev); }); | ||||
tlock.join(); | tlock.join(); | ||||
tsync.join(); | tsync.join(); | ||||
} | } | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(cleanup_test) { | |||||
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); | |||||
} | |||||
BOOST_AUTO_TEST_SUITE_END() | BOOST_AUTO_TEST_SUITE_END() |