diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 4cb1034eef..c1e496486a 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace eosio::chain { using boost::multi_index_container; @@ -56,6 +57,7 @@ namespace eosio::chain { BOOST_MULTI_INDEX_CONST_MEM_FUN(bs, const block_id_type&, id)>, composite_key_compare, std::greater, std::greater, sha256_less>>>>; + std::shared_mutex mtx; fork_multi_index_type index; bsp root; // Only uses the block_header_state_legacy portion bsp head; @@ -88,6 +90,7 @@ namespace eosio::chain { template void fork_database_t::open( const std::filesystem::path& fork_db_file, validator_t& validator ) { + std::lock_guard g( my->mtx ); my->open_impl( fork_db_file, validator ); } @@ -163,6 +166,7 @@ namespace eosio::chain { template void fork_database_t::close(const std::filesystem::path& fork_db_file) { + std::lock_guard g( my->mtx ); my->close_impl(fork_db_file); } @@ -234,6 +238,7 @@ namespace eosio::chain { template void fork_database_t::reset( const bhs& root_bhs ) { + std::lock_guard g( my->mtx ); my->reset_impl(root_bhs); } @@ -248,6 +253,7 @@ namespace eosio::chain { template void fork_database_t::rollback_head_to_root() { + std::lock_guard g( my->mtx ); my->rollback_head_to_root_impl(); } @@ -266,6 +272,7 @@ namespace eosio::chain { template void fork_database_t::advance_root( const block_id_type& id ) { + std::lock_guard g( my->mtx ); my->advance_root_impl( id ); } @@ -306,6 +313,7 @@ namespace eosio::chain { template fork_database_t::bhsp fork_database_t::get_block_header( const block_id_type& id ) const { + std::shared_lock g( my->mtx ); return my->get_block_header_impl( id ); } @@ -362,6 +370,7 @@ namespace eosio::chain { template void fork_database_t::add( const bsp& n, bool ignore_duplicate ) { + std::lock_guard g( my->mtx ); my->add_impl( n, ignore_duplicate, false, []( block_timestamp_type timestamp, const flat_set& cur_features, @@ -372,16 +381,19 @@ namespace eosio::chain { template bsp fork_database_t::root() const { + std::shared_lock g( my->mtx ); return my->root; } template bsp fork_database_t::head() const { + std::shared_lock g( my->mtx ); return my->head; } template bsp fork_database_t::pending_head() const { + std::shared_lock g( my->mtx ); const auto& indx = my->index.template get(); auto itr = indx.lower_bound( false ); @@ -397,6 +409,7 @@ namespace eosio::chain { fork_database_t::branch_type fork_database_t::fetch_branch(const block_id_type& h, uint32_t trim_after_block_num) const { + std::shared_lock g(my->mtx); return my->fetch_branch_impl(h, trim_after_block_num); } @@ -414,6 +427,7 @@ namespace eosio::chain { template bsp fork_database_t::search_on_branch( const block_id_type& h, uint32_t block_num ) const { + std::shared_lock g( my->mtx ); return my->search_on_branch_impl( h, block_num ); } @@ -434,6 +448,7 @@ namespace eosio::chain { template fork_database_t::branch_type_pair fork_database_t::fetch_branch_from(const block_id_type& first, const block_id_type& second) const { + std::shared_lock g(my->mtx); return my->fetch_branch_from_impl(first, second); } @@ -500,6 +515,7 @@ namespace eosio::chain { /// remove all of the invalid forks built off of this id including this id template void fork_database_t::remove( const block_id_type& id ) { + std::lock_guard g( my->mtx ); return my->remove_impl( id ); } @@ -527,6 +543,7 @@ namespace eosio::chain { template void fork_database_t::mark_valid( const bsp& h ) { + std::lock_guard g( my->mtx ); my->mark_valid_impl( h ); } @@ -553,6 +570,7 @@ namespace eosio::chain { template bsp fork_database_t::get_block(const block_id_type& id) const { + std::shared_lock g( my->mtx ); return my->get_block_impl(id); } @@ -567,7 +585,10 @@ namespace eosio::chain { // ------------------ fork_database ------------------------- fork_database::fork_database(const std::filesystem::path& data_dir) - : data_dir(data_dir) { + : data_dir(data_dir) + // currently needed because chain_head is accessed before fork database open + , fork_db_legacy{std::make_unique(fork_database_legacy_t::legacy_magic_number)} + { } fork_database::~fork_database() { @@ -579,7 +600,6 @@ namespace eosio::chain { } void fork_database::open( validator_t& validator ) { - std::lock_guard g(m); if (!std::filesystem::is_directory(data_dir)) std::filesystem::create_directories(data_dir); @@ -603,12 +623,14 @@ namespace eosio::chain { ); if (totem == fork_database_legacy_t::legacy_magic_number) { + // fork_db_legacy created in constructor apply_legacy([&](auto& forkdb) { forkdb.open(fork_db_file, validator); }); } else { // file is instant-finality data, so switch to fork_database_if_t - vforkdb.emplace(fork_database_if_t::magic_number); + fork_db_if = std::make_unique(fork_database_if_t::magic_number); + legacy = false; apply_if([&](auto& forkdb) { forkdb.open(fork_db_file, validator); }); @@ -618,11 +640,13 @@ namespace eosio::chain { } void fork_database::switch_from_legacy() { - std::lock_guard g(m); // no need to close fork_db because we don't want to write anything out, file is removed on open - block_state_legacy_ptr head = std::get(vforkdb).chain_head; // will throw if called after transistion + // threads may be accessing (or locked on mutex about to access legacy forkdb) so don't delete it until program exit + assert(legacy); + block_state_legacy_ptr head = fork_db_legacy->chain_head; // will throw if called after transistion auto new_head = std::make_shared(*head); - vforkdb.emplace(fork_database_if_t::magic_number); + fork_db_if = std::make_unique(fork_database_if_t::magic_number); + legacy = false; apply_if([&](auto& forkdb) { forkdb.chain_head = new_head; forkdb.reset(*new_head); diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index a2f4f7f868..c9ad269914 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -19,7 +19,8 @@ namespace eosio::chain { * blocks older than the last irreversible block are freed after emitting the * irreversible signal. * - * Not thread safe, thread safety provided by fork_database below. + * An internal mutex is used to provide thread-safety. + * * fork_database should be used instead of fork_database_t directly as it manages * the different supported types. */ @@ -108,22 +109,25 @@ namespace eosio::chain { using fork_database_if_t = fork_database_t; /** - * Provides thread safety on fork_database_t and provide mechanism for opening the correct type + * Provides mechanism for opening the correct type * as well as switching from legacy (old dpos) to instant-finality. * * All methods assert until open() is closed. */ class fork_database { - mutable std::recursive_mutex m; const std::filesystem::path data_dir; - std::variant, fork_database_t> vforkdb; + std::atomic legacy = true; + std::unique_ptr fork_db_legacy; + std::unique_ptr fork_db_if; public: explicit fork_database(const std::filesystem::path& data_dir); ~fork_database(); // close on destruction + // not thread safe, expected to be called from main thread before allowing concurrent access void open( validator_t& validator ); void close(); + // expected to be called from main thread, accesses chain_head void switch_from_legacy(); // see fork_database_t::fetch_branch(forkdb->head()->id()) @@ -131,63 +135,85 @@ namespace eosio::chain { template R apply(const F& f) { - std::lock_guard g(m); - if constexpr (std::is_same_v) - std::visit([&](auto& forkdb) { f(forkdb); }, vforkdb); - else - return std::visit([&](auto& forkdb) -> R { return f(forkdb); }, vforkdb); + if constexpr (std::is_same_v) { + if (legacy) { + f(*fork_db_legacy); + } else { + f(*fork_db_if); + } + } else { + if (legacy) { + return f(*fork_db_legacy); + } else { + return f(*fork_db_if); + } + } } template R apply(const F& f) const { - std::lock_guard g(m); - if constexpr (std::is_same_v) - std::visit([&](const auto& forkdb) { f(forkdb); }, vforkdb); - else - return std::visit([&](const auto& forkdb) -> R { return f(forkdb); }, vforkdb); + if constexpr (std::is_same_v) { + if (legacy) { + f(*fork_db_legacy); + } else { + f(*fork_db_if); + } + } else { + if (legacy) { + return f(*fork_db_legacy); + } else { + return f(*fork_db_if); + } + } } /// Apply for when only need lambda executed when in instant-finality mode template R apply_if(const F& f) { - std::lock_guard g(m); - if constexpr (std::is_same_v) - std::visit(overloaded{[&](fork_database_legacy_t&) {}, - [&](fork_database_if_t& forkdb) { f(forkdb); }}, - vforkdb); - else - return std::visit(overloaded{[&](fork_database_legacy_t&) -> R { return {}; }, - [&](fork_database_if_t& forkdb) -> R { return f(forkdb); }}, - vforkdb); + if constexpr (std::is_same_v) { + if (!legacy) { + f(*fork_db_if); + } + } else { + if (!legacy) { + return f(*fork_db_if); + } + return {}; + } } /// Apply for when only need lambda executed when in legacy mode template R apply_legacy(const F& f) { - std::lock_guard g(m); - if constexpr (std::is_same_v) - std::visit(overloaded{[&](fork_database_legacy_t& forkdb) { f(forkdb); }, - [&](fork_database_if_t&) {}}, - vforkdb); - else - return std::visit(overloaded{[&](fork_database_legacy_t& forkdb) -> R { return f(forkdb); }, - [&](fork_database_if_t&) -> R { return {}; }}, - vforkdb); + if constexpr (std::is_same_v) { + if (legacy) { + f(*fork_db_legacy); + } + } else { + if (legacy) { + return f(*fork_db_legacy); + } + return {}; + } } /// @param legacy_f the lambda to execute if in legacy mode /// @param if_f the lambda to execute if in instant-finality mode template R apply(const LegacyF& legacy_f, const IfF& if_f) { - std::lock_guard g(m); - if constexpr (std::is_same_v) - std::visit(overloaded{[&](fork_database_legacy_t& forkdb) { legacy_f(forkdb); }, - [&](fork_database_if_t& forkdb) { if_f(forkdb); }}, - vforkdb); - else - return std::visit(overloaded{[&](fork_database_legacy_t& forkdb) -> R { return legacy_f(forkdb); }, - [&](fork_database_if_t& forkdb) -> R { return if_f(forkdb); }}, - vforkdb); + if constexpr (std::is_same_v) { + if (legacy) { + legacy_f(*fork_db_legacy); + } else { + if_f(*fork_db_if); + } + } else { + if (legacy) { + return legacy_f(*fork_db_legacy); + } else { + return if_f(*fork_db_if); + } + } } // if we ever support more than one version then need to save min/max in fork_database_t