Changeset View
Changeset View
Standalone View
Standalone View
src/rcu.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 "sync.h" | #include "sync.h" | ||||
#include <cstdio> | #include <chrono> | ||||
#include <condition_variable> | |||||
std::atomic<uint64_t> RCUInfos::revision{0}; | std::atomic<uint64_t> RCUInfos::revision{0}; | ||||
thread_local RCUInfos RCUInfos::infos{}; | thread_local RCUInfos RCUInfos::infos{}; | ||||
/** | /** | ||||
* How many time a busy loop runs before yelding. | * How many time a busy loop runs before yelding. | ||||
*/ | */ | ||||
static const uint64_t RCU_ACTIVE_LOOP_COUNT = 30; | static const int RCU_ACTIVE_LOOP_COUNT = 30; | ||||
/** | /** | ||||
* We maintain a linked list of all the RCUInfos for each active thread. Upon | * We maintain a linked list of all the RCUInfos for each active thread. Upon | ||||
* start, a new thread adds itself to the head of the liked list and the node is | * start, a new thread adds itself to the head of the liked list and the node is | ||||
* then removed when the threads shuts down. | * then removed when the threads shuts down. | ||||
* | * | ||||
* Insertion is fairly straightforward. The first step is to set the next | * Insertion is fairly straightforward. The first step is to set the next | ||||
* pointer of the node being inserted as the first node in the list as follow: | * pointer of the node being inserted as the first node in the list as follow: | ||||
▲ Show 20 Lines • Show All 128 Lines • ▼ Show 20 Lines | while (true) { | ||||
* list of reader to check and may therefore not be waited for. | * list of reader to check and may therefore not be waited for. | ||||
*/ | */ | ||||
synchronize(); | synchronize(); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
void RCUInfos::synchronize() { | void RCUInfos::synchronize() { | ||||
uint64_t count = 0, syncRev = ++revision; | uint64_t syncRev = ++revision; | ||||
// Loop until all thread synced. | // Loop a few time lock free. | ||||
while (!hasSynced(syncRev)) { | for (int i = 0; i < RCU_ACTIVE_LOOP_COUNT; i++) { | ||||
if (count++ >= RCU_ACTIVE_LOOP_COUNT) { | if (hasSynced(syncRev)) { | ||||
// Make sure we don't starve the system by spinning too long without | // Done! | ||||
// yielding. | return; | ||||
std::this_thread::yield(); | |||||
} | } | ||||
} | } | ||||
// It seems like we have some contention. Let's try to not starve the | |||||
// system. Let's make sure threads that land here proceed one by one. | |||||
// XXX: The best option long term is most likely to use a futex on one of | |||||
// the thread causing synchronization delay so this thread can be waked up | |||||
// at an apropriate time. | |||||
static std::condition_variable cond; | |||||
static CWaitableCriticalSection cs; | |||||
WAIT_LOCK(cs, lock); | |||||
do { | |||||
cond.notify_one(); | |||||
} while (!cond.wait_for(lock, std::chrono::microseconds(1), | |||||
[&] { return hasSynced(syncRev); })); | |||||
} | } | ||||
bool RCUInfos::hasSynced(uint64_t syncRev) { | bool RCUInfos::hasSynced(uint64_t syncRev) { | ||||
// Go over the list and check all threads are past the synchronization | // Go over the list and check all threads are past the synchronization | ||||
// point. | // point. | ||||
RCULock lock(this); | RCULock lock(this); | ||||
RCUInfos *current = threadInfos.load(); | RCUInfos *current = threadInfos.load(); | ||||
while (current != nullptr) { | while (current != nullptr) { | ||||
Show All 10 Lines |