Compare commits

..

20 Commits

Author SHA1 Message Date
Clayton Calabrese 4ccaca399f Merge branch 'fix_missing_node_py_changes' of https://github.com/AntelopeIO/leap into fix_missing_node_py_changes 2023-03-22 18:14:16 -05:00
Clayton Calabrese 09390a4725 return lost changes to Node.py functions 2023-03-22 18:13:24 -05:00
Matt Witherspoon 7c94e3a102 add PACKED_STRUCT on ControlStructureImm to silence warning 2023-03-22 18:12:54 -05:00
Matt Witherspoon f469d2b78a add static_assert()s on required size of IR::OpcodeAndImm<>s 2023-03-22 18:12:54 -05:00
Matt Witherspoon a9c5cd9f95 fix comparison of different signedness warning in log_index 2023-03-22 18:12:54 -05:00
Lin Huang 41f5a148bb correct version after merging from 4.0.0-rc1 2023-03-22 18:12:54 -05:00
Zach Butler 1f0f5280d5 Restore markdown formatting erroneously deleted in commit 06c9e25327 2023-03-22 18:12:54 -05:00
Zach Butler 6b54dac75f Cardinal objects should be in a numbered list 2023-03-22 18:12:54 -05:00
Zach Butler df4a974b8a Pull note about sudo into its own quote block 2023-03-22 18:12:54 -05:00
Zach Butler c9596cd699 Minor pinned build script README changes 2023-03-22 18:12:54 -05:00
Clayton Calabrese 6c8441bc6c return lost changes to Node.py functions 2023-03-22 17:15:59 -05:00
Matt Witherspoon 1985e28a63 Merge pull request #866 from AntelopeIO/ignored_pack_warn_fix
fix noisy `ignoring packed attribute` warning on `IR::ControlStructureImm`
2023-03-22 11:06:46 -07:00
Vlad 2829420ecb Merge pull request #856 from AntelopeIO/increase-snapshot-test-robustness
[4.0] Increase robustness of snapshot diff python test
2023-03-22 11:51:46 -04:00
Matt Witherspoon 95c22b6e70 add PACKED_STRUCT on ControlStructureImm to silence warning 2023-03-22 10:57:40 -04:00
Matt Witherspoon f8ca18d5f5 add static_assert()s on required size of IR::OpcodeAndImm<>s 2023-03-22 10:55:29 -04:00
Matt Witherspoon bef672aae2 Merge pull request #860 from AntelopeIO/log_index_un_signed_warn
fix comparison of different signedness warning in log_index
2023-03-22 07:30:28 -07:00
Kevin Heifner 87c7480656 Merge pull request #852 from AntelopeIO/GH-850-test-fix
[4.0] Test Fix: Pass a large time limit to get table call
2023-03-22 09:25:02 -05:00
Matt Witherspoon c2366d392b fix comparison of different signedness warning in log_index 2023-03-21 20:17:49 -04:00
766C6164 cb64c3fb38 schedule snapshot at exact block num of another one 2023-03-21 15:43:31 -04:00
Kevin Heifner 446a04a607 GH-850 Pass a large time limit so python does not have to handle more=true case. 2023-03-21 13:09:26 -05:00
28 changed files with 325 additions and 551 deletions
+1 -1
View File
@@ -1533,7 +1533,7 @@ namespace eosio { namespace chain {
ilog("blocks.log and blocks.index agree on number of blocks");
if (interval == 0) {
interval = std::max((log_bundle.log_index.num_blocks() + 7) >> 3, 1);
interval = std::max((log_bundle.log_index.num_blocks() + 7u) >> 3, 1u);
}
uint32_t expected_block_num = log_bundle.log_data.first_block_num();
@@ -32,7 +32,7 @@ class log_index {
bool is_open() const { return file_.is_open(); }
uint64_t back() { return nth_block_position(num_blocks()-1); }
int num_blocks() const { return num_blocks_; }
unsigned num_blocks() const { return num_blocks_; }
uint64_t nth_block_position(uint32_t n) {
file_.seek(n*sizeof(uint64_t));
uint64_t r;
@@ -1,33 +1,30 @@
#pragma once
#include <appbase/application_base.hpp>
#include <eosio/chain/exec_pri_queue.hpp>
#include <mutex>
#include <appbase/execution_priority_queue.hpp>
/*
* Customize appbase to support two-queue executor.
* Custmomize appbase to support two-queue exector.
*/
namespace appbase {
enum class exec_window {
read, // the window during which operations from parallel queue
// can be executed in parallel in the read-only thread pool
// as well as in the app thread.
write, // the window during which operations from both read_write and
// parallel queues can be executed in app thread,
// while read-only operations are not executed in read-only
// thread pool. The read-only thread pool is not active; only
// the main app thread is active.
read, // the window during which operations from read_only_trx_safe queue
// can be executed in app thread in parallel with read-only transactions
// in read-only transaction excuting threads.
write, // the window during which operations from both general and
// read_only_trx_safe queues can be executed in app thread,
// while read-only transactions are not executed in read-only
// transaction excuting threads.
};
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
// 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
// on the app thread while read-only tasks are
// not being executed in read-only threads. Single threaded.
read_only_trx_safe, // the queue storing operations which are safe to execute
// on app thread in parallel with read-only transactions
// in read-only transaction excuting threads.
general // the queue storing operations which can be only executed
// on the app thread while read-only transactions are
// not being executed in read-only threads
};
class two_queue_executor {
@@ -35,64 +32,56 @@ public:
template <typename Func>
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)));
if ( q == exec_queue::general )
return boost::asio::post(io_serv_, general_queue_.wrap(priority, --order_, std::forward<Func>(func)));
else
return boost::asio::post( io_serv_, read_only_queue_.wrap( priority, --order_, std::forward<Func>( func)));
return boost::asio::post(io_serv_, read_only_trx_safe_queue_.wrap(priority, --order_, std::forward<Func>(func)));
}
// Legacy and deprecated. To be removed after cleaning up its uses in base appbase
template <typename Func>
auto post( int priority, Func&& func ) {
// safer to use read_write queue for unknown type of operation since operations
// from read_write queue are not executed in parallel with read-only operations
return boost::asio::post(io_serv_, read_write_queue_.wrap(priority, --order_, std::forward<Func>(func)));
// safer to use general queue for unknown type of operation since operations
// from general queue are not executed in parallel with read-only transactions
return boost::asio::post(io_serv_, general_queue_.wrap(priority, --order_, std::forward<Func>(func)));
}
boost::asio::io_service& get_io_service() { return io_serv_; }
bool execute_highest() {
if ( exec_window_ == exec_window::write ) {
// During write window only main thread is accessing anything in two_queue_executor, no locking required
if( !read_write_queue_.empty() && (read_only_queue_.empty() || *read_only_queue_.top() < *read_write_queue_.top()) ) {
// read_write_queue_'s top function's priority greater than read_only_queue_'s top function's, or read_only_queue_ empty
read_write_queue_.execute_highest();
} else if( !read_only_queue_.empty() ) {
read_only_queue_.execute_highest();
if( !general_queue_.empty() && ( read_only_trx_safe_queue_.empty() || *read_only_trx_safe_queue_.top() < *general_queue_.top()) ) {
// general_queue_'s top function's priority greater than read_only_trx_safe_queue_'s top function's, or general_queue_ empty
general_queue_.execute_highest();
} else if( !read_only_trx_safe_queue_.empty() ) {
read_only_trx_safe_queue_.execute_highest();
}
return !read_only_queue_.empty() || !read_write_queue_.empty();
return !read_only_trx_safe_queue_.empty() || !general_queue_.empty();
} else {
// When in read window, multiple threads including main app thread are accessing two_queue_executor, locking required
return read_only_queue_.execute_highest_locked();
return read_only_trx_safe_queue_.execute_highest();
}
}
bool execute_highest_read_only() {
return read_only_queue_.execute_highest_locked();
}
template <typename Function>
boost::asio::executor_binder<Function, appbase::exec_pri_queue::executor>
boost::asio::executor_binder<Function, appbase::execution_priority_queue::executor>
wrap(int priority, exec_queue q, Function&& func ) {
if ( q == exec_queue::read_write )
return read_write_queue_.wrap(priority, --order_, std::forward<Function>(func));
if ( q == exec_queue::general )
return general_queue_.wrap(priority, --order_, std::forward<Function>(func));
else
return read_only_queue_.wrap( priority, --order_, std::forward<Function>( func));
return read_only_trx_safe_queue_.wrap(priority, --order_, std::forward<Function>(func));
}
void clear() {
read_only_queue_.clear();
read_write_queue_.clear();
read_only_trx_safe_queue_.clear();
general_queue_.clear();
}
void set_to_read_window() {
exec_window_ = exec_window::read;
read_only_queue_.enable_locking();
}
void set_to_write_window() {
exec_window_ = exec_window::write;
read_only_queue_.disable_locking();
}
bool is_read_window() const {
@@ -103,15 +92,15 @@ public:
return exec_window_ == exec_window::write;
}
auto& read_only_queue() { return read_only_queue_; }
auto& read_only_trx_safe_queue() { return read_only_trx_safe_queue_; }
auto& read_write_queue() { return read_write_queue_; }
auto& general_queue() { return general_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::execution_priority_queue read_only_trx_safe_queue_;
appbase::execution_priority_queue general_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 };
};
@@ -1,203 +0,0 @@
#pragma once
#include <boost/asio.hpp>
#include <mutex>
#include <queue>
namespace appbase {
// adapted from: https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/example/cpp11/invocation/prioritised_handlers.cpp
// Locking has to be coordinated by caller, use with care.
class exec_pri_queue : public boost::asio::execution_context
{
public:
void enable_locking() {
lock_enabled_ = true;
}
void disable_locking() {
lock_enabled_ = false;
}
// called from appbase::application_base::exec poll_one() or run_one()
template <typename Function>
void add(int priority, size_t order, Function function)
{
std::unique_ptr<queued_handler_base> handler(new queued_handler<Function>(priority, order, std::move(function)));
if (lock_enabled_) {
std::lock_guard g( mtx_ );
handlers_.push( std::move( handler ) );
} else {
handlers_.push( std::move( handler ) );
}
}
// only call when no lock required
void clear()
{
handlers_ = prio_queue();
}
// only call when no lock required
bool execute_highest()
{
if( !handlers_.empty() ) {
handlers_.top()->execute();
handlers_.pop();
}
return !handlers_.empty();
}
private:
// has to be defined before use, auto return type
auto pop() {
auto t = std::move(const_cast<std::unique_ptr<queued_handler_base>&>(handlers_.top()));
handlers_.pop();
return t;
}
public:
bool execute_highest_locked() {
std::unique_lock g(mtx_);
if( handlers_.empty() )
return false;
auto t = pop();
g.unlock();
t->execute();
g.lock();
return !handlers_.empty();
}
// Only call when locking disabled
size_t size() const { return handlers_.size(); }
// Only call when locking disabled
bool empty() const { return handlers_.empty(); }
// Only call when locking disabled
const auto& top() const { return handlers_.top(); }
class executor
{
public:
executor(exec_pri_queue& q, int p, size_t o)
: context_(q), priority_(p), order_(o)
{
}
exec_pri_queue& context() const noexcept
{
return context_;
}
template <typename Function, typename Allocator>
void dispatch(Function f, const Allocator&) const
{
context_.add(priority_, order_, std::move(f));
}
template <typename Function, typename Allocator>
void post(Function f, const Allocator&) const
{
context_.add(priority_, order_, std::move(f));
}
template <typename Function, typename Allocator>
void defer(Function f, const Allocator&) const
{
context_.add(priority_, order_, std::move(f));
}
void on_work_started() const noexcept {}
void on_work_finished() const noexcept {}
bool operator==(const executor& other) const noexcept
{
return order_ == other.order_ && &context_ == &other.context_ && priority_ == other.priority_;
}
bool operator!=(const executor& other) const noexcept
{
return !operator==(other);
}
private:
exec_pri_queue& context_;
int priority_;
size_t order_;
};
template <typename Function>
boost::asio::executor_binder<Function, executor>
wrap(int priority, size_t order, Function&& func)
{
return boost::asio::bind_executor( executor(*this, priority, order), std::forward<Function>(func) );
}
private:
class queued_handler_base
{
public:
queued_handler_base( int p, size_t order )
: priority_( p )
, order_( order )
{
}
virtual ~queued_handler_base() = default;
virtual void execute() = 0;
int priority() const { return priority_; }
// C++20
// friend std::weak_ordering operator<=>(const queued_handler_base&,
// const queued_handler_base&) noexcept = default;
friend bool operator<(const queued_handler_base& a,
const queued_handler_base& b) noexcept
{
return std::tie( a.priority_, a.order_ ) < std::tie( b.priority_, b.order_ );
}
private:
int priority_;
size_t order_;
};
template <typename Function>
class queued_handler : public queued_handler_base
{
public:
queued_handler(int p, size_t order, Function f)
: queued_handler_base( p, order )
, function_( std::move(f) )
{
}
void execute() override
{
function_();
}
private:
Function function_;
};
struct deref_less
{
template<typename Pointer>
bool operator()(const Pointer& a, const Pointer& b) noexcept(noexcept(*a < *b))
{
return *a < *b;
}
};
bool lock_enabled_ = false;
std::mutex mtx_;
using prio_queue = std::priority_queue<std::unique_ptr<queued_handler_base>, std::deque<std::unique_ptr<queued_handler_base>>, deref_less>;
prio_queue handlers_;
};
} // appbase
@@ -27,28 +27,28 @@ BOOST_AUTO_TEST_CASE( default_exec_window ) {
// post functions
std::map<int, int> rslts {};
int seq_num = 0;
app->executor().post( priority::medium, exec_queue::read_only, [&]() { rslts[0]=seq_num; ++seq_num; } );
app->executor().post( priority::medium, exec_queue::read_write, [&]() { rslts[1]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_write, [&]() { rslts[2]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_only, [&]() { rslts[3]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_only, [&]() { rslts[4]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_write, [&]() { rslts[5]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::read_only, [&]() { rslts[6]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_write, [&]() { rslts[7]=seq_num; ++seq_num; } );
app->executor().post( priority::medium, exec_queue::read_only_trx_safe, [&]() { rslts[0]=seq_num; ++seq_num; } );
app->executor().post( priority::medium, exec_queue::general, [&]() { rslts[1]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::general, [&]() { rslts[2]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_only_trx_safe, [&]() { rslts[3]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_only_trx_safe, [&]() { rslts[4]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::general, [&]() { rslts[5]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::read_only_trx_safe, [&]() { rslts[6]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::general, [&]() { rslts[7]=seq_num; ++seq_num; } );
// Stop app. Use the lowest priority to make sure this function to execute the last
app->executor().post( priority::lowest, exec_queue::read_only, [&]() {
// read_only_queue should only contain the current lambda function,
// and read_write_queue should have executed all its functions
BOOST_REQUIRE_EQUAL( app->executor().read_only_queue().size(), 1); // pop()s after execute
BOOST_REQUIRE_EQUAL( app->executor().read_write_queue().size(), 0 );
app->executor().post( priority::lowest, exec_queue::read_only_trx_safe, [&]() {
// read_only_trx_safe_queue should only contain the current lambda function,
// and general_queue should have execute all its functions
BOOST_REQUIRE_EQUAL( app->executor().read_only_trx_safe_queue().size(), 1);
BOOST_REQUIRE_EQUAL( app->executor().general_queue().size(), 0 );
app->quit();
} );
app_thread.join();
// both queues are cleared after execution
BOOST_REQUIRE_EQUAL( app->executor().read_only_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().read_write_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().read_only_trx_safe_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().general_queue().empty(), true);
// exactly number of both queues' functions processed
BOOST_REQUIRE_EQUAL( rslts.size(), 8 );
@@ -64,40 +64,40 @@ BOOST_AUTO_TEST_CASE( default_exec_window ) {
BOOST_CHECK_LT( rslts[6], rslts[7] );
}
// verify functions only from read_only queue are processed during read window
// verify functions only from read_only_trx_safe queue are processed during read window
BOOST_AUTO_TEST_CASE( execute_from_read_queue ) {
appbase::scoped_app app;
auto app_thread = start_app_thread(app);
// set to run functions from read_only queue only
// set to run functions from read_only_trx_safe queue only
app->executor().set_to_read_window();
// post functions
std::map<int, int> rslts {};
int seq_num = 0;
app->executor().post( priority::medium, exec_queue::read_write, [&]() { rslts[0]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_only, [&]() { rslts[1]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_write, [&]() { rslts[2]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_only, [&]() { rslts[3]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_only, [&]() { rslts[4]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_write, [&]() { rslts[5]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::read_only, [&]() { rslts[6]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::read_only, [&]() { rslts[7]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_only, [&]() { rslts[8]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_write, [&]() { rslts[9]=seq_num; ++seq_num; } );
app->executor().post( priority::medium, exec_queue::general, [&]() { rslts[0]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_only_trx_safe, [&]() { rslts[1]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::general, [&]() { rslts[2]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_only_trx_safe, [&]() { rslts[3]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_only_trx_safe, [&]() { rslts[4]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::general, [&]() { rslts[5]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::read_only_trx_safe, [&]() { rslts[6]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::read_only_trx_safe, [&]() { rslts[7]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_only_trx_safe, [&]() { rslts[8]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::general, [&]() { rslts[9]=seq_num; ++seq_num; } );
// stop application. Use lowest at the end to make sure this executes the last
app->executor().post( priority::lowest, exec_queue::read_only, [&]() {
// read_queue should be empty (read window pops before execute) and write_queue should have all its functions
BOOST_REQUIRE_EQUAL( app->executor().read_only_queue().size(), 0); // pop()s before execute
BOOST_REQUIRE_EQUAL( app->executor().read_write_queue().size(), 4 );
app->executor().post( priority::lowest, exec_queue::read_only_trx_safe, [&]() {
// read_queue should have current function and write_queue should have all its functions
BOOST_REQUIRE_EQUAL( app->executor().read_only_trx_safe_queue().size(), 1);
BOOST_REQUIRE_EQUAL( app->executor().general_queue().size(), 4 );
app->quit();
} );
app_thread.join();
// both queues are cleared after execution
BOOST_REQUIRE_EQUAL( app->executor().read_only_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().read_write_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().read_only_trx_safe_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().general_queue().empty(), true);
// exactly number of posts processed
BOOST_REQUIRE_EQUAL( rslts.size(), 6 );
@@ -109,40 +109,40 @@ BOOST_AUTO_TEST_CASE( execute_from_read_queue ) {
BOOST_CHECK_LT( rslts[3], rslts[4] );
}
// verify no functions are executed during read window if read_only queue is empty
// verify no functions are executed during read window if read_only_trx_safe queue is empty
BOOST_AUTO_TEST_CASE( execute_from_empty_read_queue ) {
appbase::scoped_app app;
auto app_thread = start_app_thread(app);
// set to run functions from read_only queue only
// set to run functions from read_only_trx_safe queue only
app->executor().set_to_read_window();
// post functions
std::map<int, int> rslts {};
int seq_num = 0;
app->executor().post( priority::medium, exec_queue::read_write, [&]() { rslts[0]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_write, [&]() { rslts[1]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_write, [&]() { rslts[2]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_write, [&]() { rslts[3]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_write, [&]() { rslts[4]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_write, [&]() { rslts[5]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::read_write, [&]() { rslts[6]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::read_write, [&]() { rslts[7]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_write, [&]() { rslts[8]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_write, [&]() { rslts[9]=seq_num; ++seq_num; } );
app->executor().post( priority::medium, exec_queue::general, [&]() { rslts[0]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::general, [&]() { rslts[1]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::general, [&]() { rslts[2]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::general, [&]() { rslts[3]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::general, [&]() { rslts[4]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::general, [&]() { rslts[5]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::general, [&]() { rslts[6]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::general, [&]() { rslts[7]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::general, [&]() { rslts[8]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::general, [&]() { rslts[9]=seq_num; ++seq_num; } );
// Stop application. Use lowest at the end to make sure this executes the last
app->executor().post( priority::lowest, exec_queue::read_only, [&]() {
// read_queue should be empty (read window pops before execute) and write_queue should have all its functions
BOOST_REQUIRE_EQUAL( app->executor().read_only_queue().size(), 0); // pop()s before execute
BOOST_REQUIRE_EQUAL( app->executor().read_write_queue().size(), 10 );
app->executor().post( priority::lowest, exec_queue::read_only_trx_safe, [&]() {
// read_queue should have current function and write_queue should have all its functions
BOOST_REQUIRE_EQUAL( app->executor().read_only_trx_safe_queue().size(), 1);
BOOST_REQUIRE_EQUAL( app->executor().general_queue().size(), 10 );
app->quit();
} );
app_thread.join();
// both queues are cleared after execution
BOOST_REQUIRE_EQUAL( app->executor().read_only_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().read_write_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().read_only_trx_safe_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().general_queue().empty(), true);
// no results
BOOST_REQUIRE_EQUAL( rslts.size(), 0 );
@@ -159,32 +159,32 @@ BOOST_AUTO_TEST_CASE( execute_from_both_queues ) {
// post functions
std::map<int, int> rslts {};
int seq_num = 0;
app->executor().post( priority::medium, exec_queue::read_only, [&]() { rslts[0]=seq_num; ++seq_num; } );
app->executor().post( priority::medium, exec_queue::read_write, [&]() { rslts[1]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::read_write, [&]() { rslts[2]=seq_num; ++seq_num; } );
app->executor().post( priority::lowest, exec_queue::read_only, [&]() { rslts[3]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_only, [&]() { rslts[4]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_write, [&]() { rslts[5]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::read_only, [&]() { rslts[6]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_write, [&]() { rslts[7]=seq_num; ++seq_num; } );
app->executor().post( priority::lowest, exec_queue::read_only, [&]() { rslts[8]=seq_num; ++seq_num; } );
app->executor().post( priority::lowest, exec_queue::read_only, [&]() { rslts[9]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_write, [&]() { rslts[10]=seq_num; ++seq_num; } );
app->executor().post( priority::medium, exec_queue::read_write, [&]() { rslts[11]=seq_num; ++seq_num; } );
app->executor().post( priority::medium, exec_queue::read_only_trx_safe, [&]() { rslts[0]=seq_num; ++seq_num; } );
app->executor().post( priority::medium, exec_queue::general, [&]() { rslts[1]=seq_num; ++seq_num; } );
app->executor().post( priority::high, exec_queue::general, [&]() { rslts[2]=seq_num; ++seq_num; } );
app->executor().post( priority::lowest, exec_queue::read_only_trx_safe, [&]() { rslts[3]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::read_only_trx_safe, [&]() { rslts[4]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::general, [&]() { rslts[5]=seq_num; ++seq_num; } );
app->executor().post( priority::highest,exec_queue::read_only_trx_safe, [&]() { rslts[6]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::general, [&]() { rslts[7]=seq_num; ++seq_num; } );
app->executor().post( priority::lowest, exec_queue::read_only_trx_safe, [&]() { rslts[8]=seq_num; ++seq_num; } );
app->executor().post( priority::lowest, exec_queue::read_only_trx_safe, [&]() { rslts[9]=seq_num; ++seq_num; } );
app->executor().post( priority::low, exec_queue::general, [&]() { rslts[10]=seq_num; ++seq_num; } );
app->executor().post( priority::medium, exec_queue::general, [&]() { rslts[11]=seq_num; ++seq_num; } );
// stop application. Use lowest at the end to make sure this executes the last
app->executor().post( priority::lowest, exec_queue::read_only, [&]() {
app->executor().post( priority::lowest, exec_queue::read_only_trx_safe, [&]() {
// read_queue should have current function and write_queue's functions are all executed
BOOST_REQUIRE_EQUAL( app->executor().read_only_queue().size(), 1); // pop()s after execute
BOOST_REQUIRE_EQUAL( app->executor().read_write_queue().size(), 0 );
BOOST_REQUIRE_EQUAL( app->executor().read_only_trx_safe_queue().size(), 1);
BOOST_REQUIRE_EQUAL( app->executor().general_queue().size(), 0 );
app->quit();
} );
app_thread.join();
// queues are emptied after quit
BOOST_REQUIRE_EQUAL( app->executor().read_only_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().read_write_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().read_only_trx_safe_queue().empty(), true);
BOOST_REQUIRE_EQUAL( app->executor().general_queue().empty(), true);
// exactly number of posts processed
BOOST_REQUIRE_EQUAL( rslts.size(), 12 );
+18 -2
View File
@@ -12,10 +12,11 @@ namespace IR
struct NoImm {};
struct MemoryImm {};
PACKED_STRUCT(
struct ControlStructureImm
{
ResultType resultType{};
};
});
struct BranchImm
{
@@ -675,4 +676,19 @@ namespace IR
};
IR_API const char* getOpcodeName(Opcode opcode);
}
}
//paranoia for future platforms
static_assert(sizeof(IR::OpcodeAndImm<IR::NoImm>) == 2);
static_assert(sizeof(IR::OpcodeAndImm<IR::MemoryImm>) == 2);
static_assert(sizeof(IR::OpcodeAndImm<IR::ControlStructureImm>) == 3);
static_assert(sizeof(IR::OpcodeAndImm<IR::BranchImm>) == 6);
static_assert(sizeof(IR::OpcodeAndImm<IR::BranchTableImm>) == 18);
static_assert(sizeof(IR::OpcodeAndImm<IR::LiteralImm<I32>>) == 6);
static_assert(sizeof(IR::OpcodeAndImm<IR::LiteralImm<I64>>) == 10);
static_assert(sizeof(IR::OpcodeAndImm<IR::LiteralImm<F32>>) == 6);
static_assert(sizeof(IR::OpcodeAndImm<IR::LiteralImm<F64>>) == 10);
static_assert(sizeof(IR::OpcodeAndImm<IR::GetOrSetVariableImm<false>>) == 6);
static_assert(sizeof(IR::OpcodeAndImm<IR::CallImm>) == 6);
static_assert(sizeof(IR::OpcodeAndImm<IR::CallIndirectImm>) == 6);
static_assert(sizeof(IR::OpcodeAndImm<IR::LoadOrStoreImm<0>>) == 10);
@@ -75,7 +75,7 @@ void chain_api_plugin::plugin_startup() {
ro_api.set_shorten_abi_errors( !http_plugin::verbose_errors() );
_http_plugin.add_api( {
CHAIN_RO_CALL(get_info, 200, http_params_types::no_params)}, appbase::exec_queue::read_only, appbase::priority::medium_high);
CHAIN_RO_CALL(get_info, 200, http_params_types::no_params)}, appbase::exec_queue::read_only_trx_safe, appbase::priority::medium_high);
_http_plugin.add_api({
CHAIN_RO_CALL(get_activated_protocol_features, 200, http_params_types::possible_no_params),
CHAIN_RO_CALL(get_block_info, 200, http_params_types::params_required),
@@ -103,12 +103,12 @@ void chain_api_plugin::plugin_startup() {
CHAIN_RW_CALL_ASYNC(push_transactions, chain_apis::read_write::push_transactions_results, 202, http_params_types::params_required),
CHAIN_RW_CALL_ASYNC(send_transaction, chain_apis::read_write::send_transaction_results, 202, http_params_types::params_required),
CHAIN_RW_CALL_ASYNC(send_transaction2, chain_apis::read_write::send_transaction_results, 202, http_params_types::params_required)
}, appbase::exec_queue::read_only);
}, appbase::exec_queue::read_only_trx_safe);
// Not safe to run in parallel with read-only transactions
_http_plugin.add_api({
CHAIN_RW_CALL_ASYNC(push_block, chain_apis::read_write::push_block_results, 202, http_params_types::params_required)
}, appbase::exec_queue::read_write, appbase::priority::medium_low );
}, appbase::exec_queue::general, appbase::priority::medium_low );
if (chain.account_queries_enabled()) {
_http_plugin.add_async_api({
@@ -124,7 +124,7 @@ void chain_api_plugin::plugin_startup() {
if (chain.transaction_finality_status_enabled()) {
_http_plugin.add_api({
CHAIN_RO_CALL_WITH_400(get_transaction_status, 200, http_params_types::params_required),
}, appbase::exec_queue::read_only);
}, appbase::exec_queue::read_only_trx_safe);
}
_http_plugin.add_api({
@@ -160,7 +160,7 @@ void chain_api_plugin::plugin_startup() {
}
}
}
}, appbase::exec_queue::read_only);
}, appbase::exec_queue::read_only_trx_safe);
}
void chain_api_plugin::plugin_shutdown() {}
@@ -28,7 +28,7 @@ using namespace eosio;
void db_size_api_plugin::plugin_startup() {
app().get_plugin<http_plugin>().add_api({
CALL_WITH_400(db_size, this, get, INVOKE_R_V(this, get), 200),
}, appbase::exec_queue::read_only);
}, appbase::exec_queue::read_only_trx_safe);
}
db_size_stats db_size_api_plugin::get() {
+1 -1
View File
@@ -334,7 +334,7 @@ namespace eosio {
handle_exception("node", "get_supported_apis", body.empty() ? "{}" : body, cb);
}
}
}}, appbase::exec_queue::read_only);
}}, appbase::exec_queue::read_only_trx_safe);
} catch (...) {
fc_elog(logger(), "http_plugin startup fails, shutting down");
@@ -87,12 +87,8 @@ namespace eosio {
void plugin_shutdown();
void handle_sighup() override;
void add_handler(const string& url, const url_handler&, appbase::exec_queue q,
int priority = appbase::priority::medium_low,
http_content_type content_type = http_content_type::json);
void add_api(const api_description& api, appbase::exec_queue q,
int priority = appbase::priority::medium_low,
http_content_type content_type = http_content_type::json) {
void add_handler(const string& url, const url_handler&, appbase::exec_queue q, int priority = appbase::priority::medium_low, http_content_type content_type = http_content_type::json);
void add_api(const api_description& api, appbase::exec_queue q, int priority = appbase::priority::medium_low, http_content_type content_type = http_content_type::json) {
for (const auto& call : api)
add_handler(call.first, call.second, q, priority, content_type);
}
+1 -1
View File
@@ -52,7 +52,7 @@ public:
cb(200, fc::time_point::maximum(), fc::variant(ok ? string("yes") : string("no")));
}
},
}, appbase::exec_queue::read_write);
}, appbase::exec_queue::general);
}
private:
+8 -2
View File
@@ -53,7 +53,11 @@ void net_api_plugin::plugin_startup() {
ilog("starting net_api_plugin");
// lifetime of plugin is lifetime of application
auto& net_mgr = app().get_plugin<net_plugin>();
app().get_plugin<http_plugin>().add_async_api({
app().get_plugin<http_plugin>().add_api({
// CALL(net, net_mgr, set_timeout,
// INVOKE_V_R(net_mgr, set_timeout, int64_t), 200),
// CALL(net, net_mgr, sign_transaction,
// INVOKE_R_R_R_R(net_mgr, sign_transaction, chain::signed_transaction, flat_set<public_key_type>, chain::chain_id_type), 201),
CALL_WITH_400(net, net_mgr, connect,
INVOKE_R_R(net_mgr, connect, std::string), 201),
CALL_WITH_400(net, net_mgr, disconnect,
@@ -62,7 +66,9 @@ void net_api_plugin::plugin_startup() {
INVOKE_R_R(net_mgr, status, std::string), 201),
CALL_WITH_400(net, net_mgr, connections,
INVOKE_R_V(net_mgr, connections), 201),
} );
// CALL(net, net_mgr, open,
// INVOKE_V_R(net_mgr, open, std::string), 200),
}, appbase::exec_queue::read_only_trx_safe, appbase::priority::medium_high);
}
void net_api_plugin::plugin_initialize(const variables_map& options) {
+1 -1
View File
@@ -3234,7 +3234,7 @@ namespace eosio {
my_impl->dispatcher->bcast_block( bsp->block, bsp->id );
}
app().executor().post(priority::medium, exec_queue::read_write, [ptr{std::move(ptr)}, bsp{std::move(bsp)}, id, c{std::move(c)}]() mutable {
app().executor().post(priority::medium, exec_queue::general, [ptr{std::move(ptr)}, bsp{std::move(bsp)}, id, c{std::move(c)}]() mutable {
c->process_signed_block( id, std::move(ptr), std::move(bsp) );
});
@@ -118,11 +118,9 @@ void producer_api_plugin::plugin_startup() {
INVOKE_R_R(producer, get_account_ram_corrections, producer_plugin::get_account_ram_corrections_params), 201),
CALL_WITH_400(producer, producer, get_unapplied_transactions,
INVOKE_R_R_D(producer, get_unapplied_transactions, producer_plugin::get_unapplied_transactions_params), 200),
CALL_WITH_400(producer, producer, get_snapshot_requests,
INVOKE_R_V(producer, get_snapshot_requests), 201),
}, appbase::exec_queue::read_only, appbase::priority::medium_high);
}, appbase::exec_queue::read_only_trx_safe, appbase::priority::medium_high);
// Not safe to run in parallel
// Not safe to run in parallel with read-only transactions
app().get_plugin<http_plugin>().add_api({
CALL_WITH_400(producer, producer, pause,
INVOKE_V_V(producer, pause), 201),
@@ -140,13 +138,15 @@ void producer_api_plugin::plugin_startup() {
INVOKE_R_V_ASYNC(producer, create_snapshot), 201),
CALL_WITH_400(producer, producer, schedule_snapshot,
INVOKE_V_R_II(producer, schedule_snapshot, producer_plugin::snapshot_request_information), 201),
CALL_WITH_400(producer, producer, get_snapshot_requests,
INVOKE_R_V(producer, get_snapshot_requests), 201),
CALL_WITH_400(producer, producer, unschedule_snapshot,
INVOKE_V_R(producer, unschedule_snapshot, producer_plugin::snapshot_request_id_information), 201),
CALL_WITH_400(producer, producer, get_integrity_hash,
INVOKE_R_V(producer, get_integrity_hash), 201),
CALL_WITH_400(producer, producer, schedule_protocol_feature_activations,
INVOKE_V_R(producer, schedule_protocol_feature_activations, producer_plugin::scheduled_protocol_feature_activations), 201),
}, appbase::exec_queue::read_write, appbase::priority::medium_high);
}, appbase::exec_queue::general, appbase::priority::medium_high);
}
void producer_api_plugin::plugin_initialize(const variables_map& options) {
+108 -152
View File
@@ -14,7 +14,6 @@
#include <fc/io/json.hpp>
#include <fc/log/logger_config.hpp>
#include <fc/scoped_exit.hpp>
#include <fc/time.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
@@ -325,9 +324,9 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
push_result push_transaction( const fc::time_point& block_deadline,
const transaction_metadata_ptr& trx,
bool api_trx, bool return_failure_trace,
const next_function<transaction_trace_ptr>& next );
next_function<transaction_trace_ptr> next );
push_result handle_push_result( const transaction_metadata_ptr& trx,
const next_function<transaction_trace_ptr>& next,
next_function<transaction_trace_ptr> next,
const fc::time_point& start,
const chain::controller& chain,
const transaction_trace_ptr& trace,
@@ -418,7 +417,7 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
packed_transaction_ptr trx;
next_func_t next;
};
// The queue storing previously exhausted read-only transactions to be re-executed by read-only threads
// The queue storing read-only transactions to be executed by read-only threads
struct ro_trx_queue_t {
std::mutex mtx;
std::deque<ro_trx_t> queue;
@@ -430,26 +429,24 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
fc::microseconds _ro_read_window_time_us{ 60000 };
static constexpr fc::microseconds _ro_read_window_minimum_time_us{ 10000 };
fc::microseconds _ro_read_window_effective_time_us{ 0 }; // calculated during option initialization
fc::time_point _ro_window_deadline; // only modified on app thread
boost::asio::deadline_timer _ro_write_window_timer;
boost::asio::deadline_timer _ro_read_window_timer;
fc::microseconds _ro_max_trx_time_us{ 0 }; // calculated during option initialization
ro_trx_queue_t _ro_exhausted_trx_queue;
std::atomic<uint32_t> _ro_num_active_exec_tasks{ 0 };
std::atomic<bool> _ro_exiting_read_window{ false };
bool _ro_in_read_only_mode{false}; // only modified on app thread
std::vector<std::future<bool>> _ro_exec_tasks_fut;
ro_trx_queue_t _ro_trx_queue;
std::atomic<uint32_t> _ro_num_active_trx_exec_tasks{ 0 };
std::vector<std::future<bool>> _ro_trx_exec_tasks_fut;
void start_write_window();
void switch_to_write_window();
void switch_to_read_window();
bool read_only_execution_task();
void process_read_only_transactions(const fc::time_point& deadline);
bool read_only_trx_execution_task();
bool process_read_only_transaction(const packed_transaction_ptr& trx,
const next_function<transaction_trace_ptr>& next);
const next_function<transaction_trace_ptr>& next,
const fc::time_point& read_window_start_time);
bool push_read_only_transaction(const transaction_metadata_ptr& trx,
const next_function<transaction_trace_ptr>& next);
next_function<transaction_trace_ptr> next,
const fc::time_point& read_window_start_time);
void consider_new_watermark( account_name producer, uint32_t block_num, block_timestamp_type timestamp) {
auto itr = _producer_watermarks.find( producer );
if( itr != _producer_watermarks.end() ) {
@@ -642,11 +639,12 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
transaction_metadata::trx_type trx_type,
bool return_failure_traces,
next_function<transaction_trace_ptr> next) {
if ( trx_type == transaction_metadata::trx_type::read_only ) {
// Post all read only trxs to read_only queue for execution.
app().executor().post(priority::low, exec_queue::read_only, [this, trx=trx, next{std::move(next)}]() mutable {
process_read_only_transaction( trx, next );
} );
if ( trx_type == transaction_metadata::trx_type::read_only && _ro_thread_pool_size > 0 ) {
// Parallel read-only trx execution enabled.
// Store the transaction in read-only-trx queue so that it is
// executed in read window
std::lock_guard<std::mutex> g( _ro_trx_queue.mtx );
_ro_trx_queue.queue.push_back({trx, std::move(next)});
return;
}
@@ -679,7 +677,7 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
next{std::move(next)}, trx=trx]() mutable {
if( future.valid() ) {
future.wait();
app().executor().post( priority::low, exec_queue::read_write, [self, future{std::move(future)}, api_trx, is_transient, next{std::move( next )}, trx{std::move(trx)}, return_failure_traces]() mutable {
app().executor().post( priority::low, exec_queue::general, [self, future{std::move(future)}, api_trx, is_transient, next{std::move( next )}, trx{std::move(trx)}, return_failure_traces]() mutable {
auto start = fc::time_point::now();
auto idle_time = start - self->_idle_trx_time;
self->_time_tracker.add_idle_time( idle_time );
@@ -711,7 +709,7 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
bool process_incoming_transaction_async(const transaction_metadata_ptr& trx,
bool api_trx,
bool return_failure_trace,
const next_function<transaction_trace_ptr>& next) {
next_function<transaction_trace_ptr> next) {
bool exhausted = false;
chain::controller& chain = chain_plug->chain();
try {
@@ -894,7 +892,7 @@ void producer_plugin::set_program_options(
"Number of worker threads in producer thread pool")
("snapshots-dir", bpo::value<bfs::path>()->default_value("snapshots"),
"the location of the snapshots directory (absolute path or relative to application data dir)")
("read-only-threads", bpo::value<uint16_t>(),
("read-only-threads", bpo::value<uint16_t>()->default_value(my->_ro_thread_pool_size),
"Number of worker threads in read-only transaction execution thread pool")
("read-only-write-window-time-us", bpo::value<uint32_t>()->default_value(my->_ro_write_window_time_us.count()),
"time in microseconds the write window lasts")
@@ -1075,15 +1073,11 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_
}
}
if ( options.count( "read-only-threads" ) ) {
my->_ro_thread_pool_size = options.at( "read-only-threads" ).as<uint16_t>();
EOS_ASSERT( my->_producers.empty(), plugin_config_exception, "--read-only-threads not allowed on producer node" );
} else if ( my->_producers.empty() ) {
// default to 3 threads for non producer nodes if not specified
my->_ro_thread_pool_size = 3;
}
my->_ro_thread_pool_size = options.at( "read-only-threads" ).as<uint16_t>();
// only initialize other read-only options when read-only thread pool is enabled
if ( my->_ro_thread_pool_size > 0 ) {
EOS_ASSERT( my->_producers.empty(), plugin_config_exception, "--read-only-threads not allowed on producer node" );
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
if (chain.is_eos_vm_oc_enabled()) {
// EOS VM OC requires 4.2TB Virtual for each executing thread. Make sure the memory
@@ -1115,26 +1109,25 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_
}
}
#endif
}
my->_ro_write_window_time_us = fc::microseconds( options.at( "read-only-write-window-time-us" ).as<uint32_t>() );
my->_ro_read_window_time_us = fc::microseconds( options.at( "read-only-read-window-time-us" ).as<uint32_t>() );
EOS_ASSERT( my->_ro_read_window_time_us > my->_ro_read_window_minimum_time_us, plugin_config_exception, "minimum of --read-only-read-window-time-us (${read}) must be ${min} microseconds", ("read", my->_ro_read_window_time_us) ("min", my->_ro_read_window_minimum_time_us) );
my->_ro_read_window_effective_time_us = my->_ro_read_window_time_us - my->_ro_read_window_minimum_time_us;
my->_ro_write_window_time_us = fc::microseconds( options.at( "read-only-write-window-time-us" ).as<uint32_t>() );
my->_ro_read_window_time_us = fc::microseconds( options.at( "read-only-read-window-time-us" ).as<uint32_t>() );
EOS_ASSERT( my->_ro_read_window_time_us > my->_ro_read_window_minimum_time_us, plugin_config_exception, "minimum of --read-only-read-window-time-us (${read}) must be ${min} microseconds", ("read", my->_ro_read_window_time_us) ("min", my->_ro_read_window_minimum_time_us) );
my->_ro_read_window_effective_time_us = my->_ro_read_window_time_us - my->_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.
// Use _ro_read_window_effective_time_us instead of _ro_read_window_time_us
// for safety marging
if ( my->_max_transaction_time_ms.load() > 0 ) {
EOS_ASSERT( my->_ro_read_window_effective_time_us > fc::milliseconds(my->_max_transaction_time_ms.load()), plugin_config_exception, "--read-only-read-window-time-us (${read}) must be greater than --max-transaction-time ${trx_time} ms plus a margin of ${min} us", ("read", my->_ro_read_window_time_us) ("trx_time", my->_max_transaction_time_ms.load()) ("min", my->_ro_read_window_minimum_time_us) );
my->_ro_max_trx_time_us = fc::milliseconds(my->_max_transaction_time_ms.load());
} else {
// _max_transaction_time_ms can be set to negative in testing (for unlimited)
my->_ro_max_trx_time_us = my->_ro_read_window_effective_time_us;
// Make sure a read-only transaction can finish within the read
// window if scheduled at the very beginning of the window.
// Use _ro_read_window_effective_time_us instead of _ro_read_window_time_us
// for safety marging
if ( my->_max_transaction_time_ms.load() > 0 ) {
EOS_ASSERT( my->_ro_read_window_effective_time_us > fc::milliseconds(my->_max_transaction_time_ms.load()), plugin_config_exception, "--read-only-read-window-time-us (${read}) must be greater than --max-transaction-time ${trx_time} ms plus a margin of ${min} us", ("read", my->_ro_read_window_time_us) ("trx_time", my->_max_transaction_time_ms.load()) ("min", my->_ro_read_window_minimum_time_us) );
my->_ro_max_trx_time_us = fc::milliseconds(my->_max_transaction_time_ms.load());
} else {
// _max_transaction_time_ms can be set to negative in testing (for unlimited)
my->_ro_max_trx_time_us = my->_ro_read_window_effective_time_us;
}
ilog("_ro_thread_pool_size ${s}, _ro_write_window_time_us ${ww}, _ro_read_window_time_us ${rw}, _ro_max_trx_time_us ${t}, _ro_read_window_effective_time_us ${w}", ("s", my->_ro_thread_pool_size) ("ww", my->_ro_write_window_time_us) ("rw", my->_ro_read_window_time_us) ("t", my->_ro_max_trx_time_us) ("w", my->_ro_read_window_effective_time_us));
}
ilog("ro_thread_pool_size ${s}, ro_write_window_time_us ${ww}, ro_read_window_time_us ${rw}, ro_max_trx_time_us ${t}, ro_read_window_effective_time_us ${w}",
("s", my->_ro_thread_pool_size)("ww", my->_ro_write_window_time_us)("rw", my->_ro_read_window_time_us)("t", my->_ro_max_trx_time_us)("w", my->_ro_read_window_effective_time_us));
my->_incoming_block_sync_provider = app().get_method<incoming::methods::block_sync>().register_provider(
[this](const signed_block_ptr& block, const std::optional<block_id_type>& block_id, const block_state_ptr& bsp) {
@@ -2001,8 +1994,6 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() {
process_scheduled_and_incoming_trxs( scheduled_trx_deadline, incoming_itr );
}
process_read_only_transactions( preprocess_deadline );
if( app().is_quiting() ) // db guard exception above in LOG_AND_DROP could have called app().quit()
return start_block_result::failed;
if ( should_interrupt_start_block( preprocess_deadline ) || block_is_exhausted() ) {
@@ -2204,10 +2195,9 @@ producer_plugin_impl::push_transaction( const fc::time_point& block_deadline,
const transaction_metadata_ptr& trx,
bool api_trx,
bool return_failure_trace,
const next_function<transaction_trace_ptr>& next )
next_function<transaction_trace_ptr> next )
{
auto start = fc::time_point::now();
EOS_ASSERT(!trx->is_read_only(), producer_exception, "Unexpected read-only trx");
bool disable_subjective_enforcement = (api_trx && _disable_subjective_api_billing)
|| (!api_trx && _disable_subjective_p2p_billing)
@@ -2244,6 +2234,14 @@ producer_plugin_impl::push_transaction( const fc::time_point& block_deadline,
}
}
// Make sure db_read_only_mode always to be unset
auto db_read_only_mode_guard = fc::make_scoped_exit([trx, &chain]{
if( trx->is_read_only() )
chain.unset_db_read_only_mode();
});
if( trx->is_read_only() )
chain.set_db_read_only_mode();
auto trace = chain.push_transaction( trx, block_deadline, max_trx_time, prev_billed_cpu_time_us, false, sub_bill );
return handle_push_result(trx, next, start, chain, trace, return_failure_trace, disable_subjective_enforcement, first_auth, sub_bill, prev_billed_cpu_time_us);
@@ -2251,7 +2249,7 @@ producer_plugin_impl::push_transaction( const fc::time_point& block_deadline,
producer_plugin_impl::push_result
producer_plugin_impl::handle_push_result( const transaction_metadata_ptr& trx,
const next_function<transaction_trace_ptr>& next,
next_function<transaction_trace_ptr> next,
const fc::time_point& start,
const chain::controller& chain,
const transaction_trace_ptr& trace,
@@ -2557,7 +2555,7 @@ void producer_plugin_impl::schedule_production_loop() {
_timer.expires_from_now( boost::posix_time::microseconds( config::block_interval_us / 10 ));
// we failed to start a block, so try again later?
_timer.async_wait( app().executor().wrap( priority::high, exec_queue::read_write,
_timer.async_wait( app().executor().wrap( priority::high, exec_queue::general,
[weak_this = weak_from_this(), cid = ++_timer_corelation_id]( const boost::system::error_code& ec ) {
auto self = weak_this.lock();
if( self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id ) {
@@ -2610,7 +2608,7 @@ void producer_plugin_impl::schedule_maybe_produce_block( bool exhausted ) {
("num", chain.head_block_num() + 1)("desc", block_is_exhausted() ? "Exhausted" : "Deadline exceeded") );
}
_timer.async_wait( app().executor().wrap( priority::high, exec_queue::read_write,
_timer.async_wait( app().executor().wrap( priority::high, exec_queue::general,
[&chain, weak_this = weak_from_this(), cid=++_timer_corelation_id](const boost::system::error_code& ec) {
auto self = weak_this.lock();
if( self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id ) {
@@ -2652,7 +2650,7 @@ void producer_plugin_impl::schedule_delayed_production_loop(const std::weak_ptr<
fc_dlog(_log, "Scheduling Speculative/Production Change at ${time}", ("time", wake_up_time));
static const boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
_timer.expires_at(epoch + boost::posix_time::microseconds(wake_up_time->time_since_epoch().count()));
_timer.async_wait( app().executor().wrap( priority::high, exec_queue::read_write,
_timer.async_wait( app().executor().wrap( priority::high, exec_queue::general,
[weak_this,cid=++_timer_corelation_id](const boost::system::error_code& ec) {
auto self = weak_this.lock();
if( self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id ) {
@@ -2766,7 +2764,7 @@ void producer_plugin::log_failed_transaction(const transaction_id_type& trx_id,
("entire_trx", packed_trx_ptr ? my->chain_plug->get_log_trx(packed_trx_ptr->get_transaction()) : fc::variant{trx_id}));
}
// Called from only one read_only thread
// Called from app thread
void producer_plugin_impl::switch_to_write_window() {
// this method can be called from multiple places. it is possible
// we are already in write window.
@@ -2774,29 +2772,26 @@ void producer_plugin_impl::switch_to_write_window() {
return;
}
EOS_ASSERT( _ro_num_active_exec_tasks.load() == 0 && _ro_exec_tasks_fut.empty(), producer_exception, "no read-only tasks should be running before switching to write window");
EOS_ASSERT(_ro_num_active_trx_exec_tasks.load() == 0 && _ro_trx_exec_tasks_fut.empty(), producer_exception, "no read-only tasks should be running before switching to write window");
_ro_read_window_timer.cancel();
_ro_write_window_timer.cancel();
start_write_window();
}
// Called from app thread on plugin_startup
// Called from only one read_only thread & called from app thread, but not concurrently
// Called from app thread
void producer_plugin_impl::start_write_window() {
chain::controller& chain = chain_plug->chain();
app().executor().set_to_write_window();
chain.unset_db_read_only_mode();
_ro_in_read_only_mode = false;
_idle_trx_time = fc::time_point::now();
_ro_window_deadline = _idle_trx_time + _ro_write_window_time_us; // not allowed on block producers, so no need to limit to block deadline
auto expire_time = boost::posix_time::microseconds(_ro_write_window_time_us.count());
_ro_write_window_timer.expires_from_now( expire_time );
_ro_write_window_timer.async_wait( app().executor().wrap( // run from app thread
_ro_write_window_timer.async_wait( app().executor().wrap( // stay on app thread
priority::high,
exec_queue::read_write, // placed in read_write so only called from main thread
exec_queue::read_only_trx_safe, // placed in read_only_trx_safe queue so it is ensured to be executed in either window
[weak_this = weak_from_this()]( const boost::system::error_code& ec ) {
auto self = weak_this.lock();
if( self && ec != boost::asio::error::operation_aborted ) {
@@ -2805,98 +2800,86 @@ void producer_plugin_impl::start_write_window() {
}));
}
// Called only from app thread
// Called from app thread
void producer_plugin_impl::switch_to_read_window() {
EOS_ASSERT(app().executor().is_write_window(), producer_exception, "expected to be in write window");
EOS_ASSERT( _ro_num_active_exec_tasks.load() == 0 && _ro_exec_tasks_fut.empty(), producer_exception, "_ro_exec_tasks_fut expected to be empty" );
EOS_ASSERT(_ro_num_active_trx_exec_tasks.load() == 0 && _ro_trx_exec_tasks_fut.empty(), producer_exception, "_ro_trx_exec_tasks_fut expected to be empty" );
_ro_write_window_timer.cancel();
_ro_read_window_timer.cancel();
_time_tracker.add_idle_time( fc::time_point::now() - _idle_trx_time );
// we are in write window, so no read-only trx threads are processing transactions.
// _ro_exhausted_trx_queue is not being accessed by read-only threads, no need to lock.
if ( _ro_exhausted_trx_queue.queue.empty() && app().executor().read_only_queue().empty() ) { // no read-only trxs to process. stay in write window
// _ro_trx_queue is not being accessed. No need to lock.
if ( _ro_trx_queue.queue.empty() ) { // no read-only trxs to process. stay in write window
start_write_window(); // restart write window timer for next round
return;
}
app().executor().set_to_read_window();
chain_plug->chain().set_db_read_only_mode();
_ro_in_read_only_mode = true;
_received_block = false;
_ro_exiting_read_window = false;
// start a read-only transaction execution task in each thread in the thread pool
_ro_num_active_exec_tasks = _ro_thread_pool_size;
_ro_window_deadline = fc::time_point::now() + _ro_read_window_effective_time_us;
_ro_num_active_trx_exec_tasks = _ro_thread_pool_size;
for (auto i = 0; i < _ro_thread_pool_size; ++i ) {
_ro_exec_tasks_fut.emplace_back( post_async_task( _ro_thread_pool.get_executor(), [self = this] () {
return self->read_only_execution_task();
_ro_trx_exec_tasks_fut.emplace_back( post_async_task( _ro_thread_pool.get_executor(), [self = this] () {
return self->read_only_trx_execution_task();
}) );
}
auto expire_time = boost::posix_time::microseconds(_ro_read_window_time_us.count());
_ro_read_window_timer.expires_from_now( expire_time );
// Needs to be on read_only because that is what is being processed until switch_to_write_window().
_ro_read_window_timer.async_wait( app().executor().wrap(
_ro_read_window_timer.async_wait( app().executor().wrap( // stay on app thread
priority::high,
exec_queue::read_only,
exec_queue::read_only_trx_safe,
[weak_this = weak_from_this()]( const boost::system::error_code& ec ) {
auto self = weak_this.lock();
if( self && ec != boost::asio::error::operation_aborted ) {
// use future to make sure all read-only tasks finished before switching to write window
for ( auto& task: self->_ro_exec_tasks_fut ) {
for ( auto& task: self->_ro_trx_exec_tasks_fut ) {
task.get();
}
self->_ro_exec_tasks_fut.clear();
// will be executed from the main app thread because all read-only threads are idle now
self->_ro_trx_exec_tasks_fut.clear();
self->switch_to_write_window();
} else if ( self ) {
self->_ro_exec_tasks_fut.clear();
self->_ro_trx_exec_tasks_fut.clear();
}
}));
}
// Called from a read only thread. Run in parallel with app and other read only threads
bool producer_plugin_impl::read_only_execution_task() {
// Called from a read only trx thread. Run in parallel with app and other read only trx threads
bool producer_plugin_impl::read_only_trx_execution_task() {
auto start = fc::time_point::now();
auto read_window_deadline = start + _ro_read_window_effective_time_us;
// We have 4 ways to break out the while loop:
// 1. pass read window deadline
// 2. net_plugin receives a block
// 3. No more transactions in the exhausted read-only queue and no read-only tasks to execute
// 4. A transaction execution is exhausted (means end of read window)
bool exhausted_trx_queue_empty = false;
while ( fc::time_point::now() < _ro_window_deadline && !_received_block && !_ro_exiting_read_window ) {
if (!exhausted_trx_queue_empty) {
std::unique_lock<std::mutex> lck( _ro_exhausted_trx_queue.mtx );
auto size = _ro_exhausted_trx_queue.queue.size();
if ( size > 0 ) { // execute exhausted read-only first
auto trx = _ro_exhausted_trx_queue.queue.front();
_ro_exhausted_trx_queue.queue.pop_front();
lck.unlock();
auto retry = process_read_only_transaction( trx.trx, trx.next );
if( retry ) {
_ro_exiting_read_window = true;
break;
}
}
exhausted_trx_queue_empty = (size <= 1); // one was executed above
} else {
bool more = app().executor().execute_highest_read_only();
if ( !more ) { // read_only queue empty, anything queued after this will be processed in the next window
_ro_exiting_read_window = true;
break;
}
// 2. Net_plugin receives a block
// 3. No more transactions in the read-only trx queue
// 4. A transaction execution is exhaused
while ( fc::time_point::now() < read_window_deadline && !_received_block ) {
std::unique_lock<std::mutex> lck( _ro_trx_queue.mtx );
if ( _ro_trx_queue.queue.empty() ) {
break;
}
auto trx = _ro_trx_queue.queue.front();
_ro_trx_queue.queue.pop_front();
lck.unlock();
auto retry = process_read_only_transaction( trx.trx, trx.next, start );
if ( retry ) {
lck.lock();
_ro_trx_queue.queue.push_front(trx);
// Do not schedule new execution
break;
}
}
// If all tasks are finished, do not wait until end of read window; switch to write window now.
if ( --_ro_num_active_exec_tasks == 0 ) {
// Needs to be on read_only because that is what is being processed until switch_to_write_window().
app().executor().post( priority::high, exec_queue::read_only, [self=this]() {
self->_ro_exec_tasks_fut.clear();
// will be executed from the main app thread because all read-only threads are idle now
if ( --_ro_num_active_trx_exec_tasks == 0 ) {
// Do switching on app thread to serialize
app().executor().post( priority::high, exec_queue::read_only_trx_safe, [self=this]() {
self->_ro_trx_exec_tasks_fut.clear();
self->switch_to_write_window();
} );
}
@@ -2904,28 +2887,9 @@ bool producer_plugin_impl::read_only_execution_task() {
return true;
}
// Called from app thread during start block.
// Used for executing exhausted read-only trxs when no read only thread pool enabled.
void producer_plugin_impl::process_read_only_transactions(const fc::time_point& deadline) {
// if the read only thread pool is enabled then exhausted read only transactions will be executed
// by read_only_execution_task, so don't execute them here.
if ( _ro_thread_pool_size == 0 ) {
_ro_window_deadline = std::min(deadline, fc::time_point::now() + _ro_read_window_effective_time_us);
// only app thread accessing _ro_exhausted_trx_queue since _ro_thread_pool_size == 0, no lock needed
while ( !_ro_exhausted_trx_queue.queue.empty() && fc::time_point::now() < _ro_window_deadline && !_received_block ) {
auto trx = _ro_exhausted_trx_queue.queue.front();
_ro_exhausted_trx_queue.queue.pop_front();
auto retry = process_read_only_transaction( trx.trx, trx.next );
if( retry )
break;
}
}
}
// Called from a read only trx thread. Run in parallel with app and other read only trx threads
// Return whether the trx needs to be retried in next read window
bool producer_plugin_impl::process_read_only_transaction(const packed_transaction_ptr& trx, const next_function<transaction_trace_ptr>& next) {
bool producer_plugin_impl::process_read_only_transaction(const packed_transaction_ptr& trx, const next_function<transaction_trace_ptr>& next, const fc::time_point& read_window_start_time) {
chain::controller& chain = chain_plug->chain();
auto future = transaction_metadata::start_recover_keys( trx, _thread_pool.get_executor(),
chain.get_chain_id(), fc::microseconds::maximum(),
@@ -2938,19 +2902,17 @@ bool producer_plugin_impl::process_read_only_transaction(const packed_transactio
future.wait();
try {
auto trx_metadata = future.get();
bool retry = push_read_only_transaction( trx_metadata, next );
if( retry ) {
std::lock_guard g( _ro_exhausted_trx_queue.mtx );
_ro_exhausted_trx_queue.queue.push_front( {trx, next} );
}
return retry;
return push_read_only_transaction( trx_metadata, next, read_window_start_time );
} CATCH_AND_CALL(exception_handler);
return false;
}
// Called from a read_only_trx execution thread
// Return whether the trx needs to be retried in next read window
bool producer_plugin_impl::push_read_only_transaction(const transaction_metadata_ptr& trx, const next_function<transaction_trace_ptr>& next) {
bool producer_plugin_impl::push_read_only_transaction(
const transaction_metadata_ptr& trx,
next_function<transaction_trace_ptr> next,
const fc::time_point& read_window_start_time) {
auto retry = false;
chain::controller& chain = chain_plug->chain();
@@ -2960,24 +2922,18 @@ bool producer_plugin_impl::push_read_only_transaction(const transaction_metadata
}
try {
const auto block_deadline = calculate_block_deadline( chain.pending_block_time() );
auto start = fc::time_point::now();
if ( start >= _ro_window_deadline ) {
auto time_used_in_read_window_us = ( start - read_window_start_time );
if ( time_used_in_read_window_us >= _ro_read_window_time_us ) {
// already passed read-window deadline, try next round
return true;
}
// when executing on the main thread while in the write window, need to switch db mode to read only
// _ro_in_read_only_mode can only be false if running on main thread as it is only modified from the main thread
const bool switch_read_mode = !_ro_in_read_only_mode;
auto db_read_only_mode_guard = fc::make_scoped_exit([&]{
if( switch_read_mode )
chain.unset_db_read_only_mode();
});
if( switch_read_mode )
chain.set_db_read_only_mode();
auto remaining_time_in_read_window_us = _ro_read_window_time_us - time_used_in_read_window_us;
// Ensure the trx to finish by the end of read-window.
auto trace = chain.push_transaction( trx, _ro_window_deadline, _ro_max_trx_time_us, 0, false, 0 );
auto window_deadline = std::min( start + remaining_time_in_read_window_us, block_deadline );
auto trace = chain.push_transaction( trx, window_deadline, _ro_max_trx_time_us, 0, false, 0 );
auto pr = handle_push_result(trx, next, start, chain, trace, true /*return_failure_trace*/, true /*disable_subjective_enforcement*/, {} /*first_auth*/, 0 /*sub_bill*/, 0 /*prev_billed_cpu_time_us*/);
// If a transaction was exhausted, that indicates we are close to
// the end of read window. Retry in next round.
@@ -103,7 +103,7 @@ void test_trxs_common(std::vector<const char*>& specific_args) {
for( size_t i = 1; i <= num_pushes; ++i ) {
auto ptrx = make_unique_trx( chain_id );
app->executor().post( priority::low, exec_queue::read_write, [ptrx, &next_calls, &num_posts, &trace_with_except, &trx_match, &app]() {
app->executor().post( priority::low, exec_queue::general, [ptrx, &next_calls, &num_posts, &trace_with_except, &trx_match, &app]() {
++num_posts;
bool return_failure_traces = true;
app->get_method<plugin_interface::incoming::methods::transaction_async>()(ptrx,
@@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE(snapshot_scheduler_test) {
fc::logger::get(DEFAULT_LOGGER).set_log_level(fc::log_level::debug);
std::vector<const char*> argv =
{"test", "--data-dir", temp.c_str(), "--config-dir", temp.c_str(),
"-p", "eosio", "-e", "--disable-subjective-billing=true"};
"-p", "eosio", "-e", "--max-transaction-time", "475", "--disable-subjective-billing=true"};
appbase::app().initialize<chain_plugin, producer_plugin>(argv.size(), (char**) &argv[0]);
appbase::app().startup();
plugin_promise.set_value(
@@ -112,7 +112,7 @@ BOOST_AUTO_TEST_CASE(producer) {
fc::logger::get(DEFAULT_LOGGER).set_log_level(fc::log_level::debug);
std::vector<const char*> argv =
{"test", "--data-dir", temp_dir_str.c_str(), "--config-dir", temp_dir_str.c_str(),
"-p", "eosio", "-e", "--disable-subjective-billing=true" };
"-p", "eosio", "-e", "--max-transaction-time", "475", "--disable-subjective-billing=true" };
app->initialize<chain_plugin, producer_plugin>( argv.size(), (char**) &argv[0] );
app->startup();
plugin_promise.set_value(
@@ -51,7 +51,7 @@ void test_control_api_plugin::plugin_startup() {
app().get_plugin<http_plugin>().add_api({
TEST_CONTROL_RW_CALL(kill_node_on_producer, 202, http_params_types::params_required)
}, appbase::exec_queue::read_write);
}, appbase::exec_queue::general);
}
void test_control_api_plugin::plugin_shutdown() {}
@@ -115,7 +115,7 @@ void wallet_api_plugin::plugin_startup() {
INVOKE_R_R_R(wallet_mgr, list_keys, std::string, std::string), 200),
CALL_WITH_400(wallet, wallet_mgr, get_public_keys,
INVOKE_R_V(wallet_mgr, get_public_keys), 200)
}, appbase::exec_queue::read_write);
}, appbase::exec_queue::general);
}
void wallet_api_plugin::plugin_initialize(const variables_map& options) {
+1 -1
View File
@@ -111,7 +111,7 @@ int main(int argc, char** argv)
[&a=app](string, string, url_response_callback cb) {
cb(200, fc::time_point::maximum(), fc::variant(fc::variant_object()));
a->quit();
}, appbase::exec_queue::read_write );
}, appbase::exec_queue::general );
app->startup();
app->exec();
} catch (const fc::exception& e) {
+3 -3
View File
@@ -125,11 +125,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 --clean-run ${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 --clean-run ${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 --clean-run ${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 3 --num-test-runs 3 --clean-run ${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 3 --clean-run ${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 --read-only-threads 3 --num-test-runs 3 --clean-run ${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 --read-only-threads 3 --clean-run ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST read-only-trx-parallel-eos-vm-oc-test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME subjective_billing_test COMMAND tests/subjective_billing_test.py -v -p 2 -n 4 --clean-run ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST subjective_billing_test PROPERTY LABELS nonparallelizable_tests)
+4 -1
View File
@@ -1727,4 +1727,7 @@ class Cluster(object):
for line in f:
firstTrxs.append(line.rstrip('\n'))
Utils.Print(f"first transactions: {firstTrxs}")
node.waitForTransactionsInBlock(firstTrxs)
status = node.waitForTransactionsInBlock(firstTrxs)
if status is None:
Utils.Print('ERROR: Failed to spin up transaction generators: never received first transactions')
return status
+11 -10
View File
@@ -118,10 +118,10 @@ class NodeosQueries:
# could be a transaction response
if cntxt.hasKey("processed"):
cntxt.add("processed")
cntxt.add("action_traces")
cntxt.index(0)
if not cntxt.isSectionNull("except"):
return "no_block"
cntxt.add("action_traces")
cntxt.index(0)
return cntxt.add("block_num")
# or what the trace api plugin returns
@@ -242,7 +242,7 @@ class NodeosQueries:
assert(isinstance(transId, str))
exitOnErrorForDelayed=not delayedRetry and exitOnError
timeout=3
cmdDesc="get transaction_trace"
cmdDesc=self.fetchTransactionCommand()
cmd="%s %s" % (cmdDesc, transId)
msg="(transaction id=%s)" % (transId);
for i in range(0,(int(60/timeout) - 1)):
@@ -295,8 +295,8 @@ class NodeosQueries:
refBlockNum=None
key=""
try:
key="[transaction][transaction_header][ref_block_num]"
refBlockNum=trans["transaction_header"]["ref_block_num"]
key = self.fetchKeyCommand()
refBlockNum = self.fetchRefBlock(trans)
refBlockNum=int(refBlockNum)+1
except (TypeError, ValueError, KeyError) as _:
Utils.Print("transaction%s not found. Transaction: %s" % (key, trans))
@@ -347,8 +347,8 @@ class NodeosQueries:
def getTable(self, contract, scope, table, exitOnError=False):
cmdDesc = "get table"
cmd="%s %s %s %s" % (cmdDesc, contract, scope, table)
msg="contract=%s, scope=%s, table=%s" % (contract, scope, table);
cmd=f"{cmdDesc} {self.cleosLimit} {contract} {scope} {table}"
msg=f"contract={contract}, scope={scope}, table={table}"
return self.processCleosCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg)
def getTableAccountBalance(self, contract, scope):
@@ -529,7 +529,7 @@ class NodeosQueries:
return m.group(1)
except subprocess.CalledProcessError as ex:
end=time.perf_counter()
msg=ex.output.decode("utf-8")
msg=ex.stderr.decode("utf-8")
Utils.Print("ERROR: Exception during code hash retrieval. cmd Duration: %.3f sec. %s" % (end-start, msg))
return None
@@ -580,8 +580,9 @@ class NodeosQueries:
except subprocess.CalledProcessError as ex:
if not silentErrors:
end=time.perf_counter()
msg=ex.output.decode("utf-8")
errorMsg="Exception during \"%s\". Exception message: %s. cmd Duration=%.3f sec. %s" % (cmdDesc, msg, end-start, exitMsg)
out=ex.output.decode("utf-8")
msg=ex.stderr.decode("utf-8")
errorMsg="Exception during \"%s\". Exception message: %s. stdout: %s. cmd Duration=%.3f sec. %s" % (cmdDesc, msg, out, end-start, exitMsg)
if exitOnError:
Utils.cmdError(errorMsg)
Utils.errorExit(errorMsg)
+6 -7
View File
@@ -107,7 +107,7 @@ class Transactions(NodeosQueries):
self.trackCmdTransaction(trans, reportStatus=reportStatus)
except subprocess.CalledProcessError as ex:
end=time.perf_counter()
msg=ex.output.decode("utf-8")
msg=ex.stderr.decode("utf-8")
Utils.Print("ERROR: Exception during funds transfer. cmd Duration: %.3f sec. %s" % (end-start, msg))
if exitOnError:
Utils.cmdError("could not transfer \"%s\" from %s to %s" % (amountStr, source, destination))
@@ -131,7 +131,7 @@ class Transactions(NodeosQueries):
Utils.Print("cmd Duration: %.3f sec" % (end-start))
except subprocess.CalledProcessError as ex:
end=time.perf_counter()
msg=ex.output.decode("utf-8")
msg=ex.stderr.decode("utf-8")
Utils.Print("ERROR: Exception during spawn of funds transfer. cmd Duration: %.3f sec. %s" % (end-start, msg))
if exitOnError:
Utils.cmdError("could not transfer \"%s\" from %s to %s" % (amountStr, source, destination))
@@ -158,15 +158,15 @@ class Transactions(NodeosQueries):
except subprocess.CalledProcessError as ex:
if not shouldFail:
end=time.perf_counter()
msg=ex.output.decode("utf-8")
Utils.Print("ERROR: Exception during set contract. cmd Duration: %.3f sec. %s" % (end-start, msg))
out=ex.output.decode("utf-8")
msg=ex.stderr.decode("utf-8")
Utils.Print("ERROR: Exception during set contract. stderr: %s. stdout: %s. cmd Duration: %.3f sec." % (msg, out, end-start))
return None
else:
retMap={}
retMap["returncode"]=ex.returncode
retMap["cmd"]=ex.cmd
retMap["output"]=ex.output
retMap["stdout"]=ex.stdout
retMap["stderr"]=ex.stderr
return retMap
@@ -213,7 +213,7 @@ class Transactions(NodeosQueries):
Utils.Print("cmd Duration: %.3f sec" % (end-start))
return (NodeosQueries.getTransStatus(retTrans) == 'executed', retTrans)
except subprocess.CalledProcessError as ex:
msg=ex.output.decode("utf-8")
msg=ex.stderr.decode("utf-8")
if not silentErrors:
end=time.perf_counter()
Utils.Print("ERROR: Exception during push transaction. cmd Duration=%.3f sec. %s" % (end - start, msg))
@@ -245,7 +245,6 @@ class Transactions(NodeosQueries):
except subprocess.CalledProcessError as ex:
msg=ex.stderr.decode("utf-8")
output=ex.output.decode("utf-8")
msg=ex.output.decode("utf-8")
if not silentErrors:
end=time.perf_counter()
Utils.Print("ERROR: Exception during push message. stderr: %s. stdout: %s. cmd Duration=%.3f sec." % (msg, output, end - start))
+27 -15
View File
@@ -126,13 +126,15 @@ try:
waitForBlock(node0, blockNum, blockType=BlockType.lib)
Print("Configure and launch txn generators")
targetTpsPerGenerator = 10
testTrxGenDurationSec=60*30
cluster.launchTrxGenerators(contractOwnerAcctName=cluster.eosioAccount.name, acctNamesList=[account1Name, account2Name],
acctPrivKeysList=[account1PrivKey,account2PrivKey], nodeId=snapshotNodeId, tpsPerGenerator=targetTpsPerGenerator,
numGenerators=trxGeneratorCnt, durationSec=testTrxGenDurationSec, waitToComplete=False)
cluster.waitForTrxGeneratorsSpinup(nodeId=snapshotNodeId, numGenerators=trxGeneratorCnt)
status = cluster.waitForTrxGeneratorsSpinup(nodeId=snapshotNodeId, numGenerators=trxGeneratorCnt)
assert status is not None, "ERROR: Failed to spinup Transaction Generators"
blockNum=node0.getBlockNum(BlockType.head)
timePerBlock=500
@@ -156,12 +158,8 @@ try:
minReqPctLeeway=0.60
minRequiredTransactions=minReqPctLeeway*transactionsPerBlock
assert steadyStateAvg>=minRequiredTransactions, "Expected to at least receive %s transactions per block, but only getting %s" % (minRequiredTransactions, steadyStateAvg)
Print("Create snapshot")
blockNumSnap=nodeSnap.getBlockNum(BlockType.head)
ret = nodeProg.scheduleSnapshotAt(blockNumSnap)
assert ret is not None, "Snapshot creation failed"
Print("Create snapshot (node 0)")
ret = nodeSnap.createSnapshot()
assert ret is not None, "Snapshot creation failed"
ret_head_block_num = ret["payload"]["head_block_num"]
@@ -178,6 +176,29 @@ try:
Utils.processLeapUtilCmd("snapshot to-json --input-file {}".format(snapshotFile), "snapshot to-json", silentErrors=False)
snapshotFile = snapshotFile + ".json"
Print("Trim programmable blocklog to snapshot head block num and relaunch programmable node")
nodeProg.kill(signal.SIGTERM)
output=cluster.getBlockLog(progNodeId, blockLogAction=BlockLogAction.trim, first=0, last=ret_head_block_num, throwException=True)
removeState(progNodeId)
Utils.rmFromFile(Utils.getNodeConfigDir(progNodeId, "config.ini"), "p2p-peer-address")
isRelaunchSuccess = nodeProg.relaunch(chainArg="--replay", addSwapFlags={}, timeout=relaunchTimeout, cachePopen=True)
assert isRelaunchSuccess, "Failed to relaunch programmable node"
Print("Schedule snapshot (node 2)")
ret = nodeProg.scheduleSnapshotAt(ret_head_block_num)
assert ret is not None, "Snapshot scheduling failed"
Print("Wait for programmable node lib to advance")
waitForBlock(nodeProg, ret_head_block_num+1, blockType=BlockType.lib)
Print("Kill programmable node")
nodeProg.kill(signal.SIGTERM)
Print("Convert snapshot to JSON")
progSnapshotFile = getLatestSnapshot(progNodeId)
Utils.processLeapUtilCmd("snapshot to-json --input-file {}".format(progSnapshotFile), "snapshot to-json", silentErrors=False)
progSnapshotFile = progSnapshotFile + ".json"
Print("Trim irreversible blocklog to snapshot head block num")
nodeIrr.kill(signal.SIGTERM)
output=cluster.getBlockLog(irrNodeId, blockLogAction=BlockLogAction.trim, first=0, last=ret_head_block_num, throwException=True)
@@ -205,15 +226,6 @@ try:
irrSnapshotFile = irrSnapshotFile + ".json"
assert Utils.compareFiles(snapshotFile, irrSnapshotFile), f"Snapshot files differ {snapshotFile} != {irrSnapshotFile}"
Print("Kill programmable node")
nodeProg.kill(signal.SIGTERM)
Print("Convert snapshot to JSON")
progSnapshotFile = getLatestSnapshot(progNodeId)
Utils.processLeapUtilCmd("snapshot to-json --input-file {}".format(progSnapshotFile), "snapshot to-json", silentErrors=False)
progSnapshotFile = progSnapshotFile + ".json"
assert Utils.compareFiles(progSnapshotFile, irrSnapshotFile), f"Snapshot files differ {progSnapshotFile} != {irrSnapshotFile}"
testSuccessful=True
+2 -1
View File
@@ -118,7 +118,8 @@ try:
tpsPerGenerator=targetTpsPerGenerator, numGenerators=trxGeneratorCnt, durationSec=testTrxGenDurationSec,
waitToComplete=False)
cluster.waitForTrxGeneratorsSpinup(nodeId=node0.nodeId, numGenerators=trxGeneratorCnt)
status = cluster.waitForTrxGeneratorsSpinup(nodeId=node0.nodeId, numGenerators=trxGeneratorCnt)
assert status is not None, "ERROR: Failed to spinup Transaction Generators"
blockNum=head(node0)
timePerBlock=500
+8 -10
View File
@@ -21,7 +21,6 @@ errorExit=Utils.errorExit
appArgs=AppArgs()
appArgs.add(flag="--read-only-threads", type=int, help="number of read-only threads", default=0)
appArgs.add(flag="--num-test-runs", type=int, help="number of times to run the tests", default=1)
appArgs.add_bool(flag="--eos-vm-oc-enable", help="enable eos-vm-oc")
appArgs.add(flag="--wasm-runtime", type=str, help="if set to eos-vm-oc, must compile with EOSIO_EOS_VM_OC_DEVELOPER", default="eos-vm-jit")
@@ -45,7 +44,6 @@ dontKill=args.leave_running
dumpErrorDetails=args.dump_error_details
killAll=args.clean_run
keepLogs=args.keep_logs
numTestRuns=args.num_test_runs
killWallet=not dontKill
killEosInstances=not dontKill
@@ -93,8 +91,9 @@ try:
specificExtraNodeosArgs={}
# producer nodes will be mapped to 0 through pnodes-1, so the number pnodes is the no-producing API node
specificExtraNodeosArgs[pnodes]=" --plugin eosio::net_api_plugin"
specificExtraNodeosArgs[pnodes]+=" --read-only-threads "
specificExtraNodeosArgs[pnodes]+=str(args.read_only_threads)
if args.read_only_threads > 0:
specificExtraNodeosArgs[pnodes]+=" --read-only-threads "
specificExtraNodeosArgs[pnodes]+=str(args.read_only_threads)
if args.eos_vm_oc_enable:
specificExtraNodeosArgs[pnodes]+=" --eos-vm-oc-enable"
if args.wasm_runtime:
@@ -260,7 +259,7 @@ try:
runReadOnlyTrxAndRpcInParallel("chain", "get_raw_code_and_abi", "account_name", expectedValue=testAccountName, payload = {"account_name":testAccountName})
runReadOnlyTrxAndRpcInParallel("chain", "get_raw_abi", "account_name", expectedValue=testAccountName, payload = {"account_name":testAccountName})
runReadOnlyTrxAndRpcInParallel("chain", "get_producers", "rows", payload = {"json":"true","lower_bound":""})
runReadOnlyTrxAndRpcInParallel("chain", "get_table_rows", "rows", payload = {"json":"true","code":"eosio","scope":"eosio","table":"global"})
runReadOnlyTrxAndRpcInParallel("chain", "get_table_rows", code=500, payload = {"json":"true","table":"noauth"})
runReadOnlyTrxAndRpcInParallel("chain", "get_table_by_scope", fieldIn="rows", payload = {"json":"true","table":"noauth"})
runReadOnlyTrxAndRpcInParallel("chain", "get_currency_balance", code=200, payload = {"code":"eosio.token", "account":testAccountName})
runReadOnlyTrxAndRpcInParallel("chain", "get_currency_stats", fieldIn="SYS", payload = {"code":"eosio.token", "symbol":"SYS"})
@@ -280,11 +279,10 @@ try:
basicTests()
if args.read_only_threads > 0: # Save test time. No need to run other tests if multi-threaded is not enabled
for i in range(numTestRuns):
multiReadOnlyTests()
chainApiTests()
netApiTests()
mixedOpsTest()
multiReadOnlyTests()
chainApiTests()
netApiTests()
mixedOpsTest()
testSuccessful = True
finally: