Skip to content

Commit

Permalink
Merge bitcoin#30967: refactor: Replace g_genesis_wait_cv with m_tip_b…
Browse files Browse the repository at this point in the history
…lock_cv

fa22e5c refactor: Remove dead code that assumed tip == nullptr (MarcoFalke)
fa2e443 refactor: Replace g_genesis_wait_cv with m_tip_block_cv (MarcoFalke)
fa7f52a refactor: Use wait_for predicate to check for interrupt (MarcoFalke)
5ca28ef refactor: Split up NodeContext shutdown_signal and shutdown_request (Ryan Ofsky)
fad8e7f bugfix: Mark m_tip_block_cv as guarded by m_tip_block_mutex (MarcoFalke)
fa18586 refactor: Add missing GUARDED_BY(m_tip_block_mutex) (MarcoFalke)
fa4c075 doc: Clarify waitTipChanged docs (MarcoFalke)

Pull request description:

  `g_genesis_wait_cv` is similar to `m_tip_block_cv` but shuffling everything through a redundant `boost::signals2`.

  So remove it, along with some other dead code, as well as minor fixups.

ACKs for top commit:
  ryanofsky:
    Code review ACK fa22e5c (just rebased since last review)
  Sjors:
    ACK fa22e5c
  TheCharlatan:
    ACK fa22e5c

Tree-SHA512: a2cb59b651aaf85a3574723adfe403487566788ad945933b0458816ccc841fce08ca77b31afbd2d6adb5bf1deed7229c028bee74fb4bbaf6576e9edcfa0ad817
  • Loading branch information
ryanofsky committed Oct 8, 2024
2 parents a9f6a57 + fa22e5c commit 5837e34
Show file tree
Hide file tree
Showing 20 changed files with 87 additions and 114 deletions.
2 changes: 1 addition & 1 deletion src/bitcoind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ MAIN_FUNCTION
if (ProcessInitCommands(args)) return EXIT_SUCCESS;

// Start application
if (!AppInit(node) || !Assert(node.shutdown)->wait()) {
if (!AppInit(node) || !Assert(node.shutdown_signal)->wait()) {
node.exit_status = EXIT_FAILURE;
}
Interrupt(node);
Expand Down
2 changes: 1 addition & 1 deletion src/index/base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ template <typename... Args>
void BaseIndex::FatalErrorf(util::ConstevalFormatString<sizeof...(Args)> fmt, const Args&... args)
{
auto message = tfm::format(fmt, args...);
node::AbortNode(m_chain->context()->shutdown, m_chain->context()->exit_status, Untranslated(message), m_chain->context()->warnings.get());
node::AbortNode(m_chain->context()->shutdown_request, m_chain->context()->exit_status, Untranslated(message), m_chain->context()->warnings.get());
}

CBlockLocator GetLocator(interfaces::Chain& chain, const uint256& block_hash)
Expand Down
76 changes: 28 additions & 48 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,14 @@ void InitContext(NodeContext& node)
g_shutdown.emplace();

node.args = &gArgs;
node.shutdown = &*g_shutdown;
node.shutdown_signal = &*g_shutdown;
node.shutdown_request = [&node] {
assert(node.shutdown_signal);
if (!(*node.shutdown_signal)()) return false;
// Wake any threads that may be waiting for the tip to change.
if (node.notifications) WITH_LOCK(node.notifications->m_tip_block_mutex, node.notifications->m_tip_block_cv.notify_all());
return true;
};
}

//////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -235,7 +242,7 @@ void InitContext(NodeContext& node)

bool ShutdownRequested(node::NodeContext& node)
{
return bool{*Assert(node.shutdown)};
return bool{*Assert(node.shutdown_signal)};
}

#if HAVE_SYSTEM
Expand Down Expand Up @@ -286,7 +293,7 @@ void Shutdown(NodeContext& node)

StopHTTPRPC();
StopREST();
StopRPC(&node);
StopRPC();
StopHTTPServer();
for (const auto& client : node.chain_clients) {
client->flush();
Expand Down Expand Up @@ -678,21 +685,6 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddHiddenArgs(hidden_args);
}

static bool fHaveGenesis = false;
static GlobalMutex g_genesis_wait_mutex;
static std::condition_variable g_genesis_wait_cv;

static void BlockNotifyGenesisWait(const CBlockIndex* pBlockIndex)
{
if (pBlockIndex != nullptr) {
{
LOCK(g_genesis_wait_mutex);
fHaveGenesis = true;
}
g_genesis_wait_cv.notify_all();
}
}

