Perform wallet representative action without holding any mutex (#2759)

* Perform wallet representative action without holding any mutex

 This relaxes restrictions on the action and avoids potential deadlocks through lock-order-inversion

* Relax debug_assert when adding to votes_cache as the representative counts can change even during a foreach_representative action

* Windows requires explicitly unlocking the mutex once locked
This commit is contained in:
Guilherme Lawless 2020-05-06 14:31:36 +01:00 committed by GitHub
commit f30b3e87dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 28 deletions

View file

@ -1556,3 +1556,22 @@ TEST (wallet, epoch_2_receive_unopened)
} }
ASSERT_LT (tries, max_tries); ASSERT_LT (tries, max_tries);
} }
TEST (wallet, foreach_representative_deadlock)
{
nano::system system (1);
auto & node (*system.nodes[0]);
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);
node.wallets.compute_reps ();
ASSERT_EQ (1, node.wallets.rep_counts ().voting);
node.wallets.foreach_representative ([&node](nano::public_key const & pub, nano::raw_key const & prv) {
if (node.wallets.mutex.try_lock ())
{
node.wallets.mutex.unlock ();
}
else
{
ASSERT_FALSE (true);
}
});
}

View file

@ -137,9 +137,12 @@ wallets (wallets_a)
void nano::votes_cache::add (std::shared_ptr<nano::vote> const & vote_a) void nano::votes_cache::add (std::shared_ptr<nano::vote> const & vote_a)
{ {
nano::lock_guard<std::mutex> lock (cache_mutex);
auto voting (wallets.rep_counts ().voting); auto voting (wallets.rep_counts ().voting);
debug_assert (voting > 0); if (voting == 0)
{
return;
}
nano::lock_guard<std::mutex> lock (cache_mutex);
auto const max_cache_size (network_params.voting.max_cache / std::max (voting, static_cast<decltype (voting)> (1))); auto const max_cache_size (network_params.voting.max_cache / std::max (voting, static_cast<decltype (voting)> (1)));
for (auto & block : vote_a->blocks) for (auto & block : vote_a->blocks)
{ {

View file

@ -1750,8 +1750,10 @@ void nano::wallets::foreach_representative (std::function<void(nano::public_key
{ {
if (node.config.enable_voting) if (node.config.enable_voting)
{ {
nano::lock_guard<std::mutex> lock (mutex); std::vector<std::pair<nano::public_key const, nano::raw_key const>> action_accounts_l;
{
auto transaction_l (tx_begin_read ()); auto transaction_l (tx_begin_read ());
nano::lock_guard<std::mutex> lock (mutex);
for (auto i (items.begin ()), n (items.end ()); i != n; ++i) for (auto i (items.begin ()), n (items.end ()); i != n; ++i)
{ {
auto & wallet (*i->second); auto & wallet (*i->second);
@ -1773,7 +1775,7 @@ void nano::wallets::foreach_representative (std::function<void(nano::public_key
auto error (wallet.store.fetch (transaction_l, account, prv)); auto error (wallet.store.fetch (transaction_l, account, prv));
(void)error; (void)error;
debug_assert (!error); debug_assert (!error);
action_a (account, prv); action_accounts_l.emplace_back (account, prv);
} }
else else
{ {
@ -1789,6 +1791,11 @@ void nano::wallets::foreach_representative (std::function<void(nano::public_key
} }
} }
} }
for (auto const & representative : action_accounts_l)
{
action_a (representative.first, representative.second);
}
}
} }
bool nano::wallets::exists (nano::transaction const & transaction_a, nano::account const & account_a) bool nano::wallets::exists (nano::transaction const & transaction_a, nano::account const & account_a)