Skip to content

Commit 99ecb9a

Browse files
committed
Merge bitcoin/bitcoin#30659: wallet: fix UnloadWallet thread safety assumptions
f550a8e Rename ReleaseWallet to FlushAndDeleteWallet (furszy) 64e736d wallet: WaitForDeleteWallet, do not expect thread safety (Ryan Ofsky) 8872b4a wallet: rename UnloadWallet to WaitForDeleteWallet (furszy) 5d15485 wallet: unload, notify GUI as soon as possible (furszy) Pull request description: Coming from #29073. Applied ryanofsky suggested changes on bitcoin/bitcoin#29073 (comment) with few modifications coming from bitcoin/bitcoin#18338 (comment). The only point I did not tackle from bitcoin/bitcoin#18338 (comment) is: > * Move log print and flush out of ReleaseWallet into CWallet destructor Because it would mean every `CWallet` object would flush data to disk during destruction. Which is not necessary for wallet tool utilities and unit tests. ACKs for top commit: achow101: ACK f550a8e ryanofsky: Code review ACK f550a8e. Just a simple rename since last review ismaelsadeeq: Re-ACK f550a8e Tree-SHA512: e2eb69bf36883c514f601f4838ae6a41113996b9559abf8dc2b46e16bbcdad401195ac0f2b9d1fb55a10e78bb8ea9953788a168c80474e3f101350d208cb3bd2
2 parents 2f7d9ae + f550a8e commit 99ecb9a

File tree

7 files changed

+28
-30
lines changed

7 files changed

+28
-30
lines changed

src/bench/wallet_create.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ static void WalletCreate(benchmark::Bench& bench, bool encrypted)
4242

4343
// Release wallet
4444
RemoveWallet(context, wallet, /*load_on_start=*/ std::nullopt);
45-
UnloadWallet(std::move(wallet));
45+
WaitForDeleteWallet(std::move(wallet));
4646
fs::remove_all(wallet_path);
4747
});
4848
}

src/wallet/load.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ void UnloadWallets(WalletContext& context)
178178
wallets.pop_back();
179179
std::vector<bilingual_str> warnings;
180180
RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt, warnings);
181-
UnloadWallet(std::move(wallet));
181+
WaitForDeleteWallet(std::move(wallet));
182182
}
183183
}
184184
} // namespace wallet

src/wallet/rpc/wallet.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ static RPCHelpMan unloadwallet()
495495
}
496496
}
497497

498-
UnloadWallet(std::move(wallet));
498+
WaitForDeleteWallet(std::move(wallet));
499499

500500
UniValue result(UniValue::VOBJ);
501501
PushWarnings(warnings, result);

src/wallet/test/util.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet)
7474
// Calls SyncWithValidationInterfaceQueue
7575
wallet->chain().waitForNotificationsIfTipChanged({});
7676
wallet->m_chain_notifications_handler.reset();
77-
UnloadWallet(std::move(wallet));
77+
WaitForDeleteWallet(std::move(wallet));
7878
}
7979

8080
std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database)

src/wallet/test/wallet_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
889889
context.args = &m_args;
890890
auto wallet = TestLoadWallet(context);
891891
BOOST_CHECK(wallet);
892-
UnloadWallet(std::move(wallet));
892+
WaitForDeleteWallet(std::move(wallet));
893893
}
894894

895895
BOOST_FIXTURE_TEST_CASE(RemoveTxs, TestChain100Setup)

src/wallet/wallet.cpp

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,14 @@ bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet
162162

163163
// Unregister with the validation interface which also drops shared pointers.
164164
wallet->m_chain_notifications_handler.reset();
165-
LOCK(context.wallets_mutex);
166-
std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(context.wallets.begin(), context.wallets.end(), wallet);
167-
if (i == context.wallets.end()) return false;
168-
context.wallets.erase(i);
165+
{
166+
LOCK(context.wallets_mutex);
167+
std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(context.wallets.begin(), context.wallets.end(), wallet);
168+
if (i == context.wallets.end()) return false;
169+
context.wallets.erase(i);
170+
}
171+
// Notify unload so that upper layers release the shared pointer.
172+
wallet->NotifyUnload();
169173