#if HAVE_SYSTEM
static void StartupNotify(const ArgsManager& args)
{
Expand All @@ -707,7 +699,7 @@ static void StartupNotify(const ArgsManager& args)
static bool AppInitServers(NodeContext& node)
{
const ArgsManager& args = *Assert(node.args);
if (!InitHTTPServer(*Assert(node.shutdown))) {
if (!InitHTTPServer(*Assert(node.shutdown_signal))) {
return false;
}
StartRPC();
Expand Down Expand Up @@ -1216,7 +1208,7 @@ static ChainstateLoadResult InitAndLoadChainstate(
};
Assert(ApplyArgsManOptions(args, blockman_opts)); // no error can happen, already checked in AppInitParameterInteraction
try {
node.chainman = std::make_unique<ChainstateManager>(*Assert(node.shutdown), chainman_opts, blockman_opts);
node.chainman = std::make_unique<ChainstateManager>(*Assert(node.shutdown_signal), chainman_opts, blockman_opts);
} catch (std::exception& e) {
return {ChainstateLoadStatus::FAILURE_FATAL, strprintf(Untranslated("Failed to initialize ChainstateManager: %s"), e.what())};
}
Expand Down Expand Up @@ -1327,7 +1319,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
constexpr uint64_t min_disk_space = 50 << 20; // 50 MB
if (!CheckDiskSpace(args.GetBlocksDirPath(), min_disk_space)) {
LogError("Shutting down due to lack of disk space!\n");
if (!(*Assert(node.shutdown))()) {
if (!(Assert(node.shutdown_request))()) {
LogError("Failed to send shutdown signal after disk space check\n");
}
}
Expand Down Expand Up @@ -1608,8 +1600,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)

// ********************************************************* Step 7: load block chain

node.notifications = std::make_unique<KernelNotifications>(*Assert(node.shutdown), node.exit_status, *Assert(node.warnings));
ReadNotificationArgs(args, *node.notifications);
node.notifications = std::make_unique<KernelNotifications>(Assert(node.shutdown_request), node.exit_status, *Assert(node.warnings));
auto& kernel_notifications{*node.notifications};
ReadNotificationArgs(args, kernel_notifications);

// cache size calculations
CacheSizes cache_sizes = CalculateCacheSizes(args, g_enabled_filter_types.size());
Expand Down Expand Up @@ -1649,7 +1642,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
return false;
}
do_reindex = true;
if (!Assert(node.shutdown)->reset()) {
if (!Assert(node.shutdown_signal)->reset()) {
LogError("Internal error: failed to reset shutdown signal.\n");
}
std::tie(status, error) = InitAndLoadChainstate(
Expand Down Expand Up @@ -1761,15 +1754,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
}
}

// Either install a handler to notify us when genesis activates, or set fHaveGenesis directly.
// No locking, as this happens before any background thread is started.
boost::signals2::connection block_notify_genesis_wait_connection;
if (WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip() == nullptr)) {
block_notify_genesis_wait_connection = uiInterface.NotifyBlockTip_connect(std::bind(BlockNotifyGenesisWait, std::placeholders::_2));
} else {
fHaveGenesis = true;
}

#if HAVE_SYSTEM
const std::string block_notify = args.GetArg("-blocknotify", "");
if (!block_notify.empty()) {
Expand All @@ -1794,7 +1778,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
ImportBlocks(chainman, vImportFiles);
if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
LogPrintf("Stopping after block import\n");
if (!(*Assert(node.shutdown))()) {
if (!(Assert(node.shutdown_request))()) {
LogError("Failed to send shutdown signal after finishing block import\n");
}
return;
Expand All @@ -1814,15 +1798,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
});

// Wait for genesis block to be processed
{
WAIT_LOCK(g_genesis_wait_mutex, lock);
// We previously could hang here if shutdown was requested prior to
// ImportBlocks getting started, so instead we just wait on a timer to
// check ShutdownRequested() regularly.
while (!fHaveGenesis && !ShutdownRequested(node)) {
g_genesis_wait_cv.wait_for(lock, std::chrono::milliseconds(500));
}
block_notify_genesis_wait_connection.disconnect();
if (WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip() == nullptr)) {
WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock);
kernel_notifications.m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
return !kernel_notifications.m_tip_block.IsNull() || ShutdownRequested(node);
});
}

if (ShutdownRequested(node)) {
Expand All @@ -1831,17 +1811,17 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)

// ********************************************************* Step 12: start node

