Generalise active_transaction functions for each election_behavior (#4152)

* Generalize functions by each behavior type:
- active_transactions::insert
- active_transactions::limit
- active_transactions::vacancy

* Adding counts for each behavior type within active_transactions.
This commit is contained in:
clemahieu 2023-02-23 10:57:36 +00:00 committed by GitHub
commit 11b1d4ca62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 80 deletions

View file

@ -1518,9 +1518,9 @@ TEST (active_transactions, allow_limited_overflow)
}
// Ensure active elections overfill AEC only up to normal + hinted limit
ASSERT_TIMELY_EQ (5s, node.active.size (), node.active.limit () + node.active.hinted_limit ());
ASSERT_TIMELY_EQ (5s, node.active.size (), node.active.limit () + node.active.limit (nano::election_behavior::hinted));
// And it stays that way without increasing
ASSERT_ALWAYS (1s, node.active.size () == node.active.limit () + node.active.hinted_limit ());
ASSERT_ALWAYS (1s, node.active.size () == node.active.limit () + node.active.limit (nano::election_behavior::hinted));
}
/*
@ -1556,9 +1556,9 @@ TEST (active_transactions, allow_limited_overflow_adapt)
}
// Ensure hinted election amount is bounded by hinted limit
ASSERT_TIMELY_EQ (5s, node.active.size (), node.active.hinted_limit ());
ASSERT_TIMELY_EQ (5s, node.active.size (), node.active.limit (nano::election_behavior::hinted));
// And it stays that way without increasing
ASSERT_ALWAYS (1s, node.active.size () == node.active.hinted_limit ());
ASSERT_ALWAYS (1s, node.active.size () == node.active.limit (nano::election_behavior::hinted));
// Insert the first part of the blocks into normal election scheduler
for (auto const & block : blocks1)

View file

@ -55,7 +55,6 @@ enum class detail : uint8_t
total,
process,
update,
insert,
request,
broadcast,
@ -240,7 +239,6 @@ enum class detail : uint8_t
generator_spacing,
// hinting
insert_failed,
missing_block,
// bootstrap server
@ -257,6 +255,10 @@ enum class detail : uint8_t
// backlog
activated,
// active
insert,
insert_failed,
// unchecked
put,
satisfied,

View file

@ -11,6 +11,8 @@
#include <mutex>
#include <vector>
#include <magic_enum_containers.hpp>
namespace boost
{
namespace filesystem
@ -57,6 +59,12 @@ void assert_internal (char const * check_expr, char const * func, char const * f
namespace nano
{
/**
* Array indexable by enum values
*/
template <typename Index, typename Value>
using enum_array = magic_enum::containers::array<Index, Value>;
/* These containers are used to collect information about sequence containers.
* It makes use of the composite design pattern to collect information
* from sequence containers and sequence containers inside member variables.

View file

@ -19,6 +19,8 @@ nano::active_transactions::active_transactions (nano::node & node_a, nano::confi
recently_cemented{ node.config.confirmation_history_size },
election_time_to_live{ node_a.network_params.network.is_dev_network () ? 0s : 2s }
{
count_by_behavior.fill (0); // Zero initialize array
// Register a callback which will get called after a block is cemented
confirmation_height_processor.add_cemented_observer ([this] (std::shared_ptr<nano::block> const & callback_block_a) {
this->block_cemented_callback (callback_block_a);
@ -181,29 +183,38 @@ void nano::active_transactions::block_already_cemented_callback (nano::block_has
remove_election_winner_details (hash_a);
}
int64_t nano::active_transactions::limit () const
int64_t nano::active_transactions::limit (nano::election_behavior behavior) const
{
return static_cast<int64_t> (node.config.active_elections_size);
switch (behavior)
{
case nano::election_behavior::normal:
{
return static_cast<int64_t> (node.config.active_elections_size);
}
case nano::election_behavior::hinted:
{
const uint64_t limit = node.config.active_elections_hinted_limit_percentage * node.config.active_elections_size / 100;
return static_cast<int64_t> (limit);
}
}
debug_assert (false, "unknown election behavior");
return 0;
}
int64_t nano::active_transactions::hinted_limit () const
int64_t nano::active_transactions::vacancy (nano::election_behavior behavior) const
{
const uint64_t limit = node.config.active_elections_hinted_limit_percentage * node.config.active_elections_size / 100;
return static_cast<int64_t> (limit);
}
int64_t nano::active_transactions::vacancy () const
{
nano::lock_guard<nano::mutex> lock{ mutex };
auto result = limit () - static_cast<int64_t> (roots.size ());
return result;
}
int64_t nano::active_transactions::vacancy_hinted () const
{
nano::lock_guard<nano::mutex> lock{ mutex };
auto result = hinted_limit () - active_hinted_elections_count;
return result;
nano::lock_guard<nano::mutex> guard{ mutex };
switch (behavior)
{
case nano::election_behavior::normal:
return limit () - static_cast<int64_t> (roots.size ());
case nano::election_behavior::hinted:
return limit (nano::election_behavior::hinted) - count_by_behavior[nano::election_behavior::hinted];
;
}
debug_assert (false); // Unknown enum
return 0;
}
void nano::active_transactions::request_confirm (nano::unique_lock<nano::mutex> & lock_a)
@ -253,10 +264,9 @@ void nano::active_transactions::cleanup_election (nano::unique_lock<nano::mutex>
debug_assert (lock_a.owns_lock ());
node.stats.inc (completion_type (*election), nano::to_stat_detail (election->behavior ()));
if (election->behavior () == election_behavior::hinted)
{
--active_hinted_elections_count;
}
// Keep track of election count by election type
debug_assert (count_by_behavior[election->behavior ()] > 0);
count_by_behavior[election->behavior ()]--;
auto blocks_l = election->blocks ();
for (auto const & [hash, block] : blocks_l)
@ -346,6 +356,16 @@ void nano::active_transactions::request_loop ()
}
}
nano::election_insertion_result nano::active_transactions::insert (const std::shared_ptr<nano::block> & block, nano::election_behavior behavior)
{
debug_assert (block != nullptr);
nano::unique_lock<nano::mutex> lock{ mutex };
auto result = insert_impl (lock, block, behavior);
return result;
}
nano::election_insertion_result nano::active_transactions::insert_impl (nano::unique_lock<nano::mutex> & lock_a, std::shared_ptr<nano::block> const & block_a, nano::election_behavior election_behavior_a, std::function<void (std::shared_ptr<nano::block> const &)> const & confirmation_action_a)
{
debug_assert (lock_a.owns_lock ());
@ -369,11 +389,10 @@ nano::election_insertion_result nano::active_transactions::insert_impl (nano::un
election_behavior_a);
roots.get<tag_root> ().emplace (nano::active_transactions::conflict_info{ root, result.election });
blocks.emplace (hash, result.election);
// Increase hinted election counter while still holding lock
if (election_behavior_a == election_behavior::hinted)
{
active_hinted_elections_count++;
}
// Keep track of election count by election type
debug_assert (count_by_behavior[result.election->behavior ()] >= 0);
count_by_behavior[result.election->behavior ()]++;
lock_a.unlock ();
if (auto const cache = node.inactive_vote_cache.find (hash); cache)
{
@ -403,17 +422,6 @@ nano::election_insertion_result nano::active_transactions::insert_impl (nano::un
return result;
}
nano::election_insertion_result nano::active_transactions::insert_hinted (std::shared_ptr<nano::block> const & block_a)
{
debug_assert (block_a != nullptr);
debug_assert (vacancy_hinted () > 0); // Should only be called when there are free hinted election slots
nano::unique_lock<nano::mutex> lock{ mutex };
auto result = insert_impl (lock, block_a, nano::election_behavior::hinted);
return result;
}
// Validate a vote and apply it to the current election if one exists
nano::vote_code nano::active_transactions::vote (std::shared_ptr<nano::vote> const & vote_a)
{
@ -660,22 +668,14 @@ void nano::active_transactions::clear ()
std::unique_ptr<nano::container_info_component> nano::collect_container_info (active_transactions & active_transactions, std::string const & name)
{
std::size_t roots_count;
std::size_t blocks_count;
std::size_t hinted_count;
{
nano::lock_guard<nano::mutex> guard{ active_transactions.mutex };
roots_count = active_transactions.roots.size ();
blocks_count = active_transactions.blocks.size ();
hinted_count = active_transactions.active_hinted_elections_count;
}
nano::lock_guard<nano::mutex> guard{ active_transactions.mutex };
auto composite = std::make_unique<container_info_composite> (name);
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "roots", roots_count, sizeof (decltype (active_transactions.roots)::value_type) }));
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "blocks", blocks_count, sizeof (decltype (active_transactions.blocks)::value_type) }));
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "roots", active_transactions.roots.size (), sizeof (decltype (active_transactions.roots)::value_type) }));
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "blocks", active_transactions.blocks.size (), sizeof (decltype (active_transactions.blocks)::value_type) }));
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "election_winner_details", active_transactions.election_winner_details_size (), sizeof (decltype (active_transactions.election_winner_details)::value_type) }));
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "hinted", hinted_count, 0 }));
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "normal", static_cast<std::size_t> (active_transactions.count_by_behavior[nano::election_behavior::normal]), 0 }));
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "hinted", static_cast<std::size_t> (active_transactions.count_by_behavior[nano::election_behavior::hinted]), 0 }));
composite->add_component (active_transactions.recently_confirmed.collect_container_info ("recently_confirmed"));
composite->add_component (active_transactions.recently_cemented.collect_container_info ("recently_cemented"));

View file

@ -146,10 +146,9 @@ public:
void stop ();
/**
* Starts new election with hinted behavior type
* Hinted elections have shorter timespan and only can take up limited space inside active elections container
* Starts new election with a specified behavior type
*/
nano::election_insertion_result insert_hinted (std::shared_ptr<nano::block> const & block_a);
nano::election_insertion_result insert (std::shared_ptr<nano::block> const & block, nano::election_behavior behavior = nano::election_behavior::normal);
// Distinguishes replay votes, cannot be determined if the block is not in any election
nano::vote_code vote (std::shared_ptr<nano::vote> const &);
// Is the root of this block in the roots container
@ -174,20 +173,14 @@ public:
void block_already_cemented_callback (nano::block_hash const &);
/**
* Maximum number of all elections that should be present in this container.
* This is only a soft limit, it is possible for this container to exceed this count.
* Maximum number of elections that should be present in this container
* NOTE: This is only a soft limit, it is possible for this container to exceed this count
*/
int64_t limit () const;
int64_t limit (nano::election_behavior behavior = nano::election_behavior::normal) const;
/**
* Maximum number of hinted elections that should be present in this container.
* How many election slots are available for specified election type
*/
int64_t hinted_limit () const;
int64_t vacancy () const;
/**
* How many election slots are available for hinted elections.
* The limit of AEC taken up by hinted elections is controlled by `node_config::active_elections_hinted_limit_percentage`
*/
int64_t vacancy_hinted () const;
int64_t vacancy (nano::election_behavior behavior = nano::election_behavior::normal) const;
std::function<void ()> vacancy_update{ [] () {} };
std::size_t election_winner_details_size ();
@ -231,14 +224,14 @@ private:
// Maximum time an election can be kept active if it is extending the container
std::chrono::seconds const election_time_to_live;
int active_hinted_elections_count{ 0 };
/** Keeps track of number of elections by election behavior (normal, hinted, optimistic) */
nano::enum_array<nano::election_behavior, int64_t> count_by_behavior;
nano::condition_variable condition;
bool stopped{ false };
std::thread thread;
friend class election;
friend class election_scheduler;
friend std::unique_ptr<container_info_component> collect_container_info (active_transactions &, std::string const &);
public: // Tests