170174
// Write the wallet setting
171175
UpdateWalletSetting(chain, name, load_on_start, warnings);
@@ -223,38 +227,35 @@ static std::set<std::string> g_loading_wallet_set GUARDED_BY(g_loading_wallet_mu
223227
static std::set<std::string> g_unloading_wallet_set GUARDED_BY(g_wallet_release_mutex);
224228

225229
// Custom deleter for shared_ptr<CWallet>.
226-
static void ReleaseWallet(CWallet* wallet)
230+
static void FlushAndDeleteWallet(CWallet* wallet)
227231
{
228232
const std::string name = wallet->GetName();
229-
wallet->WalletLogPrintf("Releasing wallet\n");
233+
wallet->WalletLogPrintf("Releasing wallet %s..\n", name);
230234
wallet->Flush();
231235
delete wallet;
232-
// Wallet is now released, notify UnloadWallet, if any.
236+
// Wallet is now released, notify WaitForDeleteWallet, if any.
233237
{
234238
LOCK(g_wallet_release_mutex);
235239
if (g_unloading_wallet_set.erase(name) == 0) {
236-
// UnloadWallet was not called for this wallet, all done.
240+
// WaitForDeleteWallet was not called for this wallet, all done.
237241
return;
238242
}
239243
}
240244
g_wallet_release_cv.notify_all();
241245
}
242246

243-
void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
247+
void WaitForDeleteWallet(std::shared_ptr<CWallet>&& wallet)
244248
{
245249
// Mark wallet for unloading.
246250
const std::string name = wallet->GetName();
247251
{
248252
LOCK(g_wallet_release_mutex);
249-
auto it = g_unloading_wallet_set.insert(name);
250-
assert(it.second);
253+
g_unloading_wallet_set.insert(name);
254+
// Do not expect to be the only one removing this wallet.
255+
// Multiple threads could simultaneously be waiting for deletion.
251256
}
252-
// The wallet can be in use so it's not possible to explicitly unload here.
253-
// Notify the unload intent so that all remaining shared pointers are
254-
// released.
255-
wallet->NotifyUnload();
256257

257-
// Time to ditch our shared_ptr and wait for ReleaseWallet call.
258+
// Time to ditch our shared_ptr and wait for FlushAndDeleteWallet call.
258259
wallet.reset();
259260
{
260261
WAIT_LOCK(g_wallet_release_mutex, lock);
@@ -2971,7 +2972,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
29712972
const auto start{SteadyClock::now()};
29722973
// TODO: Can't use std::make_shared because we need a custom deleter but
29732974
// should be possible to use std::allocate_shared.
2974-
std::shared_ptr<CWallet> walletInstance(new CWallet(chain, name, std::move(database)), ReleaseWallet);
2975+
std::shared_ptr<CWallet> walletInstance(new CWallet(chain, name, std::move(database)), FlushAndDeleteWallet);
29752976
walletInstance->m_keypool_size = std::max(args.GetIntArg("-keypool", DEFAULT_KEYPOOL_SIZE), int64_t{1});
29762977
walletInstance->m_notify_tx_changed_script = args.GetArg("-walletnotify", "");
29772978

@@ -4387,7 +4388,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
43874388
if (!RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt, warnings)) {
43884389
return util::Error{_("Unable to unload the wallet before migrating")};
43894390
}
4390-
UnloadWallet(std::move(wallet));
4391+
WaitForDeleteWallet(std::move(wallet));
43914392
was_loaded = true;
43924393
} else {
43934394
// Check if the wallet is BDB
@@ -4531,7 +4532,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
45314532
error += _("\nUnable to cleanup failed migration");
45324533
return util::Error{error};
45334534
}
4534-
UnloadWallet(std::move(w));
4535+
WaitForDeleteWallet(std::move(w));
45354536
} else {
45364537
// Unloading for wallets in local context
45374538
assert(w.use_count() == 1);

src/wallet/wallet.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,9 @@ struct bilingual_str;
8383
namespace wallet {
8484
struct WalletContext;
8585

86-
//! Explicitly unload and delete the wallet.
87-
//! Blocks the current thread after signaling the unload intent so that all
88-
//! wallet pointer owners release the wallet.
89-
//! Note that, when blocking is not required, the wallet is implicitly unloaded
90-
//! by the shared pointer deleter.
91-
void UnloadWallet(std::shared_ptr<CWallet>&& wallet);
86+
//! Explicitly delete the wallet.
87+
//! Blocks the current thread until the wallet is destructed.
88+
void WaitForDeleteWallet(std::shared_ptr<CWallet>&& wallet);
9289

9390
bool AddWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet);
9491
bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start, std::vector<bilingual_str>& warnings);

0 commit comments

Comments
 (0)