Skip to content

Commit

Permalink
GH-1095 Refactor platform_timer and transaction_checktime_timer to us…
Browse files Browse the repository at this point in the history
…e an enum for timer state instead of a bool expired. This allows differentiation between timer expired and interruption.
  • Loading branch information
heifner committed Jan 16, 2025
1 parent 1a2a0c9 commit 11f1e9a
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 27 deletions.
2 changes: 1 addition & 1 deletion libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4533,7 +4533,7 @@ struct controller_impl {
// validated.
if (applying_block) {
ilog("Interrupting apply block");
main_thread_timer.expire_now();
main_thread_timer.interrupt_timer();
}
}

Expand Down
16 changes: 12 additions & 4 deletions libraries/chain/include/eosio/chain/platform_timer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

#include <atomic>

#include <signal.h>

namespace eosio { namespace chain {

struct platform_timer {
Expand All @@ -17,7 +15,8 @@ struct platform_timer {

void start(fc::time_point tp);
void stop();
void expire_now();
void interrupt_timer();
void _expire_now(); // called by internal timer

/* Sets a callback for when timer expires. Be aware this could might fire from a signal handling context and/or
on any particular thread. Only a single callback can be registered at once; trying to register more will
Expand All @@ -35,9 +34,18 @@ struct platform_timer {
_expiration_callback_data = user;
}

std::atomic_bool expired = true;
enum class state_t {
running,
timed_out,
interrupted,
stopped
};
state_t timer_state() const { return _state; }

private:
std::atomic<state_t> _state = state_t::stopped;


struct impl;
constexpr static size_t fwd_size = 8;
fc::fwd<impl,fwd_size> my;
Expand Down
3 changes: 2 additions & 1 deletion libraries/chain/include/eosio/chain/transaction_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ namespace eosio::chain {
void start(fc::time_point tp);
void stop();

platform_timer::state_t timer_state() const { return _timer.timer_state(); }

/* Sets a callback for when timer expires. Be aware this could might fire from a signal handling context and/or
on any particular thread. Only a single callback can be registered at once; trying to register more will
result in an exception. Use nullptr to disable a previously set callback. */
void set_expiration_callback(void(*func)(void*), void* user);

std::atomic_bool& expired;
private:
platform_timer& _timer;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ struct eosvmoc_tier {
return;
if (executing_code_hash.load() == code_id) {
ilog("EOS VM OC tier up interrupting ${id}", ("id", code_id));
eos_vm_oc_compile_interrupt = true;
main_thread_timer.expire_now();
main_thread_timer.interrupt_timer();
}
});
}
Expand Down Expand Up @@ -172,7 +171,6 @@ struct eosvmoc_tier {
const bool allow_oc_interrupt = attempt_tierup && context.is_applying_block() && context.trx_context.has_undo();
auto ex = fc::make_scoped_exit([&]() {
if (allow_oc_interrupt) {
eos_vm_oc_compile_interrupt = false;
executing_code_hash.store({}); // indicate no longer executing
}
});
Expand All @@ -181,7 +179,7 @@ struct eosvmoc_tier {
try {
get_instantiated_module(code_hash, vm_type, vm_version, context.trx_context)->apply(context);
} catch (const interrupt_exception& e) {
if (allow_oc_interrupt && eos_vm_oc_compile_interrupt) {
if (allow_oc_interrupt && main_thread_timer.timer_state() == platform_timer::state_t::interrupted) {
++eos_vm_oc_compile_interrupt_count;
dlog("EOS VM OC compile complete interrupt of ${r} <= ${a}::${act} code ${h}, interrupt #${c}",
("r", context.get_receiver())("a", context.get_action().account)
Expand Down Expand Up @@ -310,7 +308,6 @@ struct eosvmoc_tier {
const wasm_interface::vm_type wasm_runtime_time;
const wasm_interface::vm_oc_enable eosvmoc_tierup;
large_atomic<digest_type> executing_code_hash{};
std::atomic<bool> eos_vm_oc_compile_interrupt{false};
uint32_t eos_vm_oc_compile_interrupt_count{0}; // for testing

#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
Expand Down
3 changes: 2 additions & 1 deletion libraries/chain/platform_timer_accuracy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ void compute_and_print_timer_accuracy(platform_timer& timer) {
for(unsigned int i = 0; i < loops; ++i) {
auto start = std::chrono::high_resolution_clock::now();
timer.start(fc::time_point(fc::time_point::now().time_since_epoch() + fc::microseconds(interval)));
while(!timer.expired) {}
while(timer.timer_state() == platform_timer::state_t::running) {}
auto end = std::chrono::high_resolution_clock::now();
int timer_slop = std::chrono::duration_cast<std::chrono::microseconds>(end-start).count() - interval;
timer.stop();

//since more samples are run for the shorter expirations, weigh the longer expirations accordingly. This
//helps to make a few results more fair. Two such examples: AWS c4&i5 xen instances being rather stable
Expand Down
27 changes: 17 additions & 10 deletions libraries/chain/platform_timer_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct platform_timer::impl {

static void sig_handler(int, siginfo_t* si, void*) {
platform_timer* self = (platform_timer*)si->si_value.sival_ptr;
self->expire_now();
self->_expire_now();
}
};

Expand Down Expand Up @@ -56,35 +56,42 @@ platform_timer::~platform_timer() {

void platform_timer::start(fc::time_point tp) {
if(tp == fc::time_point::maximum()) {
expired = false;
_state = state_t::running;
return;
}
fc::microseconds x = tp.time_since_epoch() - fc::time_point::now().time_since_epoch();
if(x.count() <= 0)
expired = true;
_state = state_t::timed_out;
else {
time_t secs = x.count() / 1000000;
long nsec = (x.count() - (secs*1000000)) * 1000;
struct itimerspec enable = {{0, 0}, {secs, nsec}};
expired = false;
_state = state_t::running;
if(timer_settime(my->timerid, 0, &enable, NULL) != 0)
expired = true;
_state = state_t::timed_out;
}
}

void platform_timer::expire_now() {
bool expected = false;
if (expired.compare_exchange_strong(expected, true)) {
void platform_timer::_expire_now() {
state_t expected = state_t::running;
if (_state.compare_exchange_strong(expected, state_t::timed_out)) {
call_expiration_callback();
}
}

void platform_timer::interrupt_timer() {
state_t expected = state_t::running;
if (_state.compare_exchange_strong(expected, state_t::interrupted)) {
call_expiration_callback();
}
}

void platform_timer::stop() {
if(expired)
if(_state == state_t::stopped)
return;
struct itimerspec disable = {{0, 0}, {0, 0}};
timer_settime(my->timerid, 0, &disable, NULL);
expired = true;
_state = state_t::stopped;
}

}
8 changes: 4 additions & 4 deletions libraries/chain/transaction_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
namespace eosio::chain {

transaction_checktime_timer::transaction_checktime_timer(platform_timer& timer)
: expired(timer.expired), _timer(timer) {
expired = 0;
: _timer(timer) {
}

void transaction_checktime_timer::start(fc::time_point tp) {
Expand Down Expand Up @@ -489,11 +488,12 @@ namespace eosio::chain {
}

void transaction_context::checktime()const {
if(BOOST_LIKELY(transaction_timer.expired == false))
platform_timer::state_t expired = transaction_timer.timer_state();
if(BOOST_LIKELY(expired == platform_timer::state_t::running))
return;

auto now = fc::time_point::now();
if (explicit_billed_cpu_time && block_deadline > now) {
if (expired == platform_timer::state_t::interrupted) {
EOS_THROW( interrupt_exception, "interrupt signaled, ran ${bt}us, start ${s}",
("bt", now - pseudo_start)("s", start) );
} else if( explicit_billed_cpu_time || deadline_exception_code == deadline_exception::code_value ) {
Expand Down
3 changes: 2 additions & 1 deletion libraries/chain/webassembly/runtimes/eos-vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ namespace {
guard(transaction_checktime_timer& timer, F&& func)
: _timer(timer), _func(std::forward<F>(func)) {
_timer.set_expiration_callback(&callback, this);
if(_timer.expired) {
platform_timer::state_t expired = _timer.timer_state();
if(expired == platform_timer::state_t::timed_out || expired == platform_timer::state_t::interrupted) {
_func(); // it's harmless if _func is invoked twice
}
}
Expand Down

0 comments on commit 11f1e9a

Please sign in to comment.