diff --git a/CMakeLists.txt b/CMakeLists.txt index 00de5c2a9128..931aebf4b275 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,7 +194,8 @@ set(SERVER_SOURCES ./src/httprpc.cpp ./src/httpserver.cpp ./src/init.cpp - ./src/interface/wallet.cpp + ./src/interfaces/handler.cpp + ./src/interfaces/wallet.cpp ./src/dbwrapper.cpp ./src/legacy/validation_zerocoin_legacy.cpp ./src/merkleblock.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 513a08125860..6482a20d2a13 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -190,7 +190,8 @@ BITCOIN_CORE_H = \ httprpc.h \ httpserver.h \ init.h \ - interface/wallet.h \ + interfaces/handler.h \ + interfaces/wallet.h \ invalid.h \ invalid_outpoints.json.h \ invalid_serials.json.h \ @@ -373,7 +374,7 @@ libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ activemasternode.cpp \ bip38.cpp \ - interface/wallet.cpp \ + interfaces/wallet.cpp \ addressbook.cpp \ crypter.cpp \ key_io.cpp \ @@ -533,6 +534,7 @@ libbitcoin_util_a_SOURCES = \ compat/glibcxx_sanity.cpp \ compat/strnlen.cpp \ fs.cpp \ + interfaces/handler.cpp \ logging.cpp \ random.cpp \ rpc/protocol.cpp \ diff --git a/src/init.cpp b/src/init.cpp index 01bd0f90963e..44a16e9c4510 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -232,7 +232,10 @@ void PrepareShutdown() #endif StopMapPort(); - UnregisterValidationInterface(peerLogic.get()); + // Because these depend on each-other, we make sure that neither can be + // using the other before destroying them. + if (peerLogic) UnregisterValidationInterface(peerLogic.get()); + if (g_connman) g_connman->Stop(); peerLogic.reset(); g_connman.reset(); @@ -258,6 +261,19 @@ void PrepareShutdown() fFeeEstimatesInitialized = false; } + // FlushStateToDisk generates a SetBestChain callback, which we should avoid missing + FlushStateToDisk(); + + // After there are no more peers/RPC left to give us new data which may generate + // CValidationInterface callbacks, flush them... + GetMainSignals().FlushBackgroundCallbacks(); + + // Any future callbacks will be dropped. This should absolutely be safe - if + // missing a callback results in an unrecoverable situation, unclean shutdown + // would too. The only reason to do the above flushes is to let the wallet catch + // up with our current chain to avoid any strange pruning edge cases and make + // next startup faster by avoiding rescan. + { LOCK(cs_main); if (pcoinsTip != NULL) { @@ -294,6 +310,7 @@ void PrepareShutdown() // Disconnect all slots UnregisterAllValidationInterfaces(); + GetMainSignals().UnregisterBackgroundSignalScheduler(); #ifndef WIN32 try { @@ -776,6 +793,18 @@ bool AppInitServers() std::terminate(); }; +namespace { // Variables internal to initialization process only + + ServiceFlags nRelevantServices = NODE_NETWORK; + int nMaxConnections; + int nUserMaxConnections; + int nFD; + ServiceFlags nLocalServices = NODE_NETWORK; + + std::string strWalletFile; + bool fDisableWallet = false; +} + bool AppInitBasicSetup() { // ********************************************************* Step 1: setup @@ -969,16 +998,10 @@ void InitLogging() LogPrintf("PIVX version %s (%s)\n", version_string, CLIENT_DATE); } -/** Initialize pivx. - * @pre Parameters should be parsed and config file should be read. - */ -bool AppInit2() +bool AppInitParameterInteraction() { - // ********************************************************* Step 1: setup - if (!AppInitBasicSetup()) - return false; - // ********************************************************* Step 2: parameter interactions + // Make sure enough file descriptors are available // -bind and -whitebind can't be set when not listening @@ -988,12 +1011,12 @@ bool AppInit2() } int nBind = std::max(nUserBind, size_t(1)); - int nUserMaxConnections = gArgs.GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS); - int nMaxConnections = std::max(nUserMaxConnections, 0); + nUserMaxConnections = gArgs.GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS); + nMaxConnections = std::max(nUserMaxConnections, 0); // Trim requested connection counts, to fit into system limitations - nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0); - int nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS); + nMaxConnections = std::max(std::min(nMaxConnections, (int) (FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0); + nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS); if (nFD < MIN_CORE_FILEDESCRIPTORS) return UIError(_("Not enough file descriptors available.")); if (nFD - MIN_CORE_FILEDESCRIPTORS < nMaxConnections) @@ -1005,7 +1028,7 @@ bool AppInit2() const std::vector& categories = gArgs.GetArgs("-debug"); if (!(gArgs.GetBoolArg("-nodebug", false) || - find(categories.begin(), categories.end(), std::string("0")) != categories.end())) { + find(categories.begin(), categories.end(), std::string("0")) != categories.end())) { for (const auto& cat : categories) { if (!g_logger->EnableCategory(cat)) { UIWarning(strprintf(_("Unsupported logging category %s=%s."), "-debug", cat)); @@ -1025,7 +1048,8 @@ bool AppInit2() UIWarning(_("Warning: Unsupported argument -debugnet ignored, use -debug=net.")); // Check for -socks - as this is a privacy risk to continue, exit here if (gArgs.IsArgSet("-socks")) - return UIError(_("Error: Unsupported argument -socks found. Setting SOCKS version isn't possible anymore, only SOCKS5 proxies are supported.")); + return UIError( + _("Error: Unsupported argument -socks found. Setting SOCKS version isn't possible anymore, only SOCKS5 proxies are supported.")); // Check for -tor - as this is a privacy risk to continue, exit here if (gArgs.GetBoolArg("-tor", false)) return UIError(_("Error: Unsupported argument -tor found, use -onion.")); @@ -1036,15 +1060,18 @@ bool AppInit2() if (gArgs.GetBoolArg("-masternode", DEFAULT_MASTERNODE) && !gArgs.GetBoolArg("-listen", DEFAULT_LISTEN)) return UIError(_("Error: -listen must be true if -masternode is set.")); // Exit early if -masternode=1 and -port is not the default port - if (gArgs.GetBoolArg("-masternode", DEFAULT_MASTERNODE) && (GetListenPort() != Params().GetDefaultPort() && !Params().IsRegTestNet())) + if (gArgs.GetBoolArg("-masternode", DEFAULT_MASTERNODE) && + (GetListenPort() != Params().GetDefaultPort() && !Params().IsRegTestNet())) return UIError(strprintf(_("Error: Invalid port %d for running a masternode."), GetListenPort()) + "\n\n" + - strprintf(_("Masternodes are required to run on port %d for %s-net"), Params().GetDefaultPort(), Params().NetworkIDString())); + strprintf(_("Masternodes are required to run on port %d for %s-net"), Params().GetDefaultPort(), + Params().NetworkIDString())); if (gArgs.GetBoolArg("-benchmark", false)) UIWarning(_("Warning: Unsupported argument -benchmark ignored, use -debug=bench.")); // Checkmempool and checkblockindex default to true in regtest mode - int ratio = std::min(std::max(gArgs.GetArg("-checkmempool", Params().DefaultConsistencyChecks() ? 1 : 0), 0), 1000000); + int ratio = std::min( + std::max(gArgs.GetArg("-checkmempool", Params().DefaultConsistencyChecks() ? 1 : 0), 0), 1000000); if (ratio != 0) { mempool.setSanityCheck(1.0 / ratio); } @@ -1055,7 +1082,8 @@ bool AppInit2() int64_t nMempoolSizeLimit = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; int64_t nMempoolDescendantSizeLimit = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; if (nMempoolSizeLimit < 0 || nMempoolSizeLimit < nMempoolDescendantSizeLimit * 40) - return UIError(strprintf(_("Error: -maxmempool must be at least %d MB"), gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) / 25)); + return UIError(strprintf(_("Error: -maxmempool must be at least %d MB"), + gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) / 25)); // -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency nScriptCheckThreads = gArgs.GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS); @@ -1102,7 +1130,7 @@ bool AppInit2() } #ifdef ENABLE_WALLET - std::string strWalletFile = gArgs.GetArg("-wallet", DEFAULT_WALLET_DAT); + strWalletFile = gArgs.GetArg("-wallet", DEFAULT_WALLET_DAT); if (!CWallet::ParameterInteraction()) return false; #endif // ENABLE_WALLET @@ -1115,9 +1143,6 @@ bool AppInit2() SetMockTime(gArgs.GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op } - ServiceFlags nLocalServices = NODE_NETWORK; - ServiceFlags nRelevantServices = NODE_NETWORK; - if (gArgs.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS)) nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM); @@ -1126,7 +1151,39 @@ bool AppInit2() if (!InitNUParams()) return false; - // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log + return true; +} + +static bool LockDataDirectory(bool probeOnly) +{ + std::string strDataDir = GetDataDir().string(); + + // Make sure only a single PIVX process is using the data directory. + fs::path pathLockFile = GetDataDir() / ".lock"; + FILE* file = fsbridge::fopen(pathLockFile, "a"); // empty lock file; created if it doesn't exist. + if (file) fclose(file); + static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); + + try { + // Wait maximum 10 seconds if an old wallet is still running. Avoids lockup during restart + if (!lock.timed_lock(boost::get_system_time() + boost::posix_time::seconds(10))) { + return UIError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), + strDataDir, _(PACKAGE_NAME))); + } + if (probeOnly) { + lock.unlock(); + } + } catch (const boost::interprocess::interprocess_exception& e) { + return UIError( + strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running.") + " %s.", + strDataDir, _(PACKAGE_NAME), e.what())); + } + return true; +} + +bool AppInitSanityChecks() +{ + // ********************************************************* Step 4: sanity checks // Initialize elliptic curve code RandomInit(); @@ -1135,19 +1192,22 @@ bool AppInit2() // Sanity check if (!InitSanityCheck()) - return UIError(_("Initialization sanity check failed. PIVX Core is shutting down.")); + return UIError(strprintf(_("Initialization sanity check failed. %s is shutting down."), _(PACKAGE_NAME))); - std::string strDataDir = GetDataDir().string(); - - // Make sure only a single PIVX process is using the data directory. - fs::path pathLockFile = GetDataDir() / ".lock"; - FILE* file = fsbridge::fopen(pathLockFile, "a"); // empty lock file; created if it doesn't exist. - if (file) fclose(file); - static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); + // Probe the data directory lock to give an early error message, if possible + return LockDataDirectory(true); +} - // Wait maximum 10 seconds if an old wallet is still running. Avoids lockup during restart - if (!lock.timed_lock(boost::get_system_time() + boost::posix_time::seconds(10))) - return UIError(strprintf(_("Cannot obtain a lock on data directory %s. PIVX Core is probably already running."), strDataDir)); +bool AppInitMain() +{ + // ********************************************************* Step 4a: application initialization + // After daemonization get the data directory lock again and hold on to it until exit + // This creates a slight window for a race condition to happen, however this condition is harmless: it + // will at most make us exit without printing a message to console. + if (!LockDataDirectory(false)) { + // Detailed error printed inside LockDataDirectory + return false; + } #ifndef WIN32 CreatePidFile(GetPidFile(), getpid()); @@ -1164,7 +1224,7 @@ bool AppInit2() if (!g_logger->m_log_timestamps) LogPrintf("Startup time: %s\n", DateTimeStrFormat("%Y-%m-%d %H:%M:%S", GetTime())); LogPrintf("Default data directory %s\n", GetDefaultDataDir().string()); - LogPrintf("Using data directory %s\n", strDataDir); + LogPrintf("Using data directory %s\n", GetDataDir().string()); LogPrintf("Using config file %s\n", GetConfigFile().string()); LogPrintf("Using at most %i connections (%i file descriptors available)\n", nMaxConnections, nFD); std::ostringstream strErrors; @@ -1184,8 +1244,10 @@ bool AppInit2() } // Start the lightweight task scheduler thread - CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler); - threadGroup.create_thread(boost::bind(&TraceThread, "scheduler", serviceLoop)); + CScheduler::Function serviceLoop = std::bind(&CScheduler::serviceQueue, &scheduler); + threadGroup.create_thread(std::bind(&TraceThread, "scheduler", serviceLoop)); + + GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); // Initialize Sapling circuit parameters LoadSaplingParams(); @@ -1696,7 +1758,7 @@ bool AppInit2() for (const std::string& strFile : gArgs.GetArgs("-loadblock")) { vImportFiles.push_back(strFile);; } - threadGroup.create_thread(boost::bind(&ThreadImport, vImportFiles)); + threadGroup.create_thread(std::bind(&ThreadImport, vImportFiles)); // Wait for genesis block to be processed LogPrintf("Waiting for genesis block to be imported...\n"); @@ -1805,7 +1867,7 @@ bool AppInit2() LogPrintf("fLiteMode %d\n", fLiteMode); LogPrintf("Budget Mode %s\n", strBudgetMode.c_str()); - threadGroup.create_thread(boost::bind(&ThreadCheckMasternodes)); + threadGroup.create_thread(std::bind(&ThreadCheckMasternodes)); if (ShutdownRequested()) { LogPrintf("Shutdown requested. Exiting.\n"); @@ -1873,7 +1935,7 @@ bool AppInit2() // StakeMiner thread disabled by default on regtest if (gArgs.GetBoolArg("-staking", !Params().IsRegTestNet() && DEFAULT_STAKING)) { - threadGroup.create_thread(boost::bind(&ThreadStakeMinter)); + threadGroup.create_thread(std::bind(&ThreadStakeMinter)); } } #endif diff --git a/src/init.h b/src/init.h index 65f4dbd742c7..2af4673eb84b 100644 --- a/src/init.h +++ b/src/init.h @@ -27,13 +27,30 @@ void PrepareShutdown(); void InitLogging(); //!Parameter interaction: change current parameters depending on various rules void InitParameterInteraction(); -bool AppInit2(); /** Initialize PIVX core: Basic context setup. * @note This can be done before daemonization. Do not call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read. */ bool AppInitBasicSetup(); +/** + * Initialization: parameter interaction. + * @note This can be done before daemonization. + * @pre Parameters should be parsed and config file should be read, AppInitBasicSetup should have been called. + */ +bool AppInitParameterInteraction(); +/** + * Initialization sanity checks: ecc init, sanity checks, dir lock. + * @note This can be done before daemonization. + * @pre Parameters should be parsed and config file should be read, AppInitParameterInteraction should have been called. + */ +bool AppInitSanityChecks(); +/** + * Bitcoin core main initialization. + * @note This should only be done after daemonization. + * @pre Parameters should be parsed and config file should be read, AppInitSanityChecks should have been called. + */ +bool AppInitMain(); /** The help message mode determines what help message to show */ enum HelpMessageMode { diff --git a/src/interfaces/handler.cpp b/src/interfaces/handler.cpp new file mode 100644 index 000000000000..c2e33080f87b --- /dev/null +++ b/src/interfaces/handler.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2018-2020 The Bitcoin Core developers +// Copyright (c) 2020 The PIVX Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include + +#include + +#include +#include + +namespace interfaces { +namespace { + +class HandlerImpl : public Handler +{ +public: + explicit HandlerImpl(boost::signals2::connection connection) : m_connection(std::move(connection)) {} + + void disconnect() override { m_connection.disconnect(); } + + boost::signals2::scoped_connection m_connection; +}; + +} // namespace + +std::unique_ptr MakeHandler(boost::signals2::connection connection) +{ + return MakeUnique(std::move(connection)); +} + +} // namespace interfaces diff --git a/src/interfaces/handler.h b/src/interfaces/handler.h new file mode 100644 index 000000000000..b15d06c39315 --- /dev/null +++ b/src/interfaces/handler.h @@ -0,0 +1,36 @@ +// Copyright (c) 2018-2020 The Bitcoin Core developers +// Copyright (c) 2020 The PIVX Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_INTERFACES_HANDLER_H +#define PIVX_INTERFACES_HANDLER_H + +#include + +namespace boost { +namespace signals2 { +class connection; +} // namespace signals2 +} // namespace boost + +namespace interfaces { + +//! Generic interface for managing an event handler or callback function +//! registered with another interface. Has a single disconnect method to cancel +//! the registration and prevent any future notifications. +class Handler +{ +public: + virtual ~Handler() {} + + //! Disconnect the handler. + virtual void disconnect() = 0; +}; + +//! Return handler wrapping a boost signal connection. +std::unique_ptr MakeHandler(boost::signals2::connection connection); + +} // namespace interfaces + +#endif // PIVX_INTERFACES_HANDLER_H diff --git a/src/interface/wallet.cpp b/src/interfaces/wallet.cpp similarity index 97% rename from src/interface/wallet.cpp rename to src/interfaces/wallet.cpp index ca9abcac0c3d..52fa57150613 100644 --- a/src/interface/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php. -#include "interface/wallet.h" +#include "interfaces/wallet.h" #include "wallet.h" namespace interfaces { diff --git a/src/interface/wallet.h b/src/interfaces/wallet.h similarity index 100% rename from src/interface/wallet.h rename to src/interfaces/wallet.h diff --git a/src/miner.cpp b/src/miner.cpp index fb77c93fb269..38b89e628247 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -738,7 +738,7 @@ void GenerateBitcoins(bool fGenerate, CWallet* pwallet, int nThreads) minerThreads = new boost::thread_group(); for (int i = 0; i < nThreads; i++) - minerThreads->create_thread(boost::bind(&ThreadBitcoinMiner, pwallet)); + minerThreads->create_thread(std::bind(&ThreadBitcoinMiner, pwallet)); } // ppcoin: stake minter thread diff --git a/src/net.cpp b/src/net.cpp index 277559bc1cfa..5e6992c9f8f4 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2180,7 +2180,7 @@ bool CConnman::Start(CScheduler& scheduler, std::string& strNodeError, Options c threadMessageHandler = std::thread(&TraceThread >, "msghand", std::function(std::bind(&CConnman::ThreadMessageHandler, this))); // Dump network addresses - scheduler.scheduleEvery(boost::bind(&CConnman::DumpData, this), DUMP_ADDRESSES_INTERVAL); + scheduler.scheduleEvery(std::bind(&CConnman::DumpData, this), DUMP_ADDRESSES_INTERVAL * 1000); return true; } diff --git a/src/pivxd.cpp b/src/pivxd.cpp index e864228ef037..c144ff0a815e 100644 --- a/src/pivxd.cpp +++ b/src/pivxd.cpp @@ -108,6 +108,24 @@ bool AppInit(int argc, char* argv[]) } } + // -server defaults to true for bitcoind but not for the GUI so do this here + gArgs.SoftSetBoolArg("-server", true); + // Set this early so that parameter interactions go to console + InitLogging(); + InitParameterInteraction(); + if (!AppInitBasicSetup()) { + // InitError will have been called with detailed error, which ends up on console + exit(1); + } + if (!AppInitParameterInteraction()) { + // InitError will have been called with detailed error, which ends up on console + exit(1); + } + if (!AppInitSanityChecks()) { + // InitError will have been called with detailed error, which ends up on console + exit(1); + } + #ifndef WIN32 fDaemon = gArgs.GetBoolArg("-daemon", false); if (fDaemon) { @@ -130,12 +148,9 @@ bool AppInit(int argc, char* argv[]) fprintf(stderr, "Error: setsid() returned %d errno %d\n", sid, errno); } #endif - gArgs.SoftSetBoolArg("-server", true); // Set this early so that parameter interactions go to console - InitLogging(); - InitParameterInteraction(); - fRet = AppInit2(); + fRet = AppInitMain(); } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInit()"); } catch (...) { diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 6756008e4a22..a54f62e83e03 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -14,7 +14,7 @@ #include "chainparams.h" #include "checkpoints.h" #include "clientversion.h" -#include "masternode-sync.h" +#include "interfaces/handler.h" #include "masternodeman.h" #include "net.h" #include "netbase.h" @@ -303,21 +303,21 @@ static void BannedListChanged(ClientModel *clientmodel) void ClientModel::subscribeToCoreSignals() { // Connect signals to client - uiInterface.ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); - uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1)); - uiInterface.NotifyAlertChanged.connect(boost::bind(NotifyAlertChanged, this)); - uiInterface.BannedListChanged.connect(boost::bind(BannedListChanged, this)); - uiInterface.NotifyBlockTip.connect(boost::bind(BlockTipChanged, this, _1, _2)); + m_handler_show_progress = interfaces::MakeHandler(uiInterface.ShowProgress.connect(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2))); + m_handler_notify_num_connections_changed = interfaces::MakeHandler(uiInterface.NotifyNumConnectionsChanged.connect(std::bind(NotifyNumConnectionsChanged, this, std::placeholders::_1))); + m_handler_notify_alert_changed = interfaces::MakeHandler(uiInterface.NotifyAlertChanged.connect(std::bind(NotifyAlertChanged, this))); + m_handler_banned_list_changed = interfaces::MakeHandler(uiInterface.BannedListChanged.connect(std::bind(BannedListChanged, this))); + m_handler_notify_block_tip = interfaces::MakeHandler(uiInterface.NotifyBlockTip.connect(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2))); } void ClientModel::unsubscribeFromCoreSignals() { // Disconnect signals from client - uiInterface.ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); - uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1)); - uiInterface.NotifyAlertChanged.disconnect(boost::bind(NotifyAlertChanged, this)); - uiInterface.BannedListChanged.disconnect(boost::bind(BannedListChanged, this)); - uiInterface.NotifyBlockTip.disconnect(boost::bind(BlockTipChanged, this, _1, _2)); + m_handler_show_progress->disconnect(); + m_handler_notify_num_connections_changed->disconnect(); + m_handler_notify_alert_changed->disconnect(); + m_handler_banned_list_changed->disconnect(); + m_handler_notify_block_tip->disconnect(); } bool ClientModel::getTorInfo(std::string& ip_port) const diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 9940aeb40605..bb89343f02d2 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -12,13 +12,17 @@ #include #include +#include + class AddressTableModel; class BanTableModel; class OptionsModel; class PeerTableModel; class TransactionTableModel; -class CWallet; +namespace interfaces { + class Handler; +} QT_BEGIN_NAMESPACE class QDateTime; @@ -91,6 +95,13 @@ class ClientModel : public QObject void stopMasternodesTimer(); private: + // Listeners + std::unique_ptr m_handler_show_progress; + std::unique_ptr m_handler_notify_num_connections_changed; + std::unique_ptr m_handler_notify_alert_changed; + std::unique_ptr m_handler_banned_list_changed; + std::unique_ptr m_handler_notify_block_tip; + QString getMasternodeCountString() const; OptionsModel* optionsModel; PeerTableModel* peerTableModel; diff --git a/src/qt/pivx.cpp b/src/qt/pivx.cpp index bb0c07decb0e..ef99ca8bcc8f 100644 --- a/src/qt/pivx.cpp +++ b/src/qt/pivx.cpp @@ -257,7 +257,19 @@ void BitcoinCore::initialize() try { qDebug() << __func__ << ": Running AppInit2 in thread"; - int rv = AppInit2(); + if (!AppInitBasicSetup()) { + Q_EMIT initializeResult(false); + return; + } + if (!AppInitParameterInteraction()) { + Q_EMIT initializeResult(false); + return; + } + if (!AppInitSanityChecks()) { + Q_EMIT initializeResult(false); + return; + } + int rv = AppInitMain(); Q_EMIT initializeResult(rv); } catch (const std::exception& e) { handleRunawayException(&e); diff --git a/src/qt/pivx/pivxgui.cpp b/src/qt/pivx/pivxgui.cpp index 08b79a36f64a..c8800cb60045 100644 --- a/src/qt/pivx/pivxgui.cpp +++ b/src/qt/pivx/pivxgui.cpp @@ -10,6 +10,7 @@ #include "qt/guiutil.h" #include "clientmodel.h" +#include "interfaces/handler.h" #include "optionsmodel.h" #include "networkstyle.h" #include "notificator.h" @@ -689,11 +690,11 @@ static bool ThreadSafeMessageBox(PIVXGUI* gui, const std::string& message, const void PIVXGUI::subscribeToCoreSignals() { // Connect signals to client - uiInterface.ThreadSafeMessageBox.connect(boost::bind(ThreadSafeMessageBox, this, _1, _2, _3)); + m_handler_message_box = interfaces::MakeHandler(uiInterface.ThreadSafeMessageBox.connect(std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); } void PIVXGUI::unsubscribeFromCoreSignals() { // Disconnect signals from client - uiInterface.ThreadSafeMessageBox.disconnect(boost::bind(ThreadSafeMessageBox, this, _1, _2, _3)); + m_handler_message_box->disconnect(); } diff --git a/src/qt/pivx/pivxgui.h b/src/qt/pivx/pivxgui.h index 148fbd0830bb..e343e9030c6a 100644 --- a/src/qt/pivx/pivxgui.h +++ b/src/qt/pivx/pivxgui.h @@ -27,6 +27,9 @@ #include "qt/pivx/settings/settingsfaqwidget.h" #include "qt/rpcconsole.h" +namespace interfaces { + class Handler; +} class ClientModel; class NetworkStyle; @@ -114,6 +117,9 @@ public Q_SLOTS: */ private: + // Handlers + std::unique_ptr m_handler_message_box; + bool enableWallet; ClientModel* clientModel = nullptr; @@ -121,7 +127,6 @@ public Q_SLOTS: QAction* quitAction = nullptr; QAction* toggleHideAction = nullptr; - // Frame NavMenuWidget *navMenu = nullptr; TopBar *topBar = nullptr; diff --git a/src/qt/pivx/splash.cpp b/src/qt/pivx/splash.cpp index 9a91fb2aa6ab..eca2c3bc8c18 100644 --- a/src/qt/pivx/splash.cpp +++ b/src/qt/pivx/splash.cpp @@ -6,6 +6,7 @@ #include "qt/pivx/forms/ui_splash.h" #include "QFile" +#include "interfaces/handler.h" #include "init.h" #include "guiinterface.h" #include "networkstyle.h" @@ -76,27 +77,30 @@ static void ShowProgress(Splash* splash, const std::string& title, int nProgress } #ifdef ENABLE_WALLET +std::unique_ptr m_handler_show_progress_wallet; static void ConnectWallet(Splash* splash, CWallet* wallet){ - wallet->ShowProgress.connect(boost::bind(ShowProgress, splash, _1, _2)); + m_handler_show_progress_wallet = interfaces::MakeHandler(wallet->ShowProgress.connect(std::bind(ShowProgress, splash, std::placeholders::_1, std::placeholders::_2))); } #endif void Splash::subscribeToCoreSignals(){ // Connect signals to client - uiInterface.InitMessage.connect(boost::bind(InitMessage, this, _1)); - uiInterface.ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); + m_handler_init_message = interfaces::MakeHandler(uiInterface.InitMessage.connect(std::bind(InitMessage, this, std::placeholders::_1))); + m_handler_show_progress = interfaces::MakeHandler(uiInterface.ShowProgress.connect(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2))); #ifdef ENABLE_WALLET - uiInterface.LoadWallet.connect(boost::bind(ConnectWallet, this, _1)); + m_handler_load_wallet = interfaces::MakeHandler(uiInterface.LoadWallet.connect(std::bind(ConnectWallet, this, std::placeholders::_1))); #endif } void Splash::unsubscribeFromCoreSignals(){ // Disconnect signals from client - uiInterface.InitMessage.disconnect(boost::bind(InitMessage, this, _1)); - uiInterface.ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); + m_handler_init_message->disconnect(); + m_handler_show_progress->disconnect(); #ifdef ENABLE_WALLET - if (pwalletMain) - pwalletMain->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); + if (pwalletMain) { + m_handler_load_wallet->disconnect(); + if (m_handler_show_progress_wallet) m_handler_show_progress_wallet->disconnect(); + } #endif } diff --git a/src/qt/pivx/splash.h b/src/qt/pivx/splash.h index b6db30427e5a..cc6a667a36ed 100644 --- a/src/qt/pivx/splash.h +++ b/src/qt/pivx/splash.h @@ -7,8 +7,14 @@ #include +#include + class NetworkStyle; +namespace interfaces { + class Handler; +}; + namespace Ui { class Splash; } @@ -34,6 +40,11 @@ public Q_SLOTS: private: Ui::Splash *ui; + // Listeners + std::unique_ptr m_handler_init_message; + std::unique_ptr m_handler_show_progress; + std::unique_ptr m_handler_load_wallet; + /** Connect core signals to splash screen */ void subscribeToCoreSignals(); /** Disconnect core signals to splash screen */ diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 1b1871e36155..ddb5c9fe3ab8 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -13,6 +13,7 @@ #include "transactionrecord.h" #include "walletmodel.h" +#include "interfaces/handler.h" #include "sync.h" #include "uint256.h" #include "util.h" @@ -879,13 +880,13 @@ static void ShowProgress(TransactionTableModel* ttm, const std::string& title, i void TransactionTableModel::subscribeToCoreSignals() { // Connect signals to wallet - wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); - wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); + m_handler_transaction_changed = interfaces::MakeHandler(wallet->NotifyTransactionChanged.connect(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); + m_handler_show_progress = interfaces::MakeHandler(wallet->ShowProgress.connect(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2))); } void TransactionTableModel::unsubscribeFromCoreSignals() { // Disconnect signals from wallet - wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); - wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); + m_handler_transaction_changed->disconnect(); + m_handler_show_progress->disconnect(); } diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index 4922bc31057f..b5f925472b2d 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -11,6 +11,12 @@ #include #include +#include + +namespace interfaces { + class Handler; +} + class TransactionRecord; class TransactionTablePriv; class WalletModel; @@ -83,6 +89,10 @@ class TransactionTableModel : public QAbstractTableModel void txArrived(const QString& hash, const bool& isCoinStake, const bool& isCSAnyType); private: + // Listeners + std::unique_ptr m_handler_transaction_changed; + std::unique_ptr m_handler_show_progress; + CWallet* wallet; WalletModel* walletModel; QStringList columns; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 70b6f013752f..c8e981c1f31f 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -16,6 +16,7 @@ #include "coincontrol.h" #include "db.h" #include "keystore.h" +#include "interfaces/handler.h" #include "sapling/key_io_sapling.h" #include "sapling/sapling_operation.h" #include "spork.h" @@ -761,23 +762,23 @@ static void NotifyWalletBacked(WalletModel* model, const bool& fSuccess, const s void WalletModel::subscribeToCoreSignals() { // Connect signals to wallet - wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); - wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5, _6)); - wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); - wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); - wallet->NotifyWatchonlyChanged.connect(boost::bind(NotifyWatchonlyChanged, this, _1)); - wallet->NotifyWalletBacked.connect(boost::bind(NotifyWalletBacked, this, _1, _2)); + m_handler_notify_status_changed = interfaces::MakeHandler(wallet->NotifyStatusChanged.connect(std::bind(&NotifyKeyStoreStatusChanged, this, std::placeholders::_1))); + m_handler_notify_addressbook_changed = interfaces::MakeHandler(wallet->NotifyAddressBookChanged.connect(std::bind(NotifyAddressBookChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6))); + m_handler_notify_transaction_changed = interfaces::MakeHandler(wallet->NotifyTransactionChanged.connect(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); + m_handler_show_progress = interfaces::MakeHandler(wallet->ShowProgress.connect(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2))); + m_handler_notify_watch_only_changed = interfaces::MakeHandler(wallet->NotifyWatchonlyChanged.connect(std::bind(NotifyWatchonlyChanged, this, std::placeholders::_1))); + m_handler_notify_walletbacked = interfaces::MakeHandler(wallet->NotifyWalletBacked.connect(std::bind(NotifyWalletBacked, this, std::placeholders::_1, std::placeholders::_2))); } void WalletModel::unsubscribeFromCoreSignals() { // Disconnect signals from wallet - wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); - wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5, _6)); - wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); - wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); - wallet->NotifyWatchonlyChanged.disconnect(boost::bind(NotifyWatchonlyChanged, this, _1)); - wallet->NotifyWalletBacked.disconnect(boost::bind(NotifyWalletBacked, this, _1, _2)); + m_handler_notify_status_changed->disconnect(); + m_handler_notify_addressbook_changed->disconnect(); + m_handler_notify_transaction_changed->disconnect(); + m_handler_show_progress->disconnect(); + m_handler_notify_watch_only_changed->disconnect(); + m_handler_notify_walletbacked->disconnect(); } // WalletModel::UnlockContext implementation diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index df21188762cb..02d3a43cfc14 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -11,7 +11,7 @@ #include "paymentrequestplus.h" #include "walletmodeltransaction.h" -#include "interface/wallet.h" +#include "interfaces/wallet.h" #include "allocators.h" /* for SecureString */ #include "operationresult.h" @@ -38,6 +38,10 @@ class CPubKey; class CWallet; class uint256; +namespace interfaces { + class Handler; +}; + QT_BEGIN_NAMESPACE class QTimer; QT_END_NAMESPACE @@ -330,6 +334,14 @@ class WalletModel : public QObject // in the model only perform the data organization (and QT wrappers) to be presented on the UI. interfaces::Wallet walletWrapper; + // Listeners + std::unique_ptr m_handler_notify_status_changed; + std::unique_ptr m_handler_notify_addressbook_changed; + std::unique_ptr m_handler_notify_transaction_changed; + std::unique_ptr m_handler_show_progress; + std::unique_ptr m_handler_notify_watch_only_changed; + std::unique_ptr m_handler_notify_walletbacked; + bool fHaveWatchOnly; bool fForceCheckBalanceChanged; diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 419c04c90c9b..1baaa3db65e9 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -20,7 +20,6 @@ #include "wallet/wallet.h" #endif // ENABLE_WALLET -#include #include #include @@ -58,12 +57,12 @@ void RPCServer::OnStopped(std::function slot) void RPCServer::OnPreCommand(std::function slot) { - g_rpcSignals.PreCommand.connect(boost::bind(slot, _1)); + g_rpcSignals.PreCommand.connect(std::bind(slot, std::placeholders::_1)); } void RPCServer::OnPostCommand(std::function slot) { - g_rpcSignals.PostCommand.connect(boost::bind(slot, _1)); + g_rpcSignals.PostCommand.connect(std::bind(slot, std::placeholders::_1)); } void RPCTypeCheck(const UniValue& params, @@ -454,11 +453,7 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const std::vector CRPCTable::listCommands() const { std::vector commandList; - typedef std::map commandMap; - - std::transform( mapCommands.begin(), mapCommands.end(), - std::back_inserter(commandList), - boost::bind(&commandMap::value_type::first,_1) ); + for (const auto& i : mapCommands) commandList.emplace_back(i.first); return commandList; } diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 90110f20750c..6748bd13080b 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -9,7 +9,6 @@ #include "reverselock.h" #include -#include #include CScheduler::CScheduler() : nThreadsServicingQueue(0), stopRequested(false), stopWhenEmpty(false) @@ -94,20 +93,20 @@ void CScheduler::schedule(CScheduler::Function f, boost::chrono::system_clock::t newTaskScheduled.notify_one(); } -void CScheduler::scheduleFromNow(CScheduler::Function f, int64_t deltaSeconds) +void CScheduler::scheduleFromNow(CScheduler::Function f, int64_t deltaMilliSeconds) { - schedule(f, boost::chrono::system_clock::now() + boost::chrono::seconds(deltaSeconds)); + schedule(f, boost::chrono::system_clock::now() + boost::chrono::milliseconds(deltaMilliSeconds)); } -static void Repeat(CScheduler* s, CScheduler::Function f, int64_t deltaSeconds) +static void Repeat(CScheduler* s, CScheduler::Function f, int64_t deltaMilliSeconds) { f(); - s->scheduleFromNow(boost::bind(&Repeat, s, f, deltaSeconds), deltaSeconds); + s->scheduleFromNow(std::bind(&Repeat, s, f, deltaMilliSeconds), deltaMilliSeconds); } -void CScheduler::scheduleEvery(CScheduler::Function f, int64_t deltaSeconds) +void CScheduler::scheduleEvery(CScheduler::Function f, int64_t deltaMilliSeconds) { - scheduleFromNow(boost::bind(&Repeat, this, f, deltaSeconds), deltaSeconds); + scheduleFromNow(std::bind(&Repeat, this, f, deltaMilliSeconds), deltaMilliSeconds); } size_t CScheduler::getQueueInfo(boost::chrono::system_clock::time_point &first, @@ -121,3 +120,75 @@ size_t CScheduler::getQueueInfo(boost::chrono::system_clock::time_point &first, } return result; } + +bool CScheduler::AreThreadsServicingQueue() const { + boost::unique_lock lock(newTaskMutex); + return nThreadsServicingQueue; +} + + +void SingleThreadedSchedulerClient::MaybeScheduleProcessQueue() { + { + LOCK(m_cs_callbacks_pending); + // Try to avoid scheduling too many copies here, but if we + // accidentally have two ProcessQueue's scheduled at once its + // not a big deal. + if (m_are_callbacks_running) return; + if (m_callbacks_pending.empty()) return; + } + m_pscheduler->schedule(std::bind(&SingleThreadedSchedulerClient::ProcessQueue, this)); +} + +void SingleThreadedSchedulerClient::ProcessQueue() { + std::function callback; + { + LOCK(m_cs_callbacks_pending); + if (m_are_callbacks_running) return; + if (m_callbacks_pending.empty()) return; + m_are_callbacks_running = true; + + callback = std::move(m_callbacks_pending.front()); + m_callbacks_pending.pop_front(); + } + + // RAII the setting of fCallbacksRunning and calling MaybeScheduleProcessQueue + // to ensure both happen safely even if callback() throws. + struct RAIICallbacksRunning { + SingleThreadedSchedulerClient* instance; + RAIICallbacksRunning(SingleThreadedSchedulerClient* _instance) : instance(_instance) {} + ~RAIICallbacksRunning() { + { + LOCK(instance->m_cs_callbacks_pending); + instance->m_are_callbacks_running = false; + } + instance->MaybeScheduleProcessQueue(); + } + } raiicallbacksrunning(this); + + callback(); +} + +void SingleThreadedSchedulerClient::AddToProcessQueue(std::function func) { + assert(m_pscheduler); + + { + LOCK(m_cs_callbacks_pending); + m_callbacks_pending.emplace_back(std::move(func)); + } + MaybeScheduleProcessQueue(); +} + +void SingleThreadedSchedulerClient::EmptyQueue() { + assert(!m_pscheduler->AreThreadsServicingQueue()); + bool should_continue = true; + while (should_continue) { + ProcessQueue(); + LOCK(m_cs_callbacks_pending); + should_continue = !m_callbacks_pending.empty(); + } +} + +size_t SingleThreadedSchedulerClient::CallbacksPending() { + LOCK(m_cs_callbacks_pending); + return m_callbacks_pending.size(); +} diff --git a/src/scheduler.h b/src/scheduler.h index 9c1a62cc9308..0801dd8dd6f2 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -15,6 +15,8 @@ #include #include +#include "sync.h" + // // Simple class for background tasks that should be run // periodically or once "after a while" @@ -23,8 +25,8 @@ // // CScheduler* s = new CScheduler(); // s->scheduleFromNow(doSomething, 11); // Assuming a: void doSomething() { } -// s->scheduleFromNow(boost::bind(Class::func, this, argument), 3); -// boost::thread* t = new boost::thread(boost::bind(CScheduler::serviceQueue, s)); +// s->scheduleFromNow(std::bind(Class::func, this, argument), 3); +// boost::thread* t = new boost::thread(std::bind(CScheduler::serviceQueue, s)); // // ... then at program shutdown, clean up the thread running serviceQueue: // t->interrupt(); @@ -42,17 +44,17 @@ class CScheduler typedef std::function Function; // Call func at/after time t - void schedule(Function f, boost::chrono::system_clock::time_point t); + void schedule(Function f, boost::chrono::system_clock::time_point t=boost::chrono::system_clock::now()); // Convenience method: call f once deltaSeconds from now - void scheduleFromNow(Function f, int64_t deltaSeconds); + void scheduleFromNow(Function f, int64_t deltaMilliSeconds); // Another convenience method: call f approximately // every deltaSeconds forever, starting deltaSeconds from now. // To be more precise: every time f is finished, it // is rescheduled to run deltaSeconds later. If you // need more accurate scheduling, don't use this method. - void scheduleEvery(Function f, int64_t deltaSeconds); + void scheduleEvery(Function f, int64_t deltaMilliSeconds); // To keep things as simple as possible, there is no unschedule. @@ -70,6 +72,9 @@ class CScheduler size_t getQueueInfo(boost::chrono::system_clock::time_point &first, boost::chrono::system_clock::time_point &last) const; + // Returns true if there are threads actively running in serviceQueue() + bool AreThreadsServicingQueue() const; + private: std::multimap taskQueue; boost::condition_variable newTaskScheduled; @@ -80,4 +85,32 @@ class CScheduler bool shouldStop() { return stopRequested || (stopWhenEmpty && taskQueue.empty()); } }; +/** + * Class used by CScheduler clients which may schedule multiple jobs + * which are required to be run serially. Does not require such jobs + * to be executed on the same thread, but no two jobs will be executed + * at the same time. + */ +class SingleThreadedSchedulerClient { +private: + CScheduler *m_pscheduler; + + RecursiveMutex m_cs_callbacks_pending; + std::list> m_callbacks_pending; + bool m_are_callbacks_running = false; + + void MaybeScheduleProcessQueue(); + void ProcessQueue(); + +public: + SingleThreadedSchedulerClient(CScheduler *pschedulerIn) : m_pscheduler(pschedulerIn) {} + void AddToProcessQueue(std::function func); + + // Processes all remaining queue members on the calling thread, blocking until queue is empty + // Must be called after the CScheduler has no remaining processing threads! + void EmptyQueue(); + + size_t CallbacksPending(); +}; + #endif diff --git a/src/sync.cpp b/src/sync.cpp index 01403b72d4f3..430bd4db94f8 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -5,6 +5,7 @@ #include "sync.h" +#include #include #include @@ -57,6 +58,11 @@ struct CLockLocation { mutexName, sourceFile, itostr(sourceLine), (fTry ? " (TRY)" : ""), m_thread_name); } + std::string Name() const + { + return mutexName; + } + private: bool fTry; std::string mutexName; @@ -152,6 +158,18 @@ void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs push_lock(cs, CLockLocation(pszName, pszFile, nLine, fTry, util::ThreadGetInternalName())); } +void CheckLastCritical(void* cs, std::string& lockname, const char* guardname, const char* file, int line) +{ + if (!g_lockstack.empty()) { + const auto& lastlock = g_lockstack.back(); + if (lastlock.first == cs) { + lockname = lastlock.second.Name(); + return; + } + } + throw std::system_error(EPERM, std::generic_category(), strprintf("%s:%s %s was not most recent critical section locked", file, line, guardname)); +} + void LeaveCritical() { pop_lock(); diff --git a/src/sync.h b/src/sync.h index 3b3ae3868830..0ba99e200ab5 100644 --- a/src/sync.h +++ b/src/sync.h @@ -51,6 +51,7 @@ LEAVE_CRITICAL_SECTION(mutex); // no RAII #ifdef DEBUG_LOCKORDER void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false); void LeaveCritical(); +void CheckLastCritical(void* cs, std::string& lockname, const char* guardname, const char* file, int line); std::string LocksHeld(); void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs); void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs); @@ -65,6 +66,7 @@ extern bool g_debug_lockorder_abort; #else void static inline EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false) {} void static inline LeaveCritical() {} +void static inline CheckLastCritical(void* cs, std::string& lockname, const char* guardname, const char* file, int line) {} void static inline AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) {} void static inline AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) {} void static inline DeleteLock(void* cs) {} @@ -172,8 +174,45 @@ class SCOPED_LOCKABLE UniqueLock : public Base { return Base::owns_lock(); } + +protected: + // needed for reverse_lock + UniqueLock() { } + +public: + /** + * An RAII-style reverse lock. Unlocks on construction and locks on destruction. + */ + class reverse_lock { + public: + explicit reverse_lock(UniqueLock& _lock, const char* _guardname, const char* _file, int _line) : lock(_lock), file(_file), line(_line) { + CheckLastCritical((void*)lock.mutex(), lockname, _guardname, _file, _line); + lock.unlock(); + LeaveCritical(); + lock.swap(templock); + } + + ~reverse_lock() { + templock.swap(lock); + EnterCritical(lockname.c_str(), file.c_str(), line, (void*)lock.mutex()); + lock.lock(); + } + + private: + reverse_lock(reverse_lock const&); + reverse_lock& operator=(reverse_lock const&); + + UniqueLock& lock; + UniqueLock templock; + std::string lockname; + const std::string file; + const int line; + }; + friend class reverse_lock; }; +#define REVERSE_LOCK(g) decltype(g)::reverse_lock PASTE2(revlock, __COUNTER__)(g, #g, __FILE__, __LINE__) + template using DebugLock = UniqueLock::type>::type>; diff --git a/src/test/reverselock_tests.cpp b/src/test/reverselock_tests.cpp index 99c63c55f6d8..22a9db28625e 100644 --- a/src/test/reverselock_tests.cpp +++ b/src/test/reverselock_tests.cpp @@ -1,35 +1,60 @@ -// Copyright (c) 2015 The Bitcoin Core developers -// Copyright (c) 2017-2019 The PIVX developers +// Copyright (c) 2015-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "reverselock.h" +#include "sync.h" +#include "test/test_pivx.h" -#include -#include -#include -#include #include -BOOST_AUTO_TEST_SUITE(reverselock_tests) +BOOST_FIXTURE_TEST_SUITE(reverselock_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(reverselock_basics) { - boost::mutex mutex; - boost::unique_lock lock(mutex); + Mutex mutex; + WAIT_LOCK(mutex, lock); BOOST_CHECK(lock.owns_lock()); { - reverse_lock > rlock(lock); + REVERSE_LOCK(lock); BOOST_CHECK(!lock.owns_lock()); } BOOST_CHECK(lock.owns_lock()); } +BOOST_AUTO_TEST_CASE(reverselock_multiple) +{ + Mutex mutex2; + Mutex mutex; + WAIT_LOCK(mutex2, lock2); + WAIT_LOCK(mutex, lock); + + // Make sure undoing two locks succeeds + { + REVERSE_LOCK(lock); + BOOST_CHECK(!lock.owns_lock()); + REVERSE_LOCK(lock2); + BOOST_CHECK(!lock2.owns_lock()); + } + BOOST_CHECK(lock.owns_lock()); + BOOST_CHECK(lock2.owns_lock()); +} + BOOST_AUTO_TEST_CASE(reverselock_errors) { - boost::mutex mutex; - boost::unique_lock lock(mutex); + Mutex mutex2; + Mutex mutex; + WAIT_LOCK(mutex2, lock2); + WAIT_LOCK(mutex, lock); + +#ifdef DEBUG_LOCKORDER + // Make sure trying to reverse lock a previous lock fails + try { + REVERSE_LOCK(lock2); + BOOST_CHECK(false); // REVERSE_LOCK(lock2) succeeded + } catch(...) { } + BOOST_CHECK(lock2.owns_lock()); +#endif // Make sure trying to reverse lock an unlocked lock fails lock.unlock(); @@ -38,7 +63,7 @@ BOOST_AUTO_TEST_CASE(reverselock_errors) bool failed = false; try { - reverse_lock > rlock(lock); + REVERSE_LOCK(lock); } catch(...) { failed = true; } @@ -53,7 +78,7 @@ BOOST_AUTO_TEST_CASE(reverselock_errors) lock.lock(); BOOST_CHECK(lock.owns_lock()); { - reverse_lock > rlock(lock); + REVERSE_LOCK(lock); BOOST_CHECK(!lock.owns_lock()); } diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp index a413877e08aa..ce46cbf5887f 100644 --- a/src/test/scheduler_tests.cpp +++ b/src/test/scheduler_tests.cpp @@ -9,7 +9,6 @@ #include "config/pivx-config.h" #endif -#include #include #include #include @@ -25,7 +24,7 @@ static void microTask(CScheduler& s, boost::mutex& mutex, int& counter, int delt } boost::chrono::system_clock::time_point noTime = boost::chrono::system_clock::time_point::min(); if (rescheduleTime != noTime) { - CScheduler::Function f = boost::bind(µTask, boost::ref(s), boost::ref(mutex), boost::ref(counter), -delta + 1, noTime); + CScheduler::Function f = std::bind(µTask, std::ref(s), std::ref(mutex), std::ref(counter), -delta + 1, noTime); s.schedule(f, rescheduleTime); } } @@ -66,8 +65,8 @@ BOOST_AUTO_TEST_CASE(manythreads) boost::chrono::system_clock::time_point t = now + boost::chrono::microseconds(randomMsec(rng)); boost::chrono::system_clock::time_point tReschedule = now + boost::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); - CScheduler::Function f = boost::bind(µTask, boost::ref(microTasks), - boost::ref(counterMutex[whichCounter]), boost::ref(counter[whichCounter]), + CScheduler::Function f = std::bind(µTask, std::ref(microTasks), + std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), randomDelta(rng), tReschedule); microTasks.schedule(f, t); } @@ -79,20 +78,20 @@ BOOST_AUTO_TEST_CASE(manythreads) // As soon as these are created they will start running and servicing the queue boost::thread_group microThreads; for (int i = 0; i < 5; i++) - microThreads.create_thread(boost::bind(&CScheduler::serviceQueue, µTasks)); + microThreads.create_thread(std::bind(&CScheduler::serviceQueue, µTasks)); MicroSleep(600); now = boost::chrono::system_clock::now(); // More threads and more tasks: for (int i = 0; i < 5; i++) - microThreads.create_thread(boost::bind(&CScheduler::serviceQueue, µTasks)); + microThreads.create_thread(std::bind(&CScheduler::serviceQueue, µTasks)); for (int i = 0; i < 100; i++) { boost::chrono::system_clock::time_point t = now + boost::chrono::microseconds(randomMsec(rng)); boost::chrono::system_clock::time_point tReschedule = now + boost::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); - CScheduler::Function f = boost::bind(µTask, boost::ref(microTasks), - boost::ref(counterMutex[whichCounter]), boost::ref(counter[whichCounter]), + CScheduler::Function f = std::bind(µTask, std::ref(microTasks), + std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), randomDelta(rng), tReschedule); microTasks.schedule(f, t); } diff --git a/src/test/test_pivx.cpp b/src/test/test_pivx.cpp index c483520f10ee..4e7a61c92fe7 100644 --- a/src/test/test_pivx.cpp +++ b/src/test/test_pivx.cpp @@ -50,6 +50,12 @@ TestingSetup::TestingSetup() pathTemp = GetTempPath() / strprintf("test_pivx_%lu_%i", (unsigned long)GetTime(), (int)(InsecureRandRange(100000))); fs::create_directories(pathTemp); gArgs.ForceSetArg("-datadir", pathTemp.string()); + + // Note that because we don't bother running a scheduler thread here, + // callbacks via CValidationInterface are unreliable, but that's OK, + // our unit tests aren't testing multiple parts of the code at once. + GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); + // Ideally we'd move all the RPC tests to the functional testing framework // instead of unit tests, but for now we need these here. RegisterAllCoreRPCCommands(tableRPC); @@ -75,6 +81,8 @@ TestingSetup::~TestingSetup() UnregisterNodeSignals(GetNodeSignals()); threadGroup.interrupt_all(); threadGroup.join_all(); + GetMainSignals().FlushBackgroundCallbacks(); + GetMainSignals().UnregisterBackgroundSignalScheduler(); UnloadBlockIndex(); delete pcoinsTip; delete pcoinsdbview; diff --git a/src/test/test_pivx.h b/src/test/test_pivx.h index 7b8ed9cb381a..69d706c90647 100644 --- a/src/test/test_pivx.h +++ b/src/test/test_pivx.h @@ -6,6 +6,7 @@ #define PIVX_TEST_TEST_PIVX_H #include "fs.h" +#include "scheduler.h" #include "txdb.h" #include @@ -48,6 +49,7 @@ struct TestingSetup: public BasicTestingSetup { fs::path pathTemp; boost::thread_group threadGroup; CConnman* connman; + CScheduler scheduler; ECCVerifyHandle globalVerifyHandle; TestingSetup(); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 1da983ae5694..0be88c2f1faf 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -374,7 +374,7 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) { CCheckQueueControl control(&scriptcheckqueue); for (int i=0; i<20; i++) - threadGroup.create_thread(boost::bind(&CCheckQueue::Thread, boost::ref(scriptcheckqueue))); + threadGroup.create_thread(std::bind(&CCheckQueue::Thread, std::ref(scriptcheckqueue))); std::vector coins; for(uint32_t i = 0; i < mtx.vin.size(); i++) { diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 6e698bfd6cd2..e35a43b79658 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include #include @@ -463,8 +462,8 @@ TorController::TorController(struct event_base* _base, const std::string& _targe if (!reconnect_ev) LogPrintf("tor: Failed to create event for reconnection: out of memory?\n"); // Start connection attempts immediately - if (!conn.Connect(_target, boost::bind(&TorController::connected_cb, this, _1), - boost::bind(&TorController::disconnected_cb, this, _1) )) { + if (!conn.Connect(_target, std::bind(&TorController::connected_cb, this, std::placeholders::_1), + std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) { LogPrintf("tor: Initiating connection to Tor control port %s failed\n", _target); } // Read service private key if cached @@ -541,7 +540,7 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& // Request hidden service, redirect port. // Note that the 'virtual' port is always the default port to avoid decloaking nodes using other ports. _conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", private_key, Params().GetDefaultPort(), GetListenPort()), - boost::bind(&TorController::add_onion_cb, this, _1, _2)); + std::bind(&TorController::add_onion_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Authentication failed\n"); } @@ -600,7 +599,7 @@ void TorController::authchallenge_cb(TorControlConnection& _conn, const TorContr } std::vector computedClientHash = ComputeResponse(TOR_SAFE_CLIENTKEY, cookie, clientNonce, serverNonce); - _conn.Command("AUTHENTICATE " + HexStr(computedClientHash), boost::bind(&TorController::auth_cb, this, _1, _2)); + _conn.Command("AUTHENTICATE " + HexStr(computedClientHash), std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Invalid reply to AUTHCHALLENGE\n"); } @@ -649,23 +648,23 @@ void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorContro if (methods.count("HASHEDPASSWORD")) { LogPrint(BCLog::TOR, "tor: Using HASHEDPASSWORD authentication\n"); boost::replace_all(torpassword, "\"", "\\\""); - _conn.Command("AUTHENTICATE \"" + torpassword + "\"", boost::bind(&TorController::auth_cb, this, _1, _2)); + _conn.Command("AUTHENTICATE \"" + torpassword + "\"", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available\n"); } } else if (methods.count("NULL")) { LogPrint(BCLog::TOR, "tor: Using NULL authentication\n"); - _conn.Command("AUTHENTICATE", boost::bind(&TorController::auth_cb, this, _1, _2)); + _conn.Command("AUTHENTICATE", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else if (methods.count("SAFECOOKIE")) { // Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie LogPrint(BCLog::TOR, "tor: Using SAFECOOKIE authentication, reading cookie authentication from %s\n", cookiefile); std::pair status_cookie = ReadBinaryFile(cookiefile, TOR_COOKIE_SIZE); if (status_cookie.first && status_cookie.second.size() == TOR_COOKIE_SIZE) { - // _conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), boost::bind(&TorController::auth_cb, this, _1, _2)); + // _conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); cookie = std::vector(status_cookie.second.begin(), status_cookie.second.end()); clientNonce = std::vector(TOR_NONCE_SIZE, 0); GetRandBytes(&clientNonce[0], TOR_NONCE_SIZE); - _conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), boost::bind(&TorController::authchallenge_cb, this, _1, _2)); + _conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), std::bind(&TorController::authchallenge_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { if (status_cookie.first) { LogPrintf("tor: Authentication cookie %s is not exactly %i bytes, as is required by the spec\n", cookiefile, TOR_COOKIE_SIZE); @@ -687,7 +686,7 @@ void TorController::connected_cb(TorControlConnection& _conn) { reconnect_timeout = RECONNECT_TIMEOUT_START; // First send a PROTOCOLINFO command to figure out what authentication is expected - if (!_conn.Command("PROTOCOLINFO 1", boost::bind(&TorController::protocolinfo_cb, this, _1, _2))) + if (!_conn.Command("PROTOCOLINFO 1", std::bind(&TorController::protocolinfo_cb, this, std::placeholders::_1, std::placeholders::_2))) LogPrintf("tor: Error sending initial protocolinfo command\n"); } @@ -714,8 +713,8 @@ void TorController::Reconnect() /* Try to reconnect and reestablish if we get booted - for example, Tor * may be restarting. */ - if (!conn.Connect(target, boost::bind(&TorController::connected_cb, this, _1), - boost::bind(&TorController::disconnected_cb, this, _1) )) { + if (!conn.Connect(target, std::bind(&TorController::connected_cb, this, std::placeholders::_1), + std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) { LogPrintf("tor: Re-initiating connection to Tor control port %s failed\n", target); } } diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 5cade2edc83f..da8516ee3a03 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -5,9 +5,109 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "validationinterface.h" +#include "scheduler.h" +#include "sync.h" +#include "util.h" + +#include +#include + +#include +#include + +//! The MainSignalsInstance manages a list of shared_ptr +//! 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 { +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 callbacks; int count = 1; }; + std::list m_list GUARDED_BY(m_mutex); + std::unordered_map::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; + + explicit MainSignalsInstance(CScheduler *pscheduler) : m_schedulerClient(pscheduler) {} + + void Register(std::shared_ptr 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 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; +void CMainSignals::RegisterBackgroundSignalScheduler(CScheduler& scheduler) { + assert(!m_internals); + m_internals.reset(new MainSignalsInstance(&scheduler)); +} + +void CMainSignals::UnregisterBackgroundSignalScheduler() { + m_internals.reset(nullptr); +} + +void CMainSignals::FlushBackgroundCallbacks() { + if (m_internals) { + m_internals->m_schedulerClient.EmptyQueue(); + } +} + +size_t CMainSignals::CallbacksPending() { + if (!m_internals) return 0; + return m_internals->m_schedulerClient.CallbacksPending(); +} + CMainSignals& GetMainSignals() { return g_signals; @@ -15,39 +115,61 @@ CMainSignals& GetMainSignals() void RegisterValidationInterface(CValidationInterface* pwalletIn) { - g_signals.UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3)); - g_signals.SyncTransaction.connect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2, _3)); - g_signals.ChainTip.connect(boost::bind(&CValidationInterface::ChainTip, pwalletIn, _1, _2, _3)); - g_signals.NotifyTransactionLock.connect(boost::bind(&CValidationInterface::NotifyTransactionLock, pwalletIn, _1)); - g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1)); - g_signals.SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1)); - g_signals.Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1)); - g_signals.BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); - g_signals.BlockFound.connect(boost::bind(&CValidationInterface::ResetRequestCount, pwalletIn, _1)); + std::shared_ptr sharedValidation = std::make_shared(*pwalletIn); + g_signals.m_internals->Register(std::move(sharedValidation)); } void UnregisterValidationInterface(CValidationInterface* pwalletIn) { - g_signals.BlockFound.disconnect(boost::bind(&CValidationInterface::ResetRequestCount, pwalletIn, _1)); - g_signals.BlockChecked.disconnect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); - g_signals.Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1)); - g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1)); - g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1)); - g_signals.NotifyTransactionLock.disconnect(boost::bind(&CValidationInterface::NotifyTransactionLock, pwalletIn, _1)); - g_signals.SyncTransaction.disconnect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2, _3)); - g_signals.ChainTip.disconnect(boost::bind(&CValidationInterface::ChainTip, pwalletIn, _1, _2, _3)); - g_signals.UpdatedBlockTip.disconnect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3)); + if (g_signals.m_internals) { + g_signals.m_internals->Unregister(pwalletIn); + } +} + +void CallFunctionInValidationInterfaceQueue(std::function func) { + g_signals.m_internals->m_schedulerClient.AddToProcessQueue(std::move(func)); } void UnregisterAllValidationInterfaces() { - g_signals.BlockFound.disconnect_all_slots(); - g_signals.BlockChecked.disconnect_all_slots(); - g_signals.Broadcast.disconnect_all_slots(); - g_signals.SetBestChain.disconnect_all_slots(); - g_signals.UpdatedTransaction.disconnect_all_slots(); - g_signals.NotifyTransactionLock.disconnect_all_slots(); - g_signals.SyncTransaction.disconnect_all_slots(); - g_signals.ChainTip.disconnect_all_slots(); - g_signals.UpdatedBlockTip.disconnect_all_slots(); + if (!g_signals.m_internals) { + return; + } + g_signals.m_internals->Clear(); +} + +void CMainSignals::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload); }); +} + +void CMainSignals::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock) { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.SyncTransaction(tx, pindex, posInBlock); }); +} + +void CMainSignals::NotifyTransactionLock(const CTransaction& tx) { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.NotifyTransactionLock(tx); }); +} + +void CMainSignals::UpdatedTransaction(const uint256& hash) { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.UpdatedTransaction(hash); }); } + +void CMainSignals::SetBestChain(const CBlockLocator& locator) { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.SetBestChain(locator); }); +} + +void CMainSignals::Broadcast(CConnman* connman) { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.Broadcast(connman); }); +} + +void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& state) { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.BlockChecked(block, state); }); +} + +void CMainSignals::BlockFound(const uint256& hash) { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.BlockFound(hash); }); +} + +void CMainSignals::ChainTip(const CBlockIndex* pindex, const CBlock* block, Optional tree) { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.ChainTip(pindex, block, tree); }); +} \ No newline at end of file diff --git a/src/validationinterface.h b/src/validationinterface.h index f50349eb645b..a210f8cd7f04 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -10,7 +10,8 @@ #include "optional.h" #include "sapling/incrementalmerkletree.hpp" -#include +#include +#include class CBlock; struct CBlockLocator; @@ -21,6 +22,7 @@ class CTransaction; class CValidationInterface; class CValidationState; class uint256; +class CScheduler; // These functions dispatch to one or all registered wallets @@ -30,46 +32,76 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn); void UnregisterValidationInterface(CValidationInterface* pwalletIn); /** Unregister all wallets from core */ void UnregisterAllValidationInterfaces(); +/** + * Pushes a function to callback onto the notification queue, guaranteeing any + * callbacks generated prior to now are finished when the function is called. + * + * Be very careful blocking on func to be called if any locks are held - + * validation interface clients may not be able to make progress as they often + * wait for things like cs_main, so blocking until func is called with cs_main + * will result in a deadlock (that DEBUG_LOCKORDER will miss). + */ +void CallFunctionInValidationInterfaceQueue(std::function func); class CValidationInterface { -protected: +public: + /** + * Protected destructor so that instances can only be deleted by derived classes. + * If that restriction is no longer desired, this should be made public and virtual. + */ + ~CValidationInterface() = default; + + /** Notifies listeners of updated block chain tip */ virtual void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) {} virtual void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) {} virtual void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, Optional added) {} virtual void NotifyTransactionLock(const CTransaction &tx) {} + /** Notifies listeners of the new active block chain on-disk. */ virtual void SetBestChain(const CBlockLocator &locator) {} virtual bool UpdatedTransaction(const uint256 &hash) { return false;} + /** Tells listeners to broadcast their data. */ + virtual void Broadcast(CConnman* connman) { } virtual void ResendWalletTransactions(CConnman* connman) {} virtual void BlockChecked(const CBlock&, const CValidationState&) {} + virtual void BlockFound(const uint256&) {} virtual void ResetRequestCount(const uint256 &hash) {}; friend void ::RegisterValidationInterface(CValidationInterface*); friend void ::UnregisterValidationInterface(CValidationInterface*); friend void ::UnregisterAllValidationInterfaces(); }; -struct CMainSignals { -// XX42 boost::signals2::signal EraseTransaction; - /** Notifies listeners of updated block chain tip */ - boost::signals2::signal UpdatedBlockTip; +struct MainSignalsInstance; +class CMainSignals { +private: + std::unique_ptr m_internals; + + friend void ::RegisterValidationInterface(CValidationInterface*); + friend void ::UnregisterValidationInterface(CValidationInterface*); + friend void ::UnregisterAllValidationInterfaces(); + friend void ::CallFunctionInValidationInterfaceQueue(std::function func); + +public: /** A posInBlock value for SyncTransaction which indicates the transaction was conflicted, disconnected, or not in a block */ static const int SYNC_TRANSACTION_NOT_IN_BLOCK = -1; - /** Notifies listeners of updated transaction data (transaction, and optionally the block it is found in. */ - boost::signals2::signal SyncTransaction; - /** Notifies listeners of an updated transaction lock without new data. */ - boost::signals2::signal NotifyTransactionLock; - /** Notifies listeners of an updated transaction without new data (for now: a coinbase potentially becoming visible). */ - boost::signals2::signal UpdatedTransaction; - /** Notifies listeners of a new active block chain. */ - boost::signals2::signal SetBestChain; - /** Tells listeners to broadcast their data. */ - boost::signals2::signal Broadcast; - /** Notifies listeners of a block validation result */ - boost::signals2::signal BlockChecked; - /** Notifies listeners that a block has been successfully mined */ - boost::signals2::signal BlockFound; - - /** Notifies listeners of a change to the tip of the active block chain. */ - boost::signals2::signal)> ChainTip; + + /** Register a CScheduler to give callbacks which should run in the background (may only be called once) */ + void RegisterBackgroundSignalScheduler(CScheduler& scheduler); + /** Unregister a CScheduler to give callbacks which should run in the background - these callbacks will now be dropped! */ + void UnregisterBackgroundSignalScheduler(); + /** Call any remaining callbacks on the calling thread */ + void FlushBackgroundCallbacks(); + + size_t CallbacksPending(); + + void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload); + void SyncTransaction(const CTransaction &, const CBlockIndex *pindex, int posInBlock); + void NotifyTransactionLock(const CTransaction&); + void UpdatedTransaction(const uint256 &); + void SetBestChain(const CBlockLocator &); + void Broadcast(CConnman* connman); + void BlockChecked(const CBlock&, const CValidationState&); + void BlockFound(const uint256&); + void ChainTip(const CBlockIndex *, const CBlock *, Optional); }; CMainSignals& GetMainSignals(); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 53b0301aa83a..bb9ae542886a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3143,7 +3143,7 @@ UniValue walletpassphrase(const JSONRPCRequest& request) if (nSleepTime > 0) { nWalletUnlockTime = GetTime () + nSleepTime; - RPCRunLater ("lockwallet", boost::bind (LockWallet, pwalletMain), nSleepTime); + RPCRunLater ("lockwallet", std::bind (LockWallet, pwalletMain), nSleepTime); } return NullUniValue; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 16b1d9b4ed85..79b6a4e752cc 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1043,7 +1043,9 @@ bool AttemptBackupWallet(const CWallet& wallet, const fs::path& pathSrc, const f LogPrintf("cannot backup to wallet source file %s\n", pathDest.string()); return false; } -#if BOOST_VERSION >= 105800 /* BOOST_LIB_VERSION 1_58 */ +#if BOOST_VERSION >= 107400 + fs::copy_file(pathSrc.c_str(), pathDest, fs::copy_options::overwrite_existing); +#elif BOOST_VERSION >= 105800 /* BOOST_LIB_VERSION 1_58 */ fs::copy_file(pathSrc.c_str(), pathDest, fs::copy_option::overwrite_if_exists); #else std::ifstream src(pathSrc.c_str(), std::ios::binary | std::ios::in);