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

Change default max-transaction-time #1655

Merged
merged 20 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ae1a89c
GH-1639 Change default max-transaction-time from 30 to 499
heifner Sep 15, 2023
494fbae
GH-1639 Execute read-only trxs only on read-only threads
heifner Sep 18, 2023
fd232b2
GH-1639 Add <optional> include
heifner Sep 18, 2023
2749256
GH-1639 Add <optional> include
heifner Sep 18, 2023
f038d65
GH-1639 Enable contracts-console on all nodes
heifner Sep 18, 2023
3e40394
GH-1639 Revert changes to test
heifner Sep 18, 2023
1a6631a
GH-1639 read-only trxs only allowed when read-only-threads > 0
heifner Sep 18, 2023
7a7aa58
GH-1639 Remove unneeded dependency on producer_plugin
heifner Sep 18, 2023
1368ecd
GH-1639 Fix producer_plugin shutdown of read only threads to prevent …
heifner Sep 18, 2023
11df9d1
GH-1639 Decrease read-only-threads from 128 to 16 since ci/cd was tim…
heifner Sep 19, 2023
36be3f7
GH-1639 Favor this over rhs for queue comparison. Also use C++20 spac…
heifner Sep 19, 2023
d2cd84c
GH-1639 Use canonical spaceship operator
heifner Sep 19, 2023
6394011
GH-1639 Revert using of spaceship operator as it appears to not work …
heifner Sep 19, 2023
e6b8493
GH-1639 Use same ec for both cancel calls
heifner Sep 19, 2023
8d221a7
GH-1639 Modify exec_pri_queue to manage the 3 priority queues instead…
heifner Sep 20, 2023
ae2424d
GH-1639 More simplification
heifner Sep 20, 2023
e6bbaaf
GH-1639 fix gcc warning
heifner Sep 20, 2023
a6465e6
GH-1639 Additional test
heifner Sep 21, 2023
b077dc3
GH-1639 Update max-transaction-time help description
heifner Sep 22, 2023
b012cce
GH-1639 Add init_read_threads
heifner Sep 22, 2023
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
8 changes: 4 additions & 4 deletions docs/01_nodeos/03_plugins/producer_plugin/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ Config Options for eosio::producer_plugin:
chain is stale.
-x [ --pause-on-startup ] Start this node in a state where
production is paused
--max-transaction-time arg (=30) Limits the maximum time (in
milliseconds) that is allowed a pushed
transaction's code to execute before
being considered invalid
--max-transaction-time arg (=499) Locally lowers the max_transaction_cpu_
usage limit (in milliseconds) that an
input transaction is allowed to execute
before being considered invalid
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
--max-irreversible-block-age arg (=-1)
Limits the maximum age (in seconds) of
the DPOS Irreversible Block for a chain
Expand Down
45 changes: 36 additions & 9 deletions libraries/custom_appbase/include/eosio/chain/application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <appbase/application_base.hpp>
#include <eosio/chain/exec_pri_queue.hpp>
#include <chrono>
#include <optional>
#include <mutex>

