Skip to content

Commit 1b6fc30

Browse files
committed
Merge #14941: rpc: Make unloadwallet wait for complete wallet unload
645e905 doc: Add release notes for unloadwallet change to synchronous call (João Barbosa) c37851d rpc: Make unloadwallet wait for complete wallet unload (João Barbosa) Pull request description: Currently the `unloadwallet` RPC is asynchronous, it only signals the intent to unload the wallet and then returns the response to the client. The actual unload can happen later and the client has no way to be notified of that. This PR makes the `unloadwallet` RPC synchronous, meaning that it blocks until the wallet is fully unloaded. Replaces #14919, fixes #14917. Tree-SHA512: ad88b980e2f3652809a58f904afbfe020299f3aa6a517f495ba943b8d54d4520f6e70074d6749be8f5967065c0f476e0faedcde64c8b4899e5f99c70f0fd6534
2 parents cf0c67b + 645e905 commit 1b6fc30

File tree

6 files changed

+61
-12
lines changed

6 files changed

+61
-12
lines changed

doc/release-notes-14941.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Miscellaneous RPC changes
2+
------------
3+
4+
- The `unloadwallet` RPC is now synchronous, meaning that it blocks until the
5+
wallet is fully unloaded.

src/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,10 @@ void Shutdown(InitInterfaces& interfaces)
261261
LogPrintf("%s: Unable to remove pidfile: %s\n", __func__, e.what());
262262
}
263263
#endif
264+
interfaces.chain_clients.clear();
264265
UnregisterAllValidationInterfaces();
265266
GetMainSignals().UnregisterBackgroundSignalScheduler();
266267
GetMainSignals().UnregisterWithMempoolSignals(mempool);
267-
interfaces.chain_clients.clear();
268268
globalVerifyHandle.reset();
269269
ECC_Stop();
270270
LogPrintf("%s: done\n", __func__);

src/wallet/init.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,11 @@ void StopWallets()
242242

243243
void UnloadWallets()
244244
{
245-
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
246-
RemoveWallet(pwallet);
245+
auto wallets = GetWallets();
246+
while (!wallets.empty()) {
247+
auto wallet = wallets.back();
248+
wallets.pop_back();
249+
RemoveWallet(wallet);
250+
UnloadWallet(std::move(wallet));
247251
}
248252
}

src/wallet/rpcwallet.cpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,16 +2637,8 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
26372637
if (!RemoveWallet(wallet)) {
26382638
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
26392639
}
2640-
UnregisterValidationInterface(wallet.get());
26412640

2642-
// The wallet can be in use so it's not possible to explicitly unload here.
2643-
// Just notify the unload intent so that all shared pointers are released.
2644-
// The wallet will be destroyed once the last shared pointer is released.
2645-
wallet->NotifyUnload();
2646-
2647-
// There's no point in waiting for the wallet to unload.
2648-
// At this point this method should never fail. The unloading could only
2649-
// fail due to an unexpected error which would cause a process termination.
2641+
UnloadWallet(std::move(wallet));
26502642

26512643
return NullUniValue;
26522644
}

src/wallet/wallet.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,52 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name)
8282
return nullptr;
8383
}
8484

85+
static Mutex g_wallet_release_mutex;
86+
static std::condition_variable g_wallet_release_cv;
87+
static std::set<CWallet*> g_unloading_wallet_set;
88+
8589
// Custom deleter for shared_ptr<CWallet>.
8690
static void ReleaseWallet(CWallet* wallet)
8791
{
92+
// Unregister and delete the wallet right after BlockUntilSyncedToCurrentChain
93+
// so that it's in sync with the current chainstate.
8894
wallet->WalletLogPrintf("Releasing wallet\n");
8995
wallet->BlockUntilSyncedToCurrentChain();
9096
wallet->Flush();
97+
UnregisterValidationInterface(wallet);
9198
delete wallet;
99+
// Wallet is now released, notify UnloadWallet, if any.
100+
{
101+
LOCK(g_wallet_release_mutex);
102+
if (g_unloading_wallet_set.erase(wallet) == 0) {
103+
// UnloadWallet was not called for this wallet, all done.
104+
return;
105+
}
106+
}
107+
g_wallet_release_cv.notify_all();
108+
}
109+
110+
void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
111+
{
112+
// Mark wallet for unloading.
113+
CWallet* pwallet = wallet.get();
114+
{
115+
LOCK(g_wallet_release_mutex);
116+
auto it = g_unloading_wallet_set.insert(pwallet);
117+
assert(it.second);
118+
}
119+
// The wallet can be in use so it's not possible to explicitly unload here.
120+
// Notify the unload intent so that all remaining shared pointers are
121+
// released.
122+
pwallet->NotifyUnload();
123+
// Time to ditch our shared_ptr and wait for ReleaseWallet call.
124+
wallet.reset();
125+
{
126+
WAIT_LOCK(g_wallet_release_mutex, lock);
127+
while (g_unloading_wallet_set.count(pwallet) == 1) {
128+
g_wallet_release_cv.wait(lock);
129+
}
130+
}
92131
}
93132

94133
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;

src/wallet/wallet.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ void StopWallets();
5454
//! Close all wallets.
5555
void UnloadWallets();
5656

57+
//! Explicitly unload and delete the wallet.
58+
// Blocks the current thread after signaling the unload intent so that all
59+
// wallet clients release the wallet.
60+
// Note that, when blocking is not required, the wallet is implicitly unloaded
61+
// by the shared pointer deleter.
62+
void UnloadWallet(std::shared_ptr<CWallet>&& wallet);
63+
5764
bool AddWallet(const std::shared_ptr<CWallet>& wallet);
5865
bool RemoveWallet(const std::shared_ptr<CWallet>& wallet);
5966
bool HasWallets();
@@ -770,6 +777,8 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
770777

771778
~CWallet()
772779
{
780+
// Should not have slots connected at this point.
781+
assert(NotifyUnload.empty());
773782
delete encrypted_batch;
774783
encrypted_batch = nullptr;
775784
}

0 commit comments

Comments
 (0)