//// debug print
int64_t best_block_time{};
{
LOCK(cs_main);
LOCK(chainman.GetMutex());
const auto& tip{*Assert(chainman.ActiveTip())};
LogPrintf("block tree size = %u\n", chainman.BlockIndex().size());
chain_active_height = chainman.ActiveChain().Height();
best_block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : chainman.GetParams().GenesisBlock().GetBlockTime();
chain_active_height = tip.nHeight;
best_block_time = tip.GetBlockTime();
if (tip_info) {
tip_info->block_height = chain_active_height;
tip_info->block_time = best_block_time;
tip_info->verification_progress = GuessVerificationProgress(chainman.GetParams().TxData(), chainman.ActiveChain().Tip());
tip_info->verification_progress = GuessVerificationProgress(chainman.GetParams().TxData(), &tip);
}
if (tip_info && chainman.m_best_header) {
tip_info->header_height = chainman.m_best_header->nHeight;
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/mining.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ class Mining
virtual std::optional<BlockRef> getTip() = 0;

/**
* Waits for the tip to change
* Waits for the connected tip to change. If the tip was not connected on
* startup, this will wait.
*
* @param[in] current_tip block hash of the current chain tip. Function waits
* for the chain tip to change if this matches, otherwise
* it returns right away.
* for the chain tip to differ from this.
* @param[in] timeout how long to wait for a new tip
* @returns Hash and height of the current chain tip after this call.
*/
Expand Down
4 changes: 2 additions & 2 deletions src/node/abort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@

namespace node {

void AbortNode(util::SignalInterrupt* shutdown, std::atomic<int>& exit_status, const bilingual_str& message, node::Warnings* warnings)
void AbortNode(const std::function<bool()>& shutdown_request, std::atomic<int>& exit_status, const bilingual_str& message, node::Warnings* warnings)
{
if (warnings) warnings->Set(Warning::FATAL_INTERNAL_ERROR, message);
InitError(_("A fatal internal error occurred, see debug.log for details: ") + message);
exit_status.store(EXIT_FAILURE);
if (shutdown && !(*shutdown)()) {
if (shutdown_request && !shutdown_request()) {
LogError("Failed to send shutdown signal\n");
};
}
Expand Down
7 changes: 2 additions & 5 deletions src/node/abort.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@
#define BITCOIN_NODE_ABORT_H

#include <atomic>
#include <functional>

struct bilingual_str;

namespace util {
class SignalInterrupt;
} // namespace util

namespace node {
class Warnings;
void AbortNode(util::SignalInterrupt* shutdown, std::atomic<int>& exit_status, const bilingual_str& message, node::Warnings* warnings);
void AbortNode(const std::function<bool()>& shutdown_request, std::atomic<int>& exit_status, const bilingual_str& message, node::Warnings* warnings);
} // namespace node

#endif // BITCOIN_NODE_ABORT_H
4 changes: 3 additions & 1 deletion src/node/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ struct NodeContext {
std::unique_ptr<ECC_Context> ecc_context;
//! Init interface for initializing current process and connecting to other processes.
interfaces::Init* init{nullptr};
//! Function to request a shutdown.
std::function<bool()> shutdown_request;
//! Interrupt object used to track whether node shutdown was requested.
util::SignalInterrupt* shutdown{nullptr};
util::SignalInterrupt* shutdown_signal{nullptr};
std::unique_ptr<AddrMan> addrman;
std::unique_ptr<CConnman> connman;
std::unique_ptr<CTxMemPool> mempool;
Expand Down
21 changes: 8 additions & 13 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,15 @@ class NodeImpl : public Node
}
void startShutdown() override
{
if (!(*Assert(Assert(m_context)->shutdown))()) {
NodeContext& ctx{*Assert(m_context)};
if (!(Assert(ctx.shutdown_request))()) {
LogError("Failed to send shutdown signal\n");
}

// Stop RPC for clean shutdown if any of waitfor* commands is executed.
if (args().GetBoolArg("-server", false)) {
InterruptRPC();
StopRPC(m_context);
StopRPC();
}
}
bool shutdownRequested() override { return ShutdownRequested(*Assert(m_context)); };
Expand Down Expand Up @@ -938,19 +940,12 @@ class MinerImpl : public Mining

BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
{
// Interrupt check interval
const MillisecondsDouble tick{1000};
auto now{std::chrono::steady_clock::now()};
auto deadline = now + timeout;
// std::chrono does not check against overflow
if (deadline < now) deadline = std::chrono::steady_clock::time_point::max();
if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono
{
WAIT_LOCK(notifications().m_tip_block_mutex, lock);
while ((notifications().m_tip_block == uint256() || notifications().m_tip_block == current_tip) && !chainman().m_interrupt) {
now = std::chrono::steady_clock::now();
if (now >= deadline) break;
notifications().m_tip_block_cv.wait_until(lock, std::min(deadline, now + tick));
}
notifications().m_tip_block_cv.wait_for(lock, timeout, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
return (notifications().m_tip_block != current_tip && notifications().m_tip_block != uint256::ZERO) || chainman().m_interrupt;
});
}
// Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
LOCK(::cs_main);
Expand Down
6 changes: 3 additions & 3 deletions src/node/kernel_notifications.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ kernel::InterruptResult KernelNotifications::blockTip(SynchronizationState state

uiInterface.NotifyBlockTip(state, &index);
if (m_stop_at_height && index.nHeight >= m_stop_at_height) {
if (!m_shutdown()) {
if (!m_shutdown_request()) {
LogError("Failed to send shutdown signal after reaching stop height\n");
}
return kernel::Interrupted{};
Expand Down Expand Up @@ -90,12 +90,12 @@ void KernelNotifications::warningUnset(kernel::Warning id)

void KernelNotifications::flushError(const bilingual_str& message)
{
AbortNode(&m_shutdown, m_exit_status, message, &m_warnings);
AbortNode(m_shutdown_request, m_exit_status, message, &m_warnings);
}

void KernelNotifications::fatalError(const bilingual_str& message)
{
node::AbortNode(m_shutdown_on_fatal_error ? &m_shutdown : nullptr,
node::AbortNode(m_shutdown_on_fatal_error ? m_shutdown_request : nullptr,
m_exit_status, message, &m_warnings);
}

Expand Down
17 changes: 8 additions & 9 deletions src/node/kernel_notifications.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include <atomic>
#include <cstdint>
#include <functional>

class ArgsManager;
class CBlockIndex;
Expand All @@ -23,10 +24,6 @@ namespace kernel {
enum class Warning;
} // namespace kernel

namespace util {
class SignalInterrupt;
} // namespace util

namespace node {

class Warnings;
Expand All @@ -35,8 +32,8 @@ static constexpr int DEFAULT_STOPATHEIGHT{0};
class KernelNotifications : public kernel::Notifications
{
public:
KernelNotifications(util::SignalInterrupt& shutdown, std::atomic<int>& exit_status, node::Warnings& warnings)
: m_shutdown(shutdown), m_exit_status{exit_status}, m_warnings{warnings} {}
KernelNotifications(const std::function<bool()>& shutdown_request, std::atomic<int>& exit_status, node::Warnings& warnings)
: m_shutdown_request(shutdown_request), m_exit_status{exit_status}, m_warnings{warnings} {}

[[nodiscard]] kernel::InterruptResult blockTip(SynchronizationState state, CBlockIndex& index) override EXCLUSIVE_LOCKS_REQUIRED(!m_tip_block_mutex);

Expand All @@ -58,12 +55,14 @@ class KernelNotifications : public kernel::Notifications
bool m_shutdown_on_fatal_error{true};

Mutex m_tip_block_mutex;
std::condition_variable m_tip_block_cv;
std::condition_variable m_tip_block_cv GUARDED_BY(m_tip_block_mutex);
//! The block for which the last blockTip notification was received for.
uint256 m_tip_block;
//! The initial ZERO means that no block has been connected yet, which may
//! be true even long after startup, until shutdown.
uint256 m_tip_block GUARDED_BY(m_tip_block_mutex){uint256::ZERO};

private:
util::SignalInterrupt& m_shutdown;
const std::function<bool()>& m_shutdown_request;
std::atomic<int>& m_exit_status;
node::Warnings& m_warnings;
};
Expand Down
7 changes: 2 additions & 5 deletions src/rpc/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ static RPCHelpMan stop()
{
// Event loop will exit after current HTTP requests have been handled, so
// this reply will get back to the client.
CHECK_NONFATAL((*CHECK_NONFATAL(EnsureAnyNodeContext(jsonRequest.context).shutdown))());
CHECK_NONFATAL((CHECK_NONFATAL(EnsureAnyNodeContext(jsonRequest.context).shutdown_request))());
if (jsonRequest.params[0].isNum()) {
UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].getInt<int>()});
}
Expand Down Expand Up @@ -294,7 +294,7 @@ void InterruptRPC()
});
}

void StopRPC(const std::any& context)
void StopRPC()
{
static std::once_flag g_rpc_stop_flag;
// This function could be called twice if the GUI has been started with -server=1.
Expand All @@ -303,9 +303,6 @@ void StopRPC(const std::any& context)
LogDebug(BCLog::RPC, "Stopping RPC\n");
WITH_LOCK(g_deadline_timers_mutex, deadlineTimers.clear());
DeleteAuthCookie();
node::NodeContext& node = EnsureAnyNodeContext(context);
// The notifications interface doesn't exist between initialization step 4a and 7.
if (node.notifications) node.notifications->m_tip_block_cv.notify_all();
LogDebug(BCLog::RPC, "RPC stopped.\n");
});
}
Expand Down
Loading

0 comments on commit 5837e34

Please sign in to comment.