/*
Expand All @@ -23,15 +24,21 @@ enum class exec_window {

enum class exec_queue {
read_only, // the queue storing tasks which are safe to execute
// in parallel with other read-only tasks in the read-only
// in parallel with other read-only & read_exclusive tasks in the read-only
// thread pool as well as on the main app thread.
// Multi-thread safe as long as nothing is executed from the read_write queue.
read_write // the queue storing tasks which can be only executed
read_write, // the queue storing tasks which can be only executed
// on the app thread while read-only tasks are
// not being executed in read-only threads. Single threaded.
read_exclusive // the queue storing tasks which should only be executed
// in parallel with other read_exclusive or read_only tasks in the
// read-only thread pool. Should never be executed on the main thread.
// If no read-only thread pool is available this queue grows unbounded
// as tasks will never execute. User is responsible for not queueing
// read_exclusive tasks if no read-only thread pool is available.
};

class two_queue_executor {
class three_queue_executor {
public:

// Trade off on returning to appbase exec() loop as the overhead of poll/run can be measurable for small running tasks.
Expand All @@ -42,8 +49,10 @@ class two_queue_executor {
auto post( int priority, exec_queue q, Func&& func ) {
if ( q == exec_queue::read_write )
return boost::asio::post(io_serv_, read_write_queue_.wrap(priority, --order_, std::forward<Func>(func)));
else
else if ( q == exec_queue::read_only )
return boost::asio::post( io_serv_, read_only_queue_.wrap( priority, --order_, std::forward<Func>( func)));
else
return boost::asio::post( io_serv_, read_exclusive_queue_.wrap( priority, --order_, std::forward<Func>( func)));
}

// Legacy and deprecated. To be removed after cleaning up its uses in base appbase
Expand All @@ -56,6 +65,7 @@ class two_queue_executor {

boost::asio::io_service& get_io_service() { return io_serv_; }

// called from main thread
bool execute_highest() {
// execute for at least minimum runtime
const auto end = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(minimum_runtime_ms);
Expand All @@ -81,13 +91,20 @@ class two_queue_executor {
return more;
}

bool execute_highest_read_only() {
bool execute_highest_read() {
// execute for at least minimum runtime
const auto end = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(minimum_runtime_ms);

bool more = false;
while (true) {
more = read_only_queue_.execute_highest_locked( true );
std::optional<bool> exec_read_only_queue = read_only_queue_.compare_queues_locked(read_exclusive_queue_);
if (!exec_read_only_queue) {
more = read_only_queue_.execute_highest_locked( true );
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
} else if (*exec_read_only_queue) {
more = read_only_queue_.execute_highest_locked( false );
} else {
more = read_exclusive_queue_.execute_highest_locked( false );
}
if (!more || std::chrono::high_resolution_clock::now() > end)
break;
}
Expand All @@ -102,20 +119,29 @@ class two_queue_executor {
else
return read_only_queue_.wrap( priority, --order_, std::forward<Function>( func));
}

void stop() {
read_only_queue_.stop();
read_write_queue_.stop();
read_exclusive_queue_.stop();
}

void clear() {
read_only_queue_.clear();
read_write_queue_.clear();
read_exclusive_queue_.clear();
}

void set_to_read_window(uint32_t num_threads, std::function<bool()> should_exit) {
exec_window_ = exec_window::read;
read_only_queue_.enable_locking(num_threads, std::move(should_exit));
read_only_queue_.enable_locking(num_threads, should_exit);
read_exclusive_queue_.enable_locking(num_threads, std::move(should_exit));
}

void set_to_write_window() {
exec_window_ = exec_window::write;
read_only_queue_.disable_locking();
read_exclusive_queue_.disable_locking();
}

bool is_read_window() const {
Expand All @@ -127,19 +153,20 @@ class two_queue_executor {
}

auto& read_only_queue() { return read_only_queue_; }

auto& read_write_queue() { return read_write_queue_; }
auto& read_exclusive_queue() { return read_exclusive_queue_; }

// members are ordered taking into account that the last one is destructed first
private:
boost::asio::io_service io_serv_;
appbase::exec_pri_queue read_only_queue_;
appbase::exec_pri_queue read_write_queue_;
appbase::exec_pri_queue read_exclusive_queue_;
std::atomic<std::size_t> order_ { std::numeric_limits<size_t>::max() }; // to maintain FIFO ordering in both queues within priority
exec_window exec_window_ { exec_window::write };
};

using application = application_t<two_queue_executor>;
using application = application_t<three_queue_executor>;
}

#include <appbase/application_instance.hpp>
17 changes: 16 additions & 1 deletion libraries/custom_appbase/include/eosio/chain/exec_pri_queue.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <condition_variable>
#include <mutex>
#include <optional>
#include <queue>

namespace appbase {
Expand All @@ -13,6 +14,12 @@ class exec_pri_queue : public boost::asio::execution_context
{
public:

void stop() {
std::lock_guard g( mtx_ );
exiting_blocking_ = true;
cond_.notify_all();
}

void enable_locking(uint32_t num_threads, std::function<bool()> should_exit) {
assert(num_threads > 0 && num_waiting_ == 0);
lock_enabled_ = true;
Expand Down Expand Up @@ -105,6 +112,14 @@ class exec_pri_queue : public boost::asio::execution_context
// Only call when locking disabled
const auto& top() const { return handlers_.top(); }

// return empty optional if both queues empty.
// true if this queue has the highest priority task to execute
std::optional<bool> compare_queues_locked( const exec_pri_queue& rhs ) {
std::scoped_lock g(mtx_, rhs.mtx_);
if (empty() && rhs.empty()) return {};
return !empty() && (rhs.empty() || *rhs.top() < *top());
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
}

class executor
{
public:
Expand Down Expand Up @@ -220,7 +235,7 @@ class exec_pri_queue : public boost::asio::execution_context
};

bool lock_enabled_ = false;
std::mutex mtx_;
mutable std::mutex mtx_;
std::condition_variable cond_;
uint32_t num_waiting_{0};
uint32_t max_waiting_{0};
Expand Down
62 changes: 26 additions & 36 deletions plugins/producer_plugin/producer_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -807,9 +807,12 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
bool return_failure_traces,
next_function<transaction_trace_ptr> next) {
if (trx_type == transaction_metadata::trx_type::read_only) {
EOS_ASSERT( _ro_thread_pool_size > 0, unsupported_feature,
linh2931 marked this conversation as resolved.
Show resolved Hide resolved
"read-only transactions execution not enabled on API node. Set read-only-threads > 0" );

// Post all read only trxs to read_only queue for execution.
auto trx_metadata = transaction_metadata::create_no_recover_keys(trx, transaction_metadata::trx_type::read_only);
app().executor().post(priority::low, exec_queue::read_only, [this, trx{std::move(trx_metadata)}, next{std::move(next)}]() mutable {
app().executor().post(priority::low, exec_queue::read_exclusive, [this, trx{std::move(trx_metadata)}, next{std::move(next)}]() mutable {
push_read_only_transaction(std::move(trx), std::move(next));
});
return;
Expand Down Expand Up @@ -1047,8 +1050,8 @@ void producer_plugin::set_program_options(
producer_options.add_options()
("enable-stale-production,e", boost::program_options::bool_switch()->notifier([this](bool e){my->_production_enabled = e;}), "Enable block production, even if the chain is stale.")
("pause-on-startup,x", boost::program_options::bool_switch()->notifier([this](bool p){my->_pause_production = p;}), "Start this node in a state where production is paused")
("max-transaction-time", bpo::value<int32_t>()->default_value(30),
"Limits the maximum time (in milliseconds) that is allowed a pushed transaction's code to execute before being considered invalid")
("max-transaction-time", bpo::value<int32_t>()->default_value(config::block_interval_ms-1),
"Locally lowers the max_transaction_cpu_usage limit (in milliseconds) that an input transaction is allowed to execute before being considered invalid")
linh2931 marked this conversation as resolved.
Show resolved Hide resolved
("max-irreversible-block-age", bpo::value<int32_t>()->default_value( -1 ),
"Limits the maximum age (in seconds) of the DPOS Irreversible Block for a chain this node will produce blocks on (use negative value to indicate unlimited)")
("producer-name,p", boost::program_options::value<vector<string>>()->composing()->multitoken(),
Expand Down Expand Up @@ -1270,28 +1273,25 @@ void producer_plugin_impl::plugin_initialize(const boost::program_options::varia
("read", _ro_read_window_time_us)("min", _ro_read_window_minimum_time_us));
_ro_read_window_effective_time_us = _ro_read_window_time_us - _ro_read_window_minimum_time_us;

// Make sure a read-only transaction can finish within the read
// window if scheduled at the very beginning of the window.
// Add _ro_read_window_minimum_time_us for safety margin.
if (_max_transaction_time_ms.load() > 0) {
EOS_ASSERT(
_ro_read_window_time_us > (fc::milliseconds(_max_transaction_time_ms.load()) + _ro_read_window_minimum_time_us),
plugin_config_exception,
"read-only-read-window-time-us (${read} us) must be greater than max-transaction-time (${trx_time} us) "
"plus ${min} us, required: ${read} us > (${trx_time} us + ${min} us).",
("read", _ro_read_window_time_us)("trx_time", _max_transaction_time_ms.load() * 1000)("min", _ro_read_window_minimum_time_us));
}
linh2931 marked this conversation as resolved.
Show resolved Hide resolved
ilog("read-only-write-window-time-us: ${ww} us, read-only-read-window-time-us: ${rw} us, effective read window time to be used: ${w} us",
("ww", _ro_write_window_time_us)("rw", _ro_read_window_time_us)("w", _ro_read_window_effective_time_us));
}

// Make sure _ro_max_trx_time_us is alwasys set.
// Make sure _ro_max_trx_time_us is always set.
// Make sure a read-only transaction can finish within the read
// window if scheduled at the very beginning of the window.
// Add _ro_read_window_minimum_time_us for safety margin.
if (_max_transaction_time_ms.load() > 0) {
_ro_max_trx_time_us = fc::milliseconds(_max_transaction_time_ms.load());
} else {
// max-transaction-time can be set to negative for unlimited time
_ro_max_trx_time_us = fc::microseconds::maximum();
}
if (_ro_max_trx_time_us > _ro_read_window_effective_time_us) {
_ro_max_trx_time_us = _ro_read_window_effective_time_us;
}
ilog("Read-only max transaction time ${rot}us set to fit in the effective read-only window ${row}us.",
("rot", _ro_max_trx_time_us)("row", _ro_read_window_effective_time_us));
ilog("read-only-threads ${s}, max read-only trx time to be enforced: ${t} us", ("s", _ro_thread_pool_size)("t", _ro_max_trx_time_us));

_incoming_block_sync_provider = app().get_method<incoming::methods::block_sync>().register_provider(
Expand Down Expand Up @@ -1427,6 +1427,10 @@ void producer_plugin::plugin_startup() {
void producer_plugin_impl::plugin_shutdown() {
boost::system::error_code ec;
_timer.cancel(ec);
boost::system::error_code ro_ec;
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
_ro_timer.cancel(ro_ec);
app().executor().stop();
_ro_thread_pool.stop();
_thread_pool.stop();
_unapplied_transactions.clear();

Expand Down Expand Up @@ -2841,7 +2845,7 @@ void producer_plugin_impl::switch_to_read_window() {
_time_tracker.pause();

// we are in write window, so no read-only trx threads are processing transactions.
if (app().executor().read_only_queue().empty()) { // no read-only tasks to process. stay in write window
if (app().executor().read_only_queue().empty() && app().executor().read_exclusive_queue().empty()) { // no read-only tasks to process. stay in write window
start_write_window(); // restart write window timer for next round
return;
}
Expand Down Expand Up @@ -2892,7 +2896,7 @@ bool producer_plugin_impl::read_only_execution_task(uint32_t pending_block_num)
// 2. net_plugin receives a block
// 3. no read-only tasks to execute
while (fc::time_point::now() < _ro_window_deadline && _received_block < pending_block_num) {
bool more = app().executor().execute_highest_read_only(); // blocks until all read only threads are idle
bool more = app().executor().execute_highest_read(); // blocks until all read only threads are idle
if (!more) {
break;
}
Expand All @@ -2909,7 +2913,7 @@ bool producer_plugin_impl::read_only_execution_task(uint32_t pending_block_num)
// last thread post any exhausted back into read_only queue with slightly higher priority (low+1) so they are executed first
ro_trx_t t;
while (_ro_exhausted_trx_queue.pop_front(t)) {
app().executor().post(priority::low + 1, exec_queue::read_only, [this, trx{std::move(t.trx)}, next{std::move(t.next)}]() mutable {
app().executor().post(priority::low + 1, exec_queue::read_exclusive, [this, trx{std::move(t.trx)}, next{std::move(t.next)}]() mutable {
push_read_only_transaction(std::move(trx), std::move(next));
});
}
Expand All @@ -2927,7 +2931,7 @@ void producer_plugin_impl::repost_exhausted_transactions(const fc::time_point& d
// post any exhausted back into read_only queue with slightly higher priority (low+1) so they are executed first
linh2931 marked this conversation as resolved.
Show resolved Hide resolved
ro_trx_t t;
while (!should_interrupt_start_block(deadline, pending_block_num) && _ro_exhausted_trx_queue.pop_front(t)) {
app().executor().post(priority::low + 1, exec_queue::read_only, [this, trx{std::move(t.trx)}, next{std::move(t.next)}]() mutable {
app().executor().post(priority::low + 1, exec_queue::read_exclusive, [this, trx{std::move(t.trx)}, next{std::move(t.next)}]() mutable {
push_read_only_transaction(std::move(trx), std::move(next));
});
}
Expand All @@ -2947,21 +2951,10 @@ bool producer_plugin_impl::push_read_only_transaction(transaction_metadata_ptr t
return true;
}

// When executing a read-only trx on the main thread while in the write window,
// need to switch db mode to read only.
auto db_read_only_mode_guard = fc::make_scoped_exit([&] {
if (chain.is_write_window())
chain.unset_db_read_only_mode();
});
assert(!chain.is_write_window());

std::optional<block_time_tracker::trx_time_tracker> trx_tracker;
if ( chain.is_write_window() ) {
chain.set_db_read_only_mode();
trx_tracker.emplace(_time_tracker.start_trx(true, start));
}

// use read-window/write-window deadline if there are read/write windows, otherwise use block_deadline if only the app thead
auto window_deadline = (_ro_thread_pool_size != 0) ? _ro_window_deadline : _pending_block_deadline;
// use read-window/write-window deadline
auto window_deadline = _ro_window_deadline;

// Ensure the trx to finish by the end of read-window or write-window or block_deadline depending on
auto trace = chain.push_transaction(trx, window_deadline, _ro_max_trx_time_us, 0, false, 0);
Expand All @@ -2979,9 +2972,6 @@ bool producer_plugin_impl::push_read_only_transaction(transaction_metadata_ptr t
_ro_exhausted_trx_queue.push_front({std::move(trx), std::move(next)});
}

if ( chain.is_write_window() && !pr.failed ) {
trx_tracker->trx_success();
}
} catch (const guard_exception& e) {
chain_plugin::handle_guard_exception(e);
} catch (boost::interprocess::bad_alloc&) {
Expand Down
6 changes: 3 additions & 3 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,11 @@ add_test(NAME nodeos_protocol_feature_test COMMAND tests/nodeos_protocol_feature
set_property(TEST nodeos_protocol_feature_test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME compute_transaction_test COMMAND tests/compute_transaction_test.py -v -p 2 -n 3 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST compute_transaction_test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME read-only-trx-basic-test COMMAND tests/read_only_trx_test.py -v -p 2 -n 3 --read-only-threads 0 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_test(NAME read-only-trx-basic-test COMMAND tests/read_only_trx_test.py -v -p 2 -n 3 --read-only-threads 1 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST read-only-trx-basic-test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME read-only-trx-parallel-test COMMAND tests/read_only_trx_test.py -v -p 2 -n 3 --read-only-threads 128 --num-test-runs 3 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_test(NAME read-only-trx-parallel-test COMMAND tests/read_only_trx_test.py -v -p 2 -n 3 --read-only-threads 16 --num-test-runs 3 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST read-only-trx-parallel-test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME read-only-trx-parallel-eos-vm-oc-test COMMAND tests/read_only_trx_test.py -v -p 2 -n 3 --eos-vm-oc-enable all --read-only-threads 128 --num-test-runs 3 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_test(NAME read-only-trx-parallel-eos-vm-oc-test COMMAND tests/read_only_trx_test.py -v -p 2 -n 3 --eos-vm-oc-enable all --read-only-threads 16 --num-test-runs 3 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST read-only-trx-parallel-eos-vm-oc-test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME read-only-trx-parallel-no-oc-test COMMAND tests/read_only_trx_test.py -v -p 2 -n 3 --eos-vm-oc-enable none --read-only-threads 6 --num-test-runs 2 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST read-only-trx-parallel-no-oc-test PROPERTY LABELS nonparallelizable_tests)
Expand Down
Loading