Merge bitcoin/bitcoin#30659: wallet: fix UnloadWallet thread safety assumptions
Some checks are pending
CI / test each commit (push) Waiting to run
CI / macOS 13 native, x86_64, no depends, sqlite only, gui (push) Waiting to run
CI / Win64 native, VS 2022 (push) Waiting to run
CI / ASan + LSan + UBSan + integer, no depends, USDT (push) Waiting to run

f550a8e035 Rename ReleaseWallet to FlushAndDeleteWallet (furszy)
64e736d79e wallet: WaitForDeleteWallet, do not expect thread safety (Ryan Ofsky)
8872b4a6ca wallet: rename UnloadWallet to WaitForDeleteWallet (furszy)
5d15485aaf wallet: unload, notify GUI as soon as possible (furszy)

Pull request description:

  Coming from #29073.

  Applied ryanofsky suggested changes on https://github.com/bitcoin/bitcoin/issues/29073#issuecomment-2274237242 with few modifications coming from https://github.com/bitcoin/bitcoin/pull/18338#issuecomment-605060348.

  The only point I did not tackle from https://github.com/bitcoin/bitcoin/pull/18338#issuecomment-605060348 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 f550a8e035
  ryanofsky:
    Code review ACK f550a8e035. Just a simple rename since last review
  ismaelsadeeq:
    Re-ACK f550a8e035

Tree-SHA512: e2eb69bf36883c514f601f4838ae6a41113996b9559abf8dc2b46e16bbcdad401195ac0f2b9d1fb55a10e78bb8ea9953788a168c80474e3f101350d208cb3bd2
This commit is contained in:
Ava Chow 2024-08-15 13:22:34 -04:00
commit 99ecb9a630
No known key found for this signature in database
GPG key ID: 17565732E08E5E41
7 changed files with 28 additions and 30 deletions

View file

@ -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);
});
}

View file

@ -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

View file

@ -495,7 +495,7 @@ static RPCHelpMan unloadwallet()
}
}
UnloadWallet(std::move(wallet));
WaitForDeleteWallet(std::move(wallet));
UniValue result(UniValue::VOBJ);
PushWarnings(warnings, result);

View file

@ -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)

View file

@ -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)

View file

@ -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);
@ -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);
@ -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", "");
@ -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
@ -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);

View file

@ -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);