Merge #18338: Fix wallet unload race condition

41b0baf43c gui: Handle WalletModel::unload asynchronous (João Barbosa)
ab31b9d6fe Fix wallet unload race condition (Russell Yanofsky)

Pull request description:

  This PR consists in two fixes. The first fixes a concurrency issues with `boost::signals2`. The second fixes a wallet model destruction while it's being used.

  From boost signal documentation at https://www.boost.org/doc/libs/1_72_0/doc/html/signals2/thread-safety.html:

  > When a signal is invoked by calling signal::operator(), the invocation first acquires a lock on the signal's mutex. Then it obtains a handle to the signal's slot list and combiner. Next it releases the signal's mutex, before invoking the combiner to iterate through the slot list.

  This means that `UnregisterValidationInterface` doesn't prevent more calls to that interface. The fix consists in capturing the `shared_ptr<CValidationInterface>` in each internal slot.

  The GUI bug is fixed by using a `Qt::QueuedConnection` in the `WalletModel::unload` connection.

ACKs for top commit:
  ryanofsky:
    Code review ACK 41b0baf43c. Only change is moving assert as suggested
  hebasto:
    ACK 41b0baf43c, tested on Linux Mint 19.3.

Tree-SHA512: 4f712d8de65bc1214411831250de5dc0a9fd505fb84da5baf9f2cc4d551bc3abffc061616f00afe43dba7525af2cd96c9b54aeead9383145e3b8801f25d85f50
This commit is contained in:
Wladimir J. van der Laan committed 2020-03-31 15:07:06 +02:00
commit 9a2b5f22c1
10 files changed
+64 -44

No files matched your search

