Skip to content

Commit

Permalink
refactor: drop boost::signals2 in validationinterface
Browse files Browse the repository at this point in the history
Stop using boost::signals2 internally in validationinterface. Replace with
std::list and Add/Remove/Clear/Iterate helper functions.

Motivation for change is to reduce dependencies and avoid issues happening with
boost versions before 1.59:

bitcoin/bitcoin#18517
bitcoin/bitcoin#18471
  • Loading branch information
ryanofsky authored and janus committed Nov 4, 2020
1 parent 13eefc0 commit 400926d
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 33 deletions.
111 changes: 78 additions & 33 deletions src/validationinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,75 @@
#include <unordered_map>
#include <utility>

#include <boost/signals2/signal.hpp>

struct ValidationInterfaceConnections {
boost::signals2::scoped_connection UpdatedBlockTip;
boost::signals2::scoped_connection TransactionAddedToMempool;
boost::signals2::scoped_connection BlockConnected;
boost::signals2::scoped_connection BlockDisconnected;
boost::signals2::scoped_connection TransactionRemovedFromMempool;
boost::signals2::scoped_connection ChainStateFlushed;
boost::signals2::scoped_connection BlockChecked;
boost::signals2::scoped_connection NewPoWValidBlock;
};

//! The MainSignalsInstance manages a list of shared_ptr<CValidationInterface>
//! callbacks.
//!
//! A std::unordered_map is used to track what callbacks are currently
//! registered, and a std::list is to used to store the callbacks that are
//! currently registered as well as any callbacks that are just unregistered
//! and about to be deleted when they are done executing.
struct MainSignalsInstance {
boost::signals2::signal<void (const CBlockIndex *, const CBlockIndex *, bool fInitialDownload)> UpdatedBlockTip;
boost::signals2::signal<void (const CTransactionRef &)> TransactionAddedToMempool;
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex)> BlockConnected;
boost::signals2::signal<void (const std::shared_ptr<const CBlock>&, const CBlockIndex* pindex)> BlockDisconnected;
boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool;
boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed;
boost::signals2::signal<void (const CBlock&, const BlockValidationState&)> BlockChecked;
boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock;

private:
Mutex m_mutex;
//! List entries consist of a callback pointer and reference count. The
//! count is equal to the number of current executions of that entry, plus 1
//! if it's registered. It cannot be 0 because that would imply it is
//! unregistered and also not being executed (so shouldn't exist).
struct ListEntry { std::shared_ptr<CValidationInterface> callbacks; int count = 1; };
std::list<ListEntry> m_list GUARDED_BY(m_mutex);
std::unordered_map<CValidationInterface*, std::list<ListEntry>::iterator> m_map GUARDED_BY(m_mutex);

public:
// We are not allowed to assume the scheduler only runs in one thread,
// but must ensure all callbacks happen in-order, so we end up creating
// our own queue here :(
SingleThreadedSchedulerClient m_schedulerClient;
std::unordered_map<CValidationInterface*, ValidationInterfaceConnections> m_connMainSignals;

explicit MainSignalsInstance(CScheduler *pscheduler) : m_schedulerClient(pscheduler) {}

void Register(std::shared_ptr<CValidationInterface> callbacks)
{
LOCK(m_mutex);
auto inserted = m_map.emplace(callbacks.get(), m_list.end());
if (inserted.second) inserted.first->second = m_list.emplace(m_list.end());
inserted.first->second->callbacks = std::move(callbacks);
}

void Unregister(CValidationInterface* callbacks)
{
LOCK(m_mutex);
auto it = m_map.find(callbacks);
if (it != m_map.end()) {
if (!--it->second->count) m_list.erase(it->second);
m_map.erase(it);
}
}

//! Clear unregisters every previously registered callback, erasing every
//! map entry. After this call, the list may still contain callbacks that
//! are currently executing, but it will be cleared when they are done
//! executing.
void Clear()
{
LOCK(m_mutex);
for (auto it = m_list.begin(); it != m_list.end();) {
it = --it->count ? std::next(it) : m_list.erase(it);
}
m_map.clear();
}

template<typename F> void Iterate(F&& f)
{
WAIT_LOCK(m_mutex, lock);
for (auto it = m_list.begin(); it != m_list.end();) {
++it->count;
{
REVERSE_LOCK(lock);
f(*it->callbacks);
}
it = --it->count ? std::next(it) : m_list.erase(it);
}
}
};

