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);
}
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)
{
nano::lock_guard<std::mutex> lock (cache_mutex);
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)));
for (auto & block : vote_a->blocks)
{

View file

@ -1750,44 +1750,51 @@ void nano::wallets::foreach_representative (std::function<void(nano::public_key
{
if (node.config.enable_voting)
{
nano::lock_guard<std::mutex> lock (mutex);
auto transaction_l (tx_begin_read ());
for (auto i (items.begin ()), n (items.end ()); i != n; ++i)
std::vector<std::pair<nano::public_key const, nano::raw_key const>> action_accounts_l;
{
auto & wallet (*i->second);
nano::lock_guard<std::recursive_mutex> store_lock (wallet.store.mutex);
decltype (wallet.representatives) representatives_l;
auto transaction_l (tx_begin_read ());
nano::lock_guard<std::mutex> lock (mutex);
for (auto i (items.begin ()), n (items.end ()); i != n; ++i)
{
nano::lock_guard<std::mutex> representatives_lock (wallet.representatives_mutex);
representatives_l = wallet.representatives;
}
for (auto const & account : representatives_l)
{
if (wallet.store.exists (transaction_l, account))
auto & wallet (*i->second);
nano::lock_guard<std::recursive_mutex> store_lock (wallet.store.mutex);
decltype (wallet.representatives) representatives_l;
{
if (!node.ledger.weight (account).is_zero ())
nano::lock_guard<std::mutex> representatives_lock (wallet.representatives_mutex);
representatives_l = wallet.representatives;
}
for (auto const & account : representatives_l)
{
if (wallet.store.exists (transaction_l, account))
{
if (wallet.store.valid_password (transaction_l))
if (!node.ledger.weight (account).is_zero ())
{
nano::raw_key prv;
auto error (wallet.store.fetch (transaction_l, account, prv));
(void)error;
debug_assert (!error);
action_a (account, prv);
}
else
{
static auto last_log = std::chrono::steady_clock::time_point ();
if (last_log < std::chrono::steady_clock::now () - std::chrono::seconds (60))
if (wallet.store.valid_password (transaction_l))
{
last_log = std::chrono::steady_clock::now ();
node.logger.always_log (boost::str (boost::format ("Representative locked inside wallet %1%") % i->first.to_string ()));
nano::raw_key prv;
auto error (wallet.store.fetch (transaction_l, account, prv));
(void)error;
debug_assert (!error);
action_accounts_l.emplace_back (account, prv);
}
else
{
static auto last_log = std::chrono::steady_clock::time_point ();
if (last_log < std::chrono::steady_clock::now () - std::chrono::seconds (60))
{
last_log = std::chrono::steady_clock::now ();
node.logger.always_log (boost::str (boost::format ("Representative locked inside wallet %1%") % i->first.to_string ()));
}
}
}
}
}
}
}
for (auto const & representative : action_accounts_l)
{
action_a (representative.first, representative.second);
}
}
}