Skip to content

Commit 5c3c7cc

Browse files
author
MarcoFalke
committed
Merge #19300: wallet: Handle concurrent wallet loading
9b009fa qa: Test concurrent wallet loading (João Barbosa) b9971ae wallet: Handle concurrent wallet loading (João Barbosa) Pull request description: This PR handles concurrent wallet loading. This can be tested by running in parallel the following script a couple of times: ```sh for i in {1..10} do src/bitcoin-cli -regtest loadwallet foo src/bitcoin-cli -regtest unloadwallet foo done ``` Eventually the error occurs: ``` error code: -4 error message: Wallet already being loading. ``` For reference, loading and already loaded wallet gives: ``` error code: -4 error message: Wallet file verification failed. Error loading wallet w1. Duplicate -wallet filename specified. ``` Fixes #19232. ACKs for top commit: MarcoFalke: Concept ACK 9b009fa I have not reviewed the code hebasto: ACK 9b009fa, tested on Linux Mint 20 (x86_64): ryanofsky: Code review good-but-not-ideal ACK 9b009fa Tree-SHA512: 0ccd77b03c0926e4c4e51efb31e193b93cb4b9ffe8bac6bb018f7344c55dfd939b873b8cf5e657dca73e6202eb75aa672de2acb787cc133184b0b3b51e47b972
2 parents dbadf74 + 9b009fa commit 5c3c7cc

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

src/wallet/wallet.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,11 @@ std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet)
9999
return interfaces::MakeHandler([it] { LOCK(cs_wallets); g_load_wallet_fns.erase(it); });
100100
}
101101

102+
static Mutex g_loading_wallet_mutex;
102103
static Mutex g_wallet_release_mutex;
103104
static std::condition_variable g_wallet_release_cv;
104-
static std::set<std::string> g_unloading_wallet_set;
105+
static std::set<std::string> g_loading_wallet_set GUARDED_BY(g_loading_wallet_mutex);
106+
static std::set<std::string> g_unloading_wallet_set GUARDED_BY(g_wallet_release_mutex);
105107

106108
// Custom deleter for shared_ptr<CWallet>.
107109
static void ReleaseWallet(CWallet* wallet)
@@ -145,7 +147,8 @@ void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
145147
}
146148
}
147149

148-
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings)
150+
namespace {
151+
std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings)
149152
{
150153
try {
151154
if (!CWallet::Verify(chain, location, error, warnings)) {
@@ -166,6 +169,19 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocati
166169
return nullptr;
167170
}
168171
}
172+
} // namespace
173+
174+
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings)
175+
{
176+
auto result = WITH_LOCK(g_loading_wallet_mutex, return g_loading_wallet_set.insert(location.GetName()));
177+
if (!result.second) {
178+
error = Untranslated("Wallet already being loading.");
179+
return nullptr;
180+
}
181+
auto wallet = LoadWalletInternal(chain, location, error, warnings);
182+
WITH_LOCK(g_loading_wallet_mutex, g_loading_wallet_set.erase(result.first));
183+
return wallet;
184+
}
169185

170186
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings)
171187
{

test/functional/wallet_multiwallet.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,36 @@
77
Verify that a bitcoind node can load multiple wallet files
88
"""
99
from decimal import Decimal
10+
from threading import Thread
1011
import os
1112
import shutil
1213
import time
1314

15+
from test_framework.authproxy import JSONRPCException
1416
from test_framework.test_framework import BitcoinTestFramework
1517
from test_framework.test_node import ErrorMatch
1618
from test_framework.util import (
1719
assert_equal,
1820
assert_raises_rpc_error,
21+
get_rpc_proxy,
1922
)
2023

2124
FEATURE_LATEST = 169900
2225

26+
got_loading_error = False
27+
def test_load_unload(node, name):
28+
global got_loading_error
29+
for i in range(10):
30+
if got_loading_error:
31+
return
32+
try:
33+
node.loadwallet(name)
34+
node.unloadwallet(name)
35+
except JSONRPCException as e:
36+
if e.error['code'] == -4 and 'Wallet already being loading' in e.error['message']:
37+
got_loading_error = True
38+
return
39+
2340

2441
class MultiWalletTest(BitcoinTestFramework):
2542
def set_test_params(self):
@@ -212,6 +229,18 @@ def wallet_file(name):
212229
w2 = node.get_wallet_rpc(wallet_names[1])
213230
w2.getwalletinfo()
214231

232+
self.log.info("Concurrent wallet loading")
233+
threads = []
234+
for _ in range(3):
235+
n = node.cli if self.options.usecli else get_rpc_proxy(node.url, 1, timeout=600, coveragedir=node.coverage_dir)
236+
t = Thread(target=test_load_unload, args=(n, wallet_names[2], ))
237+
t.start()
238+
threads.append(t)
239+
for t in threads:
240+
t.join()
241+
global got_loading_error
242+
assert_equal(got_loading_error, True)
243+
215244
self.log.info("Load remaining wallets")
216245
for wallet_name in wallet_names[2:]:
217246
loadwallet_name = self.nodes[0].loadwallet(wallet_name)

0 commit comments

Comments
 (0)