-
Notifications
You must be signed in to change notification settings - Fork 3.8k
callback support for checktime timer expiry #7803
Changes from 1 commit
357e30c
c072a01
f959c5a
c79c769
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,8 @@ | |
|
||
#include <eosio/chain/exceptions.hpp> | ||
|
||
#include <atomic> | ||
|
||
#include <signal.h> | ||
|
||
namespace eosio { namespace chain { | ||
|
@@ -18,11 +20,22 @@ struct platform_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 | ||
result in an exception. Setting to nullptr disables any current set callback */ | ||
void set_expiry_callback(void(*func)(void*), void* user) { | ||
EOS_ASSERT(!(func && __atomic_load_n(&_callback_enabled, __ATOMIC_CONSUME)), misc_exception, "Setting a platform_timer callback when one already exists"); | ||
void set_expiration_callback(void(*func)(void*), void* user) { | ||
callback_state current_state; | ||
while((current_state = _callback_state.load(std::memory_order_acquire)) == READING) {} //wait for call_expiration_callback() to be done with it | ||
if(func) | ||
EOS_ASSERT(current_state == UNSET, misc_exception, "Setting a platform_timer callback when one already exists"); | ||
if(!func && current_state == UNSET) | ||
return; | ||
|
||
//prepare for loading in new values by moving from UNSET->WRITING | ||
while(!atomic_compare_exchange_strong(&_callback_state, ¤t_state, WRITING)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm having trouble working out the control flow in here. There are ideally two expectations here: we are "clearing" the callback and the After the first iteration of the loop, we change our expected |
||
current_state = UNSET; | ||
_expiration_callback = func; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm worried that there is more than just the one "Race" that you mentioned in the PR here. Given
I think I understand in that in the current use case where T2 is practically going to be nullptr that boils down to: so, 2 out of 6 of those are crashes, 1 out of 6 calls We can improve this dramatically if we can guarantee that the pair is atomically set and queried. Does the signal handling context allow us to use atomic primitives like CAS? Or can we double buffer the function+data pair with some sort of data barrier and then "swap" the active buffer with a single index update? |
||
_expiration_callback_data = user; | ||
__atomic_store_n(&_callback_enabled, !!_expiration_callback, __ATOMIC_RELEASE); | ||
|
||
//finally go WRITING->SET | ||
_callback_state.store(func ? SET : UNSET, std::memory_order_release); | ||
} | ||
|
||
volatile sig_atomic_t expired = 1; | ||
|
@@ -32,17 +45,24 @@ struct platform_timer { | |
constexpr static size_t fwd_size = 8; | ||
fc::fwd<impl,fwd_size> my; | ||
|
||
enum callback_state { | ||
UNSET, | ||
READING, | ||
WRITING, | ||
SET | ||
}; | ||
|
||
void call_expiration_callback() { | ||
if(__atomic_load_n(&_callback_enabled, __ATOMIC_CONSUME)) { | ||
callback_state state_is_SET = SET; | ||
if(atomic_compare_exchange_strong(&_callback_state, &state_is_SET, READING)) { | ||
void(*cb)(void*) = _expiration_callback; | ||
void* cb_data = _expiration_callback_data; | ||
if(__atomic_load_n(&_callback_enabled, __ATOMIC_CONSUME)) | ||
cb(cb_data); | ||
_callback_state.store(SET, std::memory_order_release); | ||
cb(cb_data); | ||
} | ||
} | ||
|
||
bool _callback_enabled = false; | ||
static_assert(__atomic_always_lock_free(sizeof(_callback_enabled), 0), "eosio requires an always lock free bool type"); | ||
std::atomic<callback_state> _callback_state = UNSET; | ||
void(*_expiration_callback)(void*); | ||
void* _expiration_callback_data; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see a "carries a dependency" relationship here between This https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html indicates There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://en.cppreference.com/w/cpp/atomic/memory_order
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this assert will sometimes fire and sometimes not, such that you cannot make assumptions in the remaining code that
_callback_state
is eitherSET
orfunc
isnullptr
as the_callback_state
can change between this line and later lines where it is modified.