-
Notifications
You must be signed in to change notification settings - Fork 3.7k
callback support for checktime timer expiry #7803
Changes from all commits
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 |
---|---|---|
@@ -1,6 +1,11 @@ | ||
#pragma once | ||
#include <fc/time.hpp> | ||
#include <fc/fwd.hpp> | ||
#include <fc/scoped_exit.hpp> | ||
|
||
#include <eosio/chain/exceptions.hpp> | ||
|
||
#include <atomic> | ||
|
||
#include <signal.h> | ||
|
||
|
@@ -13,12 +18,43 @@ struct platform_timer { | |
void start(fc::time_point tp); | ||
void stop(); | ||
|
||
/* 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_expiration_callback(void(*func)(void*), void* user) { | ||
bool expect_false = false; | ||
while(!atomic_compare_exchange_strong(&_callback_variables_busy, &expect_false, true)) | ||
expect_false = false; | ||
auto reset_busy = fc::make_scoped_exit([this]() { | ||
_callback_variables_busy.store(false, std::memory_order_release); | ||
}); | ||
EOS_ASSERT(!(func && _expiration_callback), misc_exception, "Setting a platform_timer callback when one already exists"); | ||
|
||
_expiration_callback = func; | ||
_expiration_callback_data = user; | ||
} | ||
|
||
volatile sig_atomic_t expired = 1; | ||
|
||
private: | ||
struct impl; | ||
constexpr static size_t fwd_size = 8; | ||
fc::fwd<impl,fwd_size> my; | ||
|
||
void call_expiration_callback() { | ||
bool expect_false = false; | ||
if(atomic_compare_exchange_strong(&_callback_variables_busy, &expect_false, true)) { | ||
void(*cb)(void*) = _expiration_callback; | ||
void* cb_data = _expiration_callback_data; | ||
_callback_variables_busy.store(false, std::memory_order_release); | ||
if(cb) | ||
cb(cb_data); | ||
} | ||
} | ||
|
||
std::atomic_bool _callback_variables_busy = false; | ||
void(*_expiration_callback)(void*) = nullptr; | ||
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'm worried that there is more than just the one "Race" that you mentioned in the PR here. Given
FuncT1
DataT1
FuncT2
DataT2
(where one pair may benullptr
) Depending on how the compiler orders these instructions on bothset_expiry_callback
andcall_expiration_callback
you can at least get any of the following cases:if (FuncT1) FuncT1(DataT1);
if (FuncT1) FuncT2(DataT1)
if (FuncT1) FuncT1(DataT2)
if (FuncT1) FuncT2(DataT2)
if (FuncT2) FuncT2(DataT1)
if (FuncT2) FuncT2(DataT2)
I think I understand in that in the current use case where T2 is practically going to be nullptr that boils down to:
if (FuncT1) FuncT1(DataT1);
if (FuncT1) nullptr(DataT1)
if (FuncT1) FuncT1(nullptr)
if (FuncT1) nullptr(nullptr)
if (nullptr)...
if (nullptr)...
so, 2 out of 6 of those are crashes, 1 out of 6 calls
FuncT1
in an unexpected way. 1 out of 6 callsFuncT1
"spuriously" (the noted race in the PR) and the remaining 2 operate as expected.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?