Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move get_info off main thread and run in http thread #920

Merged
merged 26 commits into from
Oct 14, 2024
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ab2510a
create get_info_db for caching get_info results
linh2931 Oct 10, 2024
247e9a5
Use get_info_db to return the data for get_info
linh2931 Oct 10, 2024
3c15c13
Update tests for compiling
linh2931 Oct 10, 2024
2ab5dcb
Update files to use get_info_db::get_info_results
linh2931 Oct 10, 2024
28f4f18
Move get_info off main thread
linh2931 Oct 10, 2024
549bc55
Check if chain head and forkdb root are valid before use them
linh2931 Oct 10, 2024
b9226e6
add const specifier to get_info_enabled as it cannot be changed after…
linh2931 Oct 10, 2024
f43bf48
Move rw_mutex above cached_results which is what it protects for
linh2931 Oct 10, 2024
ec75c3f
Set constant values (chain_id, version_hex, version_string, and full_…
linh2931 Oct 10, 2024
5309d0d
Move itoh to FC hex.hpp
linh2931 Oct 10, 2024
6eb1324
Add handling irreversible_block signal and refactor to make it more e…
linh2931 Oct 11, 2024
d6a4d6d
Need to store all information on on_irreversible_block so it is in sync
linh2931 Oct 11, 2024
9cd5f22
Fill in actual block info for on_irreversible_block
linh2931 Oct 11, 2024
0cca888
Merge branch 'main' into get_info_off_main_thread
linh2931 Oct 11, 2024
f7cdc5d
Fix a merge conflict from main
linh2931 Oct 11, 2024
d1655be
Reimplement the main algorithm mutex free
linh2931 Oct 11, 2024
be24dce
Pre-compute fixed data
linh2931 Oct 11, 2024
cc9e89e
Add is_valid() function for code clarity
linh2931 Oct 11, 2024
44c7a9a
Merge branch 'main' into get_info_off_main_thread
linh2931 Oct 12, 2024
b2213aa
Rename is_valid() to contains_full_data() and move it to get_info_res…
linh2931 Oct 12, 2024
2f27f49
remove unused include <shared_mutex>
linh2931 Oct 14, 2024
ee913fa
Grab fork_db_root once to avoid multiple mutexes in fork db
linh2931 Oct 14, 2024
11f92c2
add const to contains_full_data()
linh2931 Oct 14, 2024
a2f18a2
Call fork_db_has_root() only once to avoid an extra mutex in fork db
linh2931 Oct 14, 2024
8d8da68
Merge branch 'main' into get_info_off_main_thread
linh2931 Oct 14, 2024
20e3140
Do not call fork_db_has_root() explicitly to save one mutex lock in f…
linh2931 Oct 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions libraries/chain/include/eosio/chain/chain_id_type.hpp
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ namespace eosio {
struct handshake_message;

namespace chain_apis {
class read_only;
class get_info_db;
}

class chain_plugin;
@@ -49,7 +49,7 @@ namespace chain {
template<typename T>
friend T fc::variant::as()const;

friend class eosio::chain_apis::read_only;
friend class eosio::chain_apis::get_info_db;

friend class eosio::net_plugin_impl;
friend struct eosio::handshake_message;
12 changes: 12 additions & 0 deletions libraries/libfc/include/fc/crypto/hex.hpp
Original file line number Diff line number Diff line change
@@ -12,4 +12,16 @@ namespace fc {
* @return the number of bytes decoded
*/
size_t from_hex( const std::string& hex_str, char* out_data, size_t out_data_len );

/**
* @return the hex string of `n`
*/
template<typename I>
std::string itoh(I n, size_t hlen = sizeof(I)<<1) {
static const char* digits = "0123456789abcdef";
std::string r(hlen, '0');
for(size_t i = 0, j = (hlen - 1) * 4 ; i < hlen; ++i, j -= 4)
r[i] = digits[(n>>j) & 0x0f];
return r;
}
}
8 changes: 5 additions & 3 deletions plugins/chain_api_plugin/chain_api_plugin.cpp
Original file line number Diff line number Diff line change
@@ -125,9 +125,11 @@ void chain_api_plugin::plugin_startup() {

ro_api.set_shorten_abi_errors( !http_plugin::verbose_errors() );

_http_plugin.add_api( {
CALL_WITH_400(chain, node, ro_api, chain_apis::read_only, get_info, 200, http_params_types::no_params)
}, appbase::exec_queue::read_only, appbase::priority::medium_high);
// Run get_info on http thread only
_http_plugin.add_async_api({
CHAIN_RO_CALL_WITH_400(get_info, 200, http_params_types::no_params)
});

_http_plugin.add_api({
CHAIN_RO_CALL(get_activated_protocol_features, 200, http_params_types::possible_no_params),
CHAIN_RO_CALL_POST(get_block, fc::variant, 200, http_params_types::params_required), // _POST because get_block() returns a lambda to be executed on the http thread pool
1 change: 1 addition & 0 deletions plugins/chain_plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ add_library( chain_plugin
chain_plugin.cpp
trx_retry_db.cpp
tracked_votes.cpp
get_info_db.cpp
${HEADERS} )

if(EOSIO_ENABLE_DEVELOPER_OPTIONS)
58 changes: 20 additions & 38 deletions plugins/chain_plugin/chain_plugin.cpp
Original file line number Diff line number Diff line change
@@ -192,6 +192,7 @@ class chain_plugin_impl {
std::optional<scoped_connection> applied_transaction_connection;
std::optional<scoped_connection> block_start_connection;

std::optional<chain_apis::get_info_db> _get_info_db;
std::optional<chain_apis::account_query_db> _account_query_db;
std::optional<chain_apis::trx_retry_db> _trx_retry_db;
chain_apis::trx_finality_status_processing_ptr _trx_finality_status_processing;
@@ -975,12 +976,14 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) {
}

// only enable last tracked votes if chain_api_plugin enabled, if no http endpoint, no reason to track
bool last_tracked_votes_enabled = false;
bool chain_api_plugin_configured = false;
if (options.count("plugin")) {
const auto& v = options.at("plugin").as<std::vector<std::string>>();
last_tracked_votes_enabled = std::ranges::any_of(v, [](const std::string& p) { return p.find("eosio::chain_api_plugin") != std::string::npos; });
chain_api_plugin_configured = std::ranges::any_of(v, [](const std::string& p) { return p.find("eosio::chain_api_plugin") != std::string::npos; });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We agreed that we didn't need that check since chain_api_plugin is most of the time configured.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is most likely configured in BP nodes but not necessarily in other nodes.

}
_last_tracked_votes.emplace(*chain, last_tracked_votes_enabled);
_last_tracked_votes.emplace(*chain, chain_api_plugin_configured);

_get_info_db.emplace(*chain, chain_api_plugin_configured);

// initialize deep mind logging
if ( options.at( "deep-mind" ).as<bool>() ) {
@@ -1051,6 +1054,10 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) {
_last_tracked_votes->on_accepted_block(block, id);
}

if (_get_info_db) {
_get_info_db->on_accepted_block();
}

accepted_block_channel.publish( priority::high, t );
} );

@@ -1065,6 +1072,10 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) {
_trx_finality_status_processing->signal_irreversible_block(block, id);
}

if (_get_info_db) {
_get_info_db->on_irreversible_block(block, id);
}

irreversible_block_channel.publish( priority::low, t );
} );

@@ -1196,10 +1207,9 @@ chain_apis::read_write chain_plugin::get_read_write_api(const fc::microseconds&
}

chain_apis::read_only chain_plugin::get_read_only_api(const fc::microseconds& http_max_response_time) const {
return chain_apis::read_only(chain(), my->_account_query_db, my->_last_tracked_votes, get_abi_serializer_max_time(), http_max_response_time, my->_trx_finality_status_processing.get());
return chain_apis::read_only(chain(), my->_get_info_db, my->_account_query_db, my->_last_tracked_votes, get_abi_serializer_max_time(), http_max_response_time, my->_trx_finality_status_processing.get());
}


void chain_plugin::accept_transaction(const chain::packed_transaction_ptr& trx, next_function<chain::transaction_trace_ptr> next) {
my->incoming_transaction_async_method(trx, false, transaction_metadata::trx_type::input, false, std::move(next));
}
@@ -1281,40 +1291,12 @@ namespace chain_apis {

const string read_only::KEYi64 = "i64";

read_only::get_info_results read_only::get_info(const read_only::get_info_params&, const fc::time_point&) const {
const auto& rm = db.get_resource_limits_manager();

auto head = db.head();
auto head_id = head.id();
auto fork_db_root = db.fork_db_root();
auto fork_db_head = db.fork_db_head();
auto fork_db_root_id = fork_db_root.id();
auto fork_db_head_id = fork_db_head.id();
get_info_db::get_info_results read_only::get_info(const read_only::get_info_params&, const fc::time_point&) const {
EOS_ASSERT(gidb, plugin_config_exception, "get_info being accessed when not enabled");

return {
itoh(static_cast<uint32_t>(app().version())),
db.get_chain_id(),
block_header::num_from_id(head_id),
block_header::num_from_id(fork_db_root_id),
fork_db_root_id,
head_id,
db.head().block_time(),
db.head().producer(),
rm.get_virtual_block_cpu_limit(),
rm.get_virtual_block_net_limit(),
rm.get_block_cpu_limit(),
rm.get_block_net_limit(),
//std::bitset<64>(db.get_dynamic_global_properties().recent_slots_filled).to_string(),
//__builtin_popcountll(db.get_dynamic_global_properties().recent_slots_filled) / 64.0,
app().version_string(),
block_header::num_from_id(fork_db_head_id),
fork_db_head_id,
app().full_version_string(),
rm.get_total_cpu_weight(),
rm.get_total_net_weight(),
db.earliest_available_block_num(),
fork_db_root.block_time()
};
// To be able to run get_info on an http thread, get_info results are stored
// in get_info_db and updated whenever accepted_block signal is received.
return gidb->get_info();
}

read_only::get_transaction_status_results
164 changes: 164 additions & 0 deletions plugins/chain_plugin/get_info_db.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#include <eosio/chain_plugin/get_info_db.hpp>
#include <eosio/chain/resource_limits.hpp>
#include <eosio/chain/application.hpp>

using namespace eosio;
using namespace eosio::chain;
using namespace appbase;

namespace eosio::chain_apis {
/**
* Implementation details of the get_info results
*/
struct get_info_db_impl {
get_info_db_impl(const chain::controller& controller, bool get_info_enabled)
: controller(controller)
, get_info_enabled(get_info_enabled)
, server_version(fc::itoh(static_cast<uint32_t>(app().version())))
, chain_id(controller.get_chain_id())
, server_version_string(app().version_string())
, server_full_version_string(app().full_version_string()) {}

// Called on accepted_block signal.
void on_accepted_block() {
try {
if (!get_info_enabled) {
return;
}

store_info();
} FC_LOG_AND_DROP(("get_info_db_impl on_accepted_block ERROR"));
}

// Called on irreversible_block signal.
void on_irreversible_block(const chain::signed_block_ptr& block, const block_id_type& id) {
try {
if (!get_info_enabled) {
return;
}

store_info(block, id);
} FC_LOG_AND_DROP(("get_info_db_impl on_irreversible_block ERROR"));
}

// Returns cached get_info results
get_info_db::get_info_results get_info() {
// safely load the info_cache pointer
std::shared_ptr<get_info_db::get_info_results> info = std::atomic_load(&info_cache);

if (info && info->contains_full_data()) {
return *info;
}

// This only happens right after initialization when starts from
// snapshot as no signals are emitted. We need to cache the current states.
store_info();

info = std::atomic_load(&info_cache);
assert(info);
return *info;
}

private:
// A handle to the controller.
const chain::controller& controller;

// Indication whether get_info RPC is enabled.
const bool get_info_enabled = false;

// Cache to store the current get_info results.
// Using std::atomic_load and std::atomic_store to switch pointers.
std::shared_ptr<get_info_db::get_info_results> info_cache = nullptr;

// Fixed data
std::string server_version;
chain::chain_id_type chain_id;
std::string server_version_string;
std::string server_full_version_string;

// Stores common data, and returns fork_db_has_root for future uses to avoid
// multiple mutexes in fork db.
bool store_info_common(const std::shared_ptr<get_info_db::get_info_results>& info) {
assert(info);

// fixed part
info->server_version = server_version;
info->chain_id = chain_id;
info->server_version_string = server_version_string;
info->server_full_version_string = server_full_version_string;

// chain head part
const auto& head = controller.head();
if (head.is_valid()) {
info->head_block_id = head.id();
info->head_block_num = block_header::num_from_id(info->head_block_id);
info->head_block_time = head.block_time();
info->head_block_producer = head.producer();
}

// fork_db part
const auto& fork_db_head = controller.fork_db_head();
bool fork_db_has_root = fork_db_head.is_valid(); // a valid head implies fork_db has root
if (fork_db_has_root) {
info->fork_db_head_block_id = fork_db_head.id();
info->fork_db_head_block_num = block_header::num_from_id(*info->fork_db_head_block_id);
info->earliest_available_block_num = controller.earliest_available_block_num();
}

// resource_limits
const auto& rm = controller.get_resource_limits_manager();
info->virtual_block_cpu_limit = rm.get_virtual_block_cpu_limit();
info->virtual_block_net_limit = rm.get_virtual_block_net_limit();
info->block_cpu_limit = rm.get_block_cpu_limit();
info->block_net_limit = rm.get_block_net_limit();
info->total_cpu_weight = rm.get_total_cpu_weight();
info->total_net_weight = rm.get_total_net_weight();

return fork_db_has_root;
}

void store_info() {
std::shared_ptr<get_info_db::get_info_results> info = std::make_shared<get_info_db::get_info_results>();

bool fork_db_has_root = store_info_common(info); // store_info_common returns fork_db_has_root to avoid mutex in fork db in call to controller.fork_db_has_root()

if (fork_db_has_root) {
const auto& root = controller.fork_db_root(); // avoid multiple mutexes in fork db
info->last_irreversible_block_id = root.id();
info->last_irreversible_block_num = block_header::num_from_id(info->last_irreversible_block_id);
info->last_irreversible_block_time = root.block_time();
}

std::atomic_store(&info_cache, info); // replace current cache safely
}

void store_info(const chain::signed_block_ptr& block, const block_id_type& id) {
std::shared_ptr<get_info_db::get_info_results> info = std::make_shared<get_info_db::get_info_results>();

store_info_common(info);

info->last_irreversible_block_id = id;
info->last_irreversible_block_num = block_header::num_from_id(info->last_irreversible_block_id);
info->last_irreversible_block_time = block->timestamp;

std::atomic_store(&info_cache, info); // replace current cache safely
}
}; // get_info_db_impl

get_info_db::get_info_db( const chain::controller& controller, bool get_info_enabled )
:_impl(std::make_unique<get_info_db_impl>(controller, get_info_enabled)) {}

get_info_db::~get_info_db() = default;

void get_info_db::on_accepted_block() {
_impl->on_accepted_block();
}

void get_info_db::on_irreversible_block(const chain::signed_block_ptr& block, const block_id_type& id) {
_impl->on_irreversible_block(block, id);
}

get_info_db::get_info_results get_info_db::get_info() const {
return _impl->get_info();
}
} // namespace eosio::chain_apis
Loading