Skip to content

Commit

Permalink
Merge bitcoin/bitcoin#30659: wallet: fix UnloadWallet thread safety a…
Browse files Browse the repository at this point in the history
…ssumptions

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
  • Loading branch information
achow101 committed Aug 15, 2024
2 parents 2f7d9ae + f550a8e commit 99ecb9a
Show file tree
Hide file tree
Showing 7 changed files with 28 additions and 30 deletions.
2 changes: 1 addition & 1 deletion src/bench/wallet_create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ static void WalletCreate(benchmark::Bench& bench, bool encrypted)

// Release wallet
RemoveWallet(context, wallet, /*load_on_start=*/ std::nullopt);
UnloadWallet(std::move(wallet));
WaitForDeleteWallet(std::move(wallet));
fs::remove_all(wallet_path);
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/load.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ void UnloadWallets(WalletContext& context)
wallets.pop_back();
std::vector<bilingual_str> warnings;
RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt, warnings);
UnloadWallet(std::move(wallet));
WaitForDeleteWallet(std::move(wallet));
}
}
} // namespace wallet
2 changes: 1 addition & 1 deletion src/wallet/rpc/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ static RPCHelpMan unloadwallet()
}
}

UnloadWallet(std::move(wallet));
WaitForDeleteWallet(std::move(wallet));

UniValue result(UniValue::VOBJ);
PushWarnings(warnings, result);
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/test/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet)
// Calls SyncWithValidationInterfaceQueue
wallet->chain().waitForNotificationsIfTipChanged({});
wallet->m_chain_notifications_handler.reset();
UnloadWallet(std::move(wallet));
WaitForDeleteWallet(std::move(wallet));
}

std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database)
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/test/wallet_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
context.args = &m_args;
auto wallet = TestLoadWallet(context);
BOOST_CHECK(wallet);
UnloadWallet(std::move(wallet));
WaitForDeleteWallet(std::move(wallet));
}

BOOST_FIXTURE_TEST_CASE(RemoveTxs, TestChain100Setup)
Expand Down
39 changes: 20 additions & 19 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,14 @@ bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet

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

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

// Custom deleter for shared_ptr<CWallet>.
static void ReleaseWallet(CWallet* wallet)
static void FlushAndDeleteWallet(CWallet* wallet)
{
const std::string name = wallet->GetName();
wallet->WalletLogPrintf("Releasing wallet\n");
wallet->WalletLogPrintf("Releasing wallet %s..\n", name);
wallet->Flush();
delete wallet;
// Wallet is now released, notify UnloadWallet, if any.
// Wallet is now released, notify WaitForDeleteWallet, if any.
{
LOCK(g_wallet_release_mutex);
if (g_unloading_wallet_set.erase(name) == 0) {
// UnloadWallet was not called for this wallet, all done.
// WaitForDeleteWallet was not called for this wallet, all done.
return;
}
}
g_wallet_release_cv.notify_all();
}

void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
void WaitForDeleteWallet(std::shared_ptr<CWallet>&& wallet)
{
// Mark wallet for unloading.
const std::string name = wallet->GetName();
{
LOCK(g_wallet_release_mutex);
auto it = g_unloading_wallet_set.insert(name);
assert(it.second);
g_unloading_wallet_set.insert(name);
// Do not expect to be the only one removing this wallet.
// Multiple threads could simultaneously be waiting for deletion.
}
// 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.
wallet->NotifyUnload();

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

Expand Down Expand Up @@ -4387,7 +4388,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
if (!RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt, warnings)) {
return util::Error{_("Unable to unload the wallet before migrating")};
}
UnloadWallet(std::move(wallet));
WaitForDeleteWallet(std::move(wallet));
was_loaded = true;
} else {
// Check if the wallet is BDB
Expand Down Expand Up @@ -4531,7 +4532,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
error += _("\nUnable to cleanup failed migration");
return util::Error{error};
}
UnloadWallet(std::move(w));
WaitForDeleteWallet(std::move(w));
} else {
// Unloading for wallets in local context
assert(w.use_count() == 1);
Expand Down
9 changes: 3 additions & 6 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,9 @@ struct bilingual_str;
namespace wallet {
struct WalletContext;

//! Explicitly unload and delete the wallet.
//! Blocks the current thread after signaling the unload intent so that all
//! wallet pointer owners 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);
//! Explicitly delete the wallet.
//! Blocks the current thread until the wallet is destructed.
void WaitForDeleteWallet(std::shared_ptr<CWallet>&& wallet);

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

0 comments on commit 99ecb9a

Please sign in to comment.