Merge #15002: 0.17: Backport #14941

0cd9ad208c rpc: Make unloadwallet wait for complete wallet unload (João Barbosa)

Pull request description:

  #14941 makes `unloadwallet` a synchronous call meaning that it waits for the wallet to fully unload/delete.

Tree-SHA512: df7a490306ee2cca399129a4ebfba4b19b65fe67d1657ec3518352fe453327cb347010f94cf7fe4a60aeb51c928cb9ad6b24c40123fd0b9dc0aab5920a59f48d
This commit is contained in:
Wladimir J. van der Laan 2019-01-31 15:35:26 +01:00
commit 30db5cc641
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
5 changed files with 56 additions and 12 deletions

View file

@ -282,10 +282,10 @@ void Shutdown()
LogPrintf("%s: Unable to remove pidfile: %s\n", __func__, e.what()); LogPrintf("%s: Unable to remove pidfile: %s\n", __func__, e.what());
} }
#endif #endif
g_wallet_init_interface.Close();
UnregisterAllValidationInterfaces(); UnregisterAllValidationInterfaces();
GetMainSignals().UnregisterBackgroundSignalScheduler(); GetMainSignals().UnregisterBackgroundSignalScheduler();
GetMainSignals().UnregisterWithMempoolSignals(mempool); GetMainSignals().UnregisterWithMempoolSignals(mempool);
g_wallet_init_interface.Close();
globalVerifyHandle.reset(); globalVerifyHandle.reset();
ECC_Stop(); ECC_Stop();
LogPrintf("%s: done\n", __func__); LogPrintf("%s: done\n", __func__);

View file

@ -264,7 +264,11 @@ void WalletInit::Stop() const
void WalletInit::Close() const void WalletInit::Close() const
{ {
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { auto wallets = GetWallets();
RemoveWallet(pwallet); while (!wallets.empty()) {
auto wallet = wallets.back();
wallets.pop_back();
RemoveWallet(wallet);
UnloadWallet(std::move(wallet));
} }
} }

View file

@ -3239,16 +3239,8 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
if (!RemoveWallet(wallet)) { if (!RemoveWallet(wallet)) {
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded"); throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
} }
UnregisterValidationInterface(wallet.get());
// The wallet can be in use so it's not possible to explicitly unload here. UnloadWallet(std::move(wallet));
// Just notify the unload intent so that all shared pointers are released.
// The wallet will be destroyed once the last shared pointer is released.
wallet->NotifyUnload();
// There's no point in waiting for the wallet to unload.
// At this point this method should never fail. The unloading could only
// fail due to an unexpected error which would cause a process termination.
return NullUniValue; return NullUniValue;
} }

View file

@ -81,13 +81,52 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name)
return nullptr; return nullptr;
} }
static std::mutex g_wallet_release_mutex;
static std::condition_variable g_wallet_release_cv;
static std::set<CWallet*> 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.
wallet->WalletLogPrintf("Releasing wallet\n"); wallet->WalletLogPrintf("Releasing wallet\n");
wallet->BlockUntilSyncedToCurrentChain(); wallet->BlockUntilSyncedToCurrentChain();
wallet->Flush(); wallet->Flush();
UnregisterValidationInterface(wallet);
delete wallet; delete wallet;
// Wallet is now released, notify UnloadWallet, if any.
{
std::lock_guard<std::mutex> lock(g_wallet_release_mutex);
if (g_unloading_wallet_set.erase(wallet) == 0) {
// UnloadWallet was not called for this wallet, all done.
return;
}
}
g_wallet_release_cv.notify_all();
}
void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
{
// Mark wallet for unloading.
CWallet* pwallet = wallet.get();
{
std::lock_guard<std::mutex> lock(g_wallet_release_mutex);
auto it = g_unloading_wallet_set.insert(pwallet);
assert(it.second);
}
// The wallet can be in use so it's not possible to explicitly unload here.
// Notify the unload intent so that all remaining shared pointers are
// released.
pwallet->NotifyUnload();
// Time to ditch our shared_ptr and wait for ReleaseWallet call.
wallet.reset();
{
std::unique_lock<std::mutex> lock(g_wallet_release_mutex);
while (g_unloading_wallet_set.count(pwallet) == 1) {
g_wallet_release_cv.wait(lock);
}
}
} }
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;

View file

@ -33,6 +33,13 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
//! Explicitly unload and delete the wallet.
// Blocks the current thread after signaling the unload intent so that all
// wallet clients release the wallet.
// Note that, when blocking is not required, the wallet is implicitly unloaded
// by the shared pointer deleter.
void UnloadWallet(std::shared_ptr<CWallet>&& wallet);
bool AddWallet(const std::shared_ptr<CWallet>& wallet); bool AddWallet(const std::shared_ptr<CWallet>& wallet);
bool RemoveWallet(const std::shared_ptr<CWallet>& wallet); bool RemoveWallet(const std::shared_ptr<CWallet>& wallet);
bool HasWallets(); bool HasWallets();
@ -817,6 +824,8 @@ public:
~CWallet() ~CWallet()
{ {
// Should not have slots connected at this point.
assert(NotifyUnload.empty());
delete encrypted_batch; delete encrypted_batch;
encrypted_batch = nullptr; encrypted_batch = nullptr;
} }