static CMainSignals g_signals;
Expand Down Expand Up @@ -87,17 +126,23 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
conns.NewPoWValidBlock = g_signals.m_internals->NewPoWValidBlock.connect(std::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, std::placeholders::_1, std::placeholders::_2));
}

void RegisterSharedValidationInterface(std::shared_ptr<CValidationInterface> pwalletIn) {
// Each connection captures pwalletIn to ensure that each callback is
// executed before pwalletIn is destroyed. For more details see #18338.
g_signals.m_internals->Register(std::move(pwalletIn));
}

void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
if (g_signals.m_internals) {
g_signals.m_internals->m_connMainSignals.erase(pwalletIn);
g_signals.m_internals->Unregister(pwalletIn);
}
}

void UnregisterAllValidationInterfaces() {
if (!g_signals.m_internals) {
return;
}
g_signals.m_internals->m_connMainSignals.clear();
g_signals.m_internals->Clear();
}

void CallFunctionInValidationInterfaceQueue(std::function<void ()> func) {
Expand Down Expand Up @@ -137,7 +182,7 @@ void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockInd
// in the same critical section where the chain is updated

auto event = [pindexNew, pindexFork, fInitialDownload, this] {
m_internals->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload);
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload); });
};
ENQUEUE_AND_LOG_EVENT(event, "%s: new block hash=%s fork block hash=%s (in IBD=%s)", __func__,
pindexNew->GetBlockHash().ToString(),
Expand All @@ -147,7 +192,7 @@ void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockInd

void CMainSignals::TransactionAddedToMempool(const CTransactionRef &ptx) {
auto event = [ptx, this] {
m_internals->TransactionAddedToMempool(ptx);
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionAddedToMempool(ptx); });
};
ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__,
ptx->GetHash().ToString(),
Expand All @@ -156,7 +201,7 @@ void CMainSignals::TransactionAddedToMempool(const CTransactionRef &ptx) {

void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef &ptx) {
auto event = [ptx, this] {
m_internals->TransactionRemovedFromMempool(ptx);
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(ptx); });
};
ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__,
ptx->GetHash().ToString(),
Expand All @@ -165,7 +210,7 @@ void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef &ptx) {

void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex) {
auto event = [pblock, pindex, this] {
m_internals->BlockConnected(pblock, pindex);
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.BlockConnected(pblock, pindex); });
};
ENQUEUE_AND_LOG_EVENT(event, "%s: block hash=%s block height=%d", __func__,
pblock->GetHash().ToString(),
Expand All @@ -175,7 +220,7 @@ void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, c
void CMainSignals::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
{
auto event = [pblock, pindex, this] {
m_internals->BlockDisconnected(pblock, pindex);
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.BlockDisconnected(pblock, pindex); });
};
ENQUEUE_AND_LOG_EVENT(event, "%s: block hash=%s block height=%d", __func__,
pblock->GetHash().ToString(),
Expand All @@ -184,7 +229,7 @@ void CMainSignals::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock

void CMainSignals::ChainStateFlushed(const CBlockLocator &locator) {
auto event = [locator, this] {
m_internals->ChainStateFlushed(locator);
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.ChainStateFlushed(locator); });
};
ENQUEUE_AND_LOG_EVENT(event, "%s: block hash=%s", __func__,
locator.IsNull() ? "null" : locator.vHave.front().ToString());
Expand All @@ -193,10 +238,10 @@ void CMainSignals::ChainStateFlushed(const CBlockLocator &locator) {
void CMainSignals::BlockChecked(const CBlock& block, const BlockValidationState& state) {
LOG_EVENT("%s: block hash=%s state=%s", __func__,
block.GetHash().ToString(), state.ToString());
m_internals->BlockChecked(block, state);
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.BlockChecked(block, state); });
}

void CMainSignals::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock> &block) {
LOG_EVENT("%s: block hash=%s", __func__, block->GetHash().ToString());
m_internals->NewPoWValidBlock(pindex, block);
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.NewPoWValidBlock(pindex, block); });
}
1 change: 1 addition & 0 deletions src/validationinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class CValidationInterface {
friend void ::RegisterValidationInterface(CValidationInterface*);
friend void ::UnregisterValidationInterface(CValidationInterface*);
friend void ::UnregisterAllValidationInterfaces();
friend class CMainSignals;
};

struct MainSignalsInstance;
Expand Down

0 comments on commit 400926d

Please sign in to comment.