+1 -2
View File
@@ -23,9 +23,8 @@ static void WalletBalance(benchmark::State& state, const bool set_dirty, const b
wallet.SetupLegacyScriptPubKeyMan(); wallet.SetupLegacyScriptPubKeyMan();
bool first_run; bool first_run;
if (wallet.LoadWallet(first_run) != DBErrors::LOAD_OK) assert(false); if (wallet.LoadWallet(first_run) != DBErrors::LOAD_OK) assert(false);
wallet.handleNotifications();
} }
auto handler = chain->handleNotifications({ &wallet, [](CWallet*) {} });
const Optional<std::string> address_mine{add_mine ? Optional<std::string>{getnewaddress(wallet)} : nullopt}; const Optional<std::string> address_mine{add_mine ? Optional<std::string>{getnewaddress(wallet)} : nullopt};
if (add_watchonly) importaddress(wallet, ADDRESS_WATCHONLY); if (add_watchonly) importaddress(wallet, ADDRESS_WATCHONLY);
+26 -18
View File
@@ -148,22 +148,12 @@ class LockImpl : public Chain::Lock, public UniqueLock<RecursiveMutex>
using UniqueLock::UniqueLock; using UniqueLock::UniqueLock;
}; };
class NotificationsHandlerImpl : public Handler, CValidationInterface class NotificationsProxy : public CValidationInterface
{ {
public: public:
explicit NotificationsHandlerImpl(Chain& chain, Chain::Notifications& notifications) explicit NotificationsProxy(std::shared_ptr<Chain::Notifications> notifications)
: m_chain(chain), m_notifications(&notifications) : m_notifications(std::move(notifications)) {}
{ virtual ~NotificationsProxy() = default;
RegisterValidationInterface(this);
}
~NotificationsHandlerImpl() override { disconnect(); }
void disconnect() override
{
if (m_notifications) {
m_notifications = nullptr;
UnregisterValidationInterface(this);
}
}
void TransactionAddedToMempool(const CTransactionRef& tx) override void TransactionAddedToMempool(const CTransactionRef& tx) override
{ {
m_notifications->transactionAddedToMempool(tx); m_notifications->transactionAddedToMempool(tx);
@@ -185,8 +175,26 @@ public:
m_notifications->updatedBlockTip(); m_notifications->updatedBlockTip();
} }
void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->chainStateFlushed(locator); } void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->chainStateFlushed(locator); }
Chain& m_chain; std::shared_ptr<Chain::Notifications> m_notifications;
Chain::Notifications* m_notifications; };
class NotificationsHandlerImpl : public Handler
{
public:
explicit NotificationsHandlerImpl(std::shared_ptr<Chain::Notifications> notifications)
: m_proxy(std::make_shared<NotificationsProxy>(std::move(notifications)))
{
RegisterSharedValidationInterface(m_proxy);
}
~NotificationsHandlerImpl() override { disconnect(); }
void disconnect() override
{
if (m_proxy) {
UnregisterSharedValidationInterface(m_proxy);
m_proxy.reset();
}
}
std::shared_ptr<NotificationsProxy> m_proxy;
}; };
class RpcHandlerImpl : public Handler class RpcHandlerImpl : public Handler
@@ -342,9 +350,9 @@ public:
{ {
::uiInterface.ShowProgress(title, progress, resume_possible); ::uiInterface.ShowProgress(title, progress, resume_possible);
} }
std::unique_ptr<Handler> handleNotifications(Notifications& notifications) override std::unique_ptr<Handler> handleNotifications(std::shared_ptr<Notifications> notifications) override
{ {
return MakeUnique<NotificationsHandlerImpl>(*this, notifications); return MakeUnique<NotificationsHandlerImpl>(std::move(notifications));
} }
void waitForNotificationsIfTipChanged(const uint256& old_tip) override void waitForNotificationsIfTipChanged(const uint256& old_tip) override
{ {
+1 -1
View File
@@ -229,7 +229,7 @@ public:
}; };
//! Register handler for notifications. //! Register handler for notifications.
virtual std::unique_ptr<Handler> handleNotifications(Notifications& notifications) = 0; virtual std::unique_ptr<Handler> handleNotifications(std::shared_ptr<Notifications> notifications) = 0;
//! Wait for pending notifications to be processed unless block hash points to the current //! Wait for pending notifications to be processed unless block hash points to the current
//! chain tip. //! chain tip.
+2 -2
View File
@@ -116,7 +116,7 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal
const bool called = QMetaObject::invokeMethod(wallet_model, "startPollBalance"); const bool called = QMetaObject::invokeMethod(wallet_model, "startPollBalance");
assert(called); assert(called);
connect(wallet_model, &WalletModel::unload, [this, wallet_model] { connect(wallet_model, &WalletModel::unload, this, [this, wallet_model] {
// Defer removeAndDeleteWallet when no modal widget is active. // Defer removeAndDeleteWallet when no modal widget is active.
// TODO: remove this workaround by removing usage of QDiallog::exec. // TODO: remove this workaround by removing usage of QDiallog::exec.
if (QApplication::activeModalWidget()) { if (QApplication::activeModalWidget()) {
@@ -128,7 +128,7 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal
} else { } else {
removeAndDeleteWallet(wallet_model); removeAndDeleteWallet(wallet_model);
} }
}); }, Qt::QueuedConnection);
// Re-emit coinsSent signal from wallet model. // Re-emit coinsSent signal from wallet model.
connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent); connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent);
+16 -2
View File
@@ -75,8 +75,10 @@ CMainSignals& GetMainSignals()
return g_signals; return g_signals;
} }
void RegisterValidationInterface(CValidationInterface* pwalletIn) { void RegisterSharedValidationInterface(std::shared_ptr<CValidationInterface> pwalletIn) {
ValidationInterfaceConnections& conns = g_signals.m_internals->m_connMainSignals[pwalletIn]; // Each connection captures pwalletIn to ensure that each callback is
// executed before pwalletIn is destroyed. For more details see #18338.
ValidationInterfaceConnections& conns = g_signals.m_internals->m_connMainSignals[pwalletIn.get()];
conns.UpdatedBlockTip = g_signals.m_internals->UpdatedBlockTip.connect(std::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); conns.UpdatedBlockTip = g_signals.m_internals->UpdatedBlockTip.connect(std::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
conns.TransactionAddedToMempool = g_signals.m_internals->TransactionAddedToMempool.connect(std::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, std::placeholders::_1)); conns.TransactionAddedToMempool = g_signals.m_internals->TransactionAddedToMempool.connect(std::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, std::placeholders::_1));
conns.BlockConnected = g_signals.m_internals->BlockConnected.connect(std::bind(&CValidationInterface::BlockConnected, pwalletIn, std::placeholders::_1, std::placeholders::_2)); conns.BlockConnected = g_signals.m_internals->BlockConnected.connect(std::bind(&CValidationInterface::BlockConnected, pwalletIn, std::placeholders::_1, std::placeholders::_2));
@@ -87,6 +89,18 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
conns.NewPoWValidBlock = g_signals.m_internals->NewPoWValidBlock.connect(std::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, std::placeholders::_1, std::placeholders::_2)); conns.NewPoWValidBlock = g_signals.m_internals->NewPoWValidBlock.connect(std::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, std::placeholders::_1, std::placeholders::_2));
} }
void RegisterValidationInterface(CValidationInterface* callbacks)
{
// Create a shared_ptr with a no-op deleter - CValidationInterface lifecycle
// is managed by the caller.
RegisterSharedValidationInterface({callbacks, [](CValidationInterface*){}});
}
void UnregisterSharedValidationInterface(std::shared_ptr<CValidationInterface> callbacks)
{
UnregisterValidationInterface(callbacks.get());
}
void UnregisterValidationInterface(CValidationInterface* pwalletIn) { void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
if (g_signals.m_internals) { if (g_signals.m_internals) {
g_signals.m_internals->m_connMainSignals.erase(pwalletIn); g_signals.m_internals->m_connMainSignals.erase(pwalletIn);
+10 -2
View File
@@ -30,6 +30,14 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn);
void UnregisterValidationInterface(CValidationInterface* pwalletIn); void UnregisterValidationInterface(CValidationInterface* pwalletIn);
/** Unregister all wallets from core */ /** Unregister all wallets from core */
void UnregisterAllValidationInterfaces(); void UnregisterAllValidationInterfaces();
// Alternate registration functions that release a shared_ptr after the last
// notification is sent. These are useful for race-free cleanup, since
// unregistration is nonblocking and can return before the last notification is
// processed.
void RegisterSharedValidationInterface(std::shared_ptr<CValidationInterface> callbacks);
void UnregisterSharedValidationInterface(std::shared_ptr<CValidationInterface> callbacks);
/** /**
* Pushes a function to callback onto the notification queue, guaranteeing any * Pushes a function to callback onto the notification queue, guaranteeing any
* callbacks generated prior to now are finished when the function is called. * callbacks generated prior to now are finished when the function is called.
@@ -163,7 +171,7 @@ protected:
* Notifies listeners that a block which builds directly on our current tip * Notifies listeners that a block which builds directly on our current tip
* has been received and connected to the headers tree, though not validated yet */ * has been received and connected to the headers tree, though not validated yet */
virtual void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& block) {}; virtual void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& block) {};
friend void ::RegisterValidationInterface(CValidationInterface*); friend void ::RegisterSharedValidationInterface(std::shared_ptr<CValidationInterface>);
friend void ::UnregisterValidationInterface(CValidationInterface*); friend void ::UnregisterValidationInterface(CValidationInterface*);
friend void ::UnregisterAllValidationInterfaces(); friend void ::UnregisterAllValidationInterfaces();
}; };
@@ -173,7 +181,7 @@ class CMainSignals {
private: private:
std::unique_ptr<MainSignalsInstance> m_internals; std::unique_ptr<MainSignalsInstance> m_internals;
friend void ::RegisterValidationInterface(CValidationInterface*); friend void ::RegisterSharedValidationInterface(std::shared_ptr<CValidationInterface>);
friend void ::UnregisterValidationInterface(CValidationInterface*); friend void ::UnregisterValidationInterface(CValidationInterface*);
friend void ::UnregisterAllValidationInterfaces(); friend void ::UnregisterAllValidationInterfaces();
friend void ::CallFunctionInValidationInterfaceQueue(std::function<void ()> func); friend void ::CallFunctionInValidationInterfaceQueue(std::function<void ()> func);
+1 -2
View File
@@ -10,7 +10,6 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName)
{ {
bool fFirstRun; bool fFirstRun;
m_wallet.LoadWallet(fFirstRun); m_wallet.LoadWallet(fFirstRun);
m_wallet.handleNotifications(); m_chain_notifications_handler = m_chain->handleNotifications({ &m_wallet, [](CWallet*) {} });
m_chain_client->registerRpcs(); m_chain_client->registerRpcs();
} }
+1
View File
@@ -23,6 +23,7 @@ struct WalletTestingSetup: public TestingSetup {
std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node); std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node);
std::unique_ptr<interfaces::ChainClient> m_chain_client = interfaces::MakeWalletClient(*m_chain, {}); std::unique_ptr<interfaces::ChainClient> m_chain_client = interfaces::MakeWalletClient(*m_chain, {});
CWallet m_wallet; CWallet m_wallet;
std::unique_ptr<interfaces::Handler> m_chain_notifications_handler;
}; };
#endif // BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H #endif // BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H
+5 -11
View File
@@ -62,8 +62,10 @@ bool AddWallet(const std::shared_ptr<CWallet>& wallet)
bool RemoveWallet(const std::shared_ptr<CWallet>& wallet) bool RemoveWallet(const std::shared_ptr<CWallet>& wallet)
{ {
LOCK(cs_wallets);
assert(wallet); assert(wallet);
// Unregister with the validation interface which also drops shared ponters.
wallet->m_chain_notifications_handler.reset();
LOCK(cs_wallets);
std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet);
if (i == vpwallets.end()) return false; if (i == vpwallets.end()) return false;
vpwallets.erase(i); vpwallets.erase(i);
@@ -105,13 +107,9 @@ static std::set<std::string> g_unloading_wallet_set;
// Custom deleter for shared_ptr<CWallet>. // Custom deleter for shared_ptr<CWallet>.
static void ReleaseWallet(CWallet* wallet) static void ReleaseWallet(CWallet* wallet)
{ {
// Unregister and delete the wallet right after BlockUntilSyncedToCurrentChain
// so that it's in sync with the current chainstate.
const std::string name = wallet->GetName(); const std::string name = wallet->GetName();
wallet->WalletLogPrintf("Releasing wallet\n"); wallet->WalletLogPrintf("Releasing wallet\n");
wallet->BlockUntilSyncedToCurrentChain();
wallet->Flush(); wallet->Flush();
wallet->m_chain_notifications_handler.reset();
delete wallet; delete wallet;
// Wallet is now released, notify UnloadWallet, if any. // Wallet is now released, notify UnloadWallet, if any.
{ {
@@ -137,6 +135,7 @@ void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
// Notify the unload intent so that all remaining shared pointers are // Notify the unload intent so that all remaining shared pointers are
// released. // released.
wallet->NotifyUnload(); wallet->NotifyUnload();
// Time to ditch our shared_ptr and wait for ReleaseWallet call. // Time to ditch our shared_ptr and wait for ReleaseWallet call.
wallet.reset(); wallet.reset();
{ {
@@ -4092,7 +4091,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
} }
// Register with the validation interface. It's ok to do this after rescan since we're still holding locked_chain. // Register with the validation interface. It's ok to do this after rescan since we're still holding locked_chain.
walletInstance->handleNotifications(); walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance);
walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
@@ -4105,11 +4104,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
return walletInstance; return walletInstance;
} }
void CWallet::handleNotifications()
{
m_chain_notifications_handler = m_chain->handleNotifications(*this);
}
void CWallet::postInitProcess() void CWallet::postInitProcess()
{ {
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
+1 -4
View File
@@ -606,7 +606,7 @@ class WalletRescanReserver; //forward declarations for ScanForWalletTransactions
/** /**
* A CWallet maintains a set of transactions and balances, and provides the ability to create new transactions. * A CWallet maintains a set of transactions and balances, and provides the ability to create new transactions.
*/ */
class CWallet final : public WalletStorage, private interfaces::Chain::Notifications class CWallet final : public WalletStorage, public interfaces::Chain::Notifications
{ {
private: private:
CKeyingMaterial vMasterKey GUARDED_BY(cs_wallet); CKeyingMaterial vMasterKey GUARDED_BY(cs_wallet);
@@ -782,9 +782,6 @@ public:
/** Registered interfaces::Chain::Notifications handler. */ /** Registered interfaces::Chain::Notifications handler. */
std::unique_ptr<interfaces::Handler> m_chain_notifications_handler; std::unique_ptr<interfaces::Handler> m_chain_notifications_handler;
/** Register the wallet for chain notifications */
void handleNotifications();
/** Interface for accessing chain state. */ /** Interface for accessing chain state. */
interfaces::Chain& chain() const { assert(m_chain); return *m_chain; } interfaces::Chain& chain() const { assert(m_chain); return *m_chain; }