-
Notifications
You must be signed in to change notification settings - Fork 37.7k
RFC: Interruptible threads #8023
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
These should be drop-in replacements for: boost::thread boost::thread_group boost::condition_variable boost::this_thread::sleep_for
|
Boost's |
while (!pred()) { | ||
bool ret = m_condvar.wait_until(lock, abs_time) == std::cv_status::timeout; | ||
this_thread::interruption_point(); | ||
if (!ret) |
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.
The loop spins after timeout. Should this test be if (ret)
?
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.
yep!
as kazcw points out, the raw thread may be running free after the interruptible has been destroyed, so its data needs to remain valid. TODO: This probably isn't enough to cope with detaching, need to poke at it and add some tests.
Clarify some wait_until, and make doubly-sure that exceptions have been thrown.
Thanks to kazcw for pointing out that boost's thread_group only deletes threads in its own dtor. Also updated create_thread to return a pointer to the thread to match the boost functionality.
@kazcw Thanks for the great review! It's much appreciated. This surely needs lots more tests, and there are still some rough edges. I'll be away for a few days, if others agree this is a good plan, I'll work on cleanup and test coverage when I get back. |
|
||
void set_interrupted() | ||
{ | ||
m_interrupted.store(true, std::memory_order_relaxed); |
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.
This needs to be at least memory_order_acquire
so it happens before the notify. Locking the mutex is only an acquire operation and won't act as a suitable fence.
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.
Yep, thanks. This and the load should've both been marked for TODO, as I don't understand their interaction with the notify well enough. The orderings seem like pure voodoo :)
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.
Yeah, it's wild stuff. I don't really understand it either.
The synchronization effects of notify
on non-condition_variable
values aren't specified, so I think we have to be conservative. One approach would be a release fence after the store and an acquire load operation. There may be marginally better-performing options on the store side, but this has the benefit that acquire/release pairs are relatively comprehensible. The load could technically be relaxed and the compiler still "should" make sure thread that's doing the loads sees the update "in a reasonable amount of time", but the compiler can hoist relaxed loads out of loops if it sees fit and who knows what's a reasonable amount of time to a compiler. Acquire and release are both cheap on x86 and anyway, interrupt points are going to be places that we expect time consuming operations to be happening, not in tight loops.
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've been thinking about this... I think it was actually fine as is. The store and the notify don't need to be ordered relative to each other because the release of the lock at the exit of the scope is synchronized-with the acquire of the lock that wait
must perform before returning.
Concept ACK on moving away from boost for threading. Since most threads are in the same |
The relationship between |
@kazcw The problem with that is that you still have to access the private condvar pointer after grabbing the double-lock, or use a getter that provides unguarded access, so the lock isn't actually providing any protection other than the understanding that the caller will use it to change the pointer (If I'm understanding you correctly). I tried that approach, and it led to the creation of an RAII wrapper holding a But by all means, I'd be happy to see something more intuitive than what's here now :) |
I see. Yeah, I don't see any way for |
I appreciate all the work thats gone into this, and I think it should be merged eventually, but as our agreed-upon timeline has been that only new code should use C++11 features until after 0.13, I have to say NACK. |
Re: C++11 changes - changing utility classes to use C++11 is fine as long as the code using it is not or hardly impacted (e.g. the change to make tinyformat use C++11 variadic templates). But here we have to change all use/call sites as well (unless we do a terrible hack to disguise as boost) so yes, need to postpone this a bit. But Concept ACK. Makes sense for 0.14 along with more compatibility changes to get rid of boost. Added the "future" milestone. |
HTTP Server cherries from Core: bitcoin/bitcoin#6719 - Make HTTP server shutdown more graceful bitcoin/bitcoin#6859 - http: Restrict maximum size of http + headers bitcoin/bitcoin#6990 - http: speed up shutdown bitcoin/bitcoin#7966 - http: Do a pending c++11 simplification handling work items bitcoin/bitcoin#8421 - httpserver: drop boost (#8023 dependency) bitcoin/bitcoin#11006 - Improve shutdown process
The intention here is to create a drop-in replacement for:
boost::thread
boost::thread_group
boost::condition_variable
boost::this_thread
These are just interruptible wrappers around the c++11 std:: versions. There are no dependencies, and afaik it's completely portable.
This allows us to move away from boost threads (which block the movement of other things like mutexes, function, bind, chrono, etc), without having to worry about refactoring to cope with our dependence on interruption points, interruptible condition variables, and interruptible sleeps.
For the places that don't require interruption, they can just move directly to std::.
The code needs some refinements, comments, and more tests, but I'd like to get a few concept ack's before continuing. Obviously the next step is to start plugging it all in.
Also, to avoid conflicting with segwit changes and backporting, it may be easier to replace things in some places and not others.
The condition_variable is actually an implementation of condition_variable_any, which means that it will accept any Lockable. So we can mix and match boost::mutex/boost::unique_lock/std::mutex/std::unique_lock without any trouble, if that eases the migration.