-
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 |
---|---|---|
|
@@ -17,11 +17,12 @@ 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 is allowed as a "no callback" hint */ | ||
result in an exception. Setting to nullptr disables any current set callback */ | ||
void set_expiry_callback(void(*func)(void*), void* user) { | ||
EOS_ASSERT(!(func && _expiration_callback), misc_exception, "Setting a platform_timer callback when one already exists"); | ||
EOS_ASSERT(!(func && __atomic_load_n(&_callback_enabled, __ATOMIC_CONSUME)), misc_exception, "Setting a platform_timer callback when one already exists"); | ||
_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); | ||
} | ||
|
||
volatile sig_atomic_t expired = 1; | ||
|
@@ -32,11 +33,17 @@ struct platform_timer { | |
fc::fwd<impl,fwd_size> my; | ||
|
||
void call_expiration_callback() { | ||
if(_expiration_callback) | ||
_expiration_callback(_expiration_callback_data); | ||
if(__atomic_load_n(&_callback_enabled, __ATOMIC_CONSUME)) { | ||
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 doubt we'd ever see this as problematic given our use case but, as these are fences, not locks there is no guarantee that between line 36 and 37 and 38, we don't see different states of both I think something like a 4 stage atomic enumerated int (unset, writing, set, reading) with:
reduces our possible damage to
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.
Yeah it's a variation of an ABA problem; but it would require someone to do set_callback(nullptr), set_callback(0x...) back to back right? Something that is pretty far fetched for our use case (it would effectively be an action ending and starting without anything in between). I have no qualms about hardening this further; as the complexity increases it starts to smell more like something that should be improved with a 128-bit CAS for at least x86_64 though 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.
As long as we have a base implementation that is safe, I support platforms dabbling with faster versions (which should also be safe) where available. In this case we just need 128bit atomic load/store right? It would effectively become last-write-wins but with the concession that we are only trying to guarantee consistency of callback/data pointers thats "safe" 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. Right, just need an atomic 128bit load/store to meet the same safety criteria it seems. Which surprisingly (to me) on x86_64 only means CMPXCHG16B -- from what I can from docs even wide SSE/AVX load/stores are not guaranteed to be atomic. anyways, it's worth benchmarking the impact of the base impl before optimizing this immediately. 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. once again, I've failed at PR review, you were right to need a 128 bit CAS as the result of the comparison allows you to maintain the expectation that you cannot overwrite an existing callback (that throws) |
||
void(*cb)(void*) = _expiration_callback; | ||
void* cb_data = _expiration_callback_data; | ||
if(__atomic_load_n(&_callback_enabled, __ATOMIC_CONSUME)) | ||
cb(cb_data); | ||
} | ||
} | ||
|
||
void(*_expiration_callback)(void*) = nullptr; | ||
bool _callback_enabled = false; | ||
static_assert(__atomic_always_lock_free(sizeof(_callback_enabled), 0), "eosio requires an always lock free bool type"); | ||
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.
to remain consistent with the name of the member variable and the
call_expiration_callback
method this should probably be renamed toset_expiration_callback