View file

@ -143,8 +143,7 @@ void nano::election_scheduler::run ()
auto const [block, previous_balance, election_behavior] = manual_queue.front ();
manual_queue.pop_front ();
lock.unlock ();
nano::unique_lock<nano::mutex> lock2 (node.active.mutex);
node.active.insert_impl (lock2, block, election_behavior);
node.active.insert (block, election_behavior);
}
else if (priority_queue_predicate ())
{
@ -152,8 +151,7 @@ void nano::election_scheduler::run ()
priority.pop ();
lock.unlock ();
std::shared_ptr<nano::election> election;
nano::unique_lock<nano::mutex> lock2 (node.active.mutex);
election = node.active.insert_impl (lock2, block).election;
election = node.active.insert (block).election;
if (election != nullptr)
{
election->transition_active ();

View file

@ -46,7 +46,7 @@ void nano::hinted_scheduler::notify ()
bool nano::hinted_scheduler::predicate (nano::uint128_t const & minimum_tally) const
{
// Check if there is space inside AEC for a new hinted election
if (active.vacancy_hinted () > 0)
if (active.vacancy (nano::election_behavior::hinted) > 0)
{
// Check if there is any vote cache entry surpassing our minimum vote tally threshold
if (inactive_vote_cache.peek (minimum_tally))
@ -72,7 +72,7 @@ bool nano::hinted_scheduler::run_one (nano::uint128_t const & minimum_tally)
{
// Try to insert it into AEC as hinted election
// We check for AEC vacancy inside our predicate
auto result = node.active.insert_hinted (block);
auto result = node.active.insert (block, nano::election_behavior::hinted);
stats.inc (nano::stat::type::hinting, result.inserted ? nano::stat::detail::hinted : nano::stat::detail::insert_failed);