mirror of
https://github.com/boostorg/fiber.git
synced 2026-02-20 14:42:21 +00:00
Merge pull request #81 from nat-goodspeed/always_set_timer
Make round_robin::suspend_until() always set asio timer.
This commit is contained in:
@@ -50,23 +50,25 @@ public:
|
||||
boost::asio::io_service::service( io_svc),
|
||||
work_{ new boost::asio::io_service::work( io_svc) } {
|
||||
io_svc.post([&io_svc](){
|
||||
while ( ! io_svc.stopped() ) {
|
||||
if ( boost::fibers::has_ready_fibers() ) {
|
||||
// run all pending handlers in round_robin
|
||||
while ( io_svc.poll() );
|
||||
// run pending (ready) fibers
|
||||
this_fiber::yield();
|
||||
} else {
|
||||
// run one handler inside io_service
|
||||
// if no handler available, block this thread
|
||||
if ( ! io_svc.run_one() ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
while ( ! io_svc.stopped() ) {
|
||||
if ( boost::fibers::has_ready_fibers() ) {
|
||||
// run all pending handlers in round_robin
|
||||
while ( io_svc.poll() );
|
||||
// run pending (ready) fibers
|
||||
this_fiber::yield();
|
||||
} else {
|
||||
// run one handler inside io_service
|
||||
// if no handler available, block this thread
|
||||
if ( ! io_svc.run_one() ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
virtual ~service() {}
|
||||
|
||||
service( service const&) = delete;
|
||||
service & operator=( service const&) = delete;
|
||||
|
||||
@@ -78,7 +80,10 @@ public:
|
||||
round_robin( boost::asio::io_service & io_svc) :
|
||||
io_svc_( io_svc),
|
||||
suspend_timer_( io_svc_) {
|
||||
boost::asio::use_service< service >( io_svc_);
|
||||
// We use add_service() very deliberately. This will throw
|
||||
// service_already_exists if you pass the same io_service instance to
|
||||
// more than one round_robin instance.
|
||||
boost::asio::add_service( io_svc_, new service( io_svc_));
|
||||
}
|
||||
|
||||
void awakened( context * ctx) noexcept {
|
||||
@@ -104,15 +109,53 @@ public:
|
||||
}
|
||||
|
||||
void suspend_until( std::chrono::steady_clock::time_point const& abs_time) noexcept {
|
||||
if ( (std::chrono::steady_clock::time_point::max)() != abs_time) {
|
||||
// Set a timer so at least one handler will eventually fire, causing
|
||||
// run_one() to eventually return. Set a timer even if abs_time ==
|
||||
// time_point::max() so the timer can be canceled by our notify()
|
||||
// method -- which calls the handler.
|
||||
if ( suspend_timer_.expires_at() != abs_time) {
|
||||
// Each expires_at(time_point) call cancels any previous pending
|
||||
// call. We could inadvertently spin like this:
|
||||
// dispatcher calls suspend_until() with earliest wake time
|
||||
// suspend_until() sets suspend_timer_
|
||||
// lambda loop calls run_one()
|
||||
// some other asio handler runs before timer expires
|
||||
// run_one() returns to lambda loop
|
||||
// lambda loop yields to dispatcher
|
||||
// dispatcher finds no ready fibers
|
||||
// dispatcher calls suspend_until() with SAME wake time
|
||||
// suspend_until() sets suspend_timer_ to same time, canceling
|
||||
// previous async_wait()
|
||||
// lambda loop calls run_one()
|
||||
// asio calls suspend_timer_ handler with operation_aborted
|
||||
// run_one() returns to lambda loop... etc. etc.
|
||||
// So only actually set the timer when we're passed a DIFFERENT
|
||||
// abs_time value.
|
||||
suspend_timer_.expires_at( abs_time);
|
||||
suspend_timer_.async_wait([](boost::system::error_code const&){
|
||||
this_fiber::yield();
|
||||
});
|
||||
// It really doesn't matter what the suspend_timer_ handler does,
|
||||
// or even whether it's called because the timer ran out or was
|
||||
// canceled. The whole point is to cause the run_one() call to
|
||||
// return. So just pass a no-op lambda with proper signature.
|
||||
suspend_timer_.async_wait([](boost::system::error_code const&){});
|
||||
}
|
||||
}
|
||||
|
||||
void notify() noexcept {
|
||||
// Something has happened that should wake one or more fibers BEFORE
|
||||
// suspend_timer_ expires. Reset the timer to cause it to fire
|
||||
// immediately, causing the run_one() call to return. In theory we
|
||||
// could use cancel() because we don't care whether suspend_timer_'s
|
||||
// handler is called with operation_aborted or success. However --
|
||||
// cancel() doesn't change the expiration time, and we use
|
||||
// suspend_timer_'s expiration time to decide whether it's already
|
||||
// set. If suspend_until() set some specific wake time, then notify()
|
||||
// canceled it, then suspend_until() was called again with the same
|
||||
// wake time, it would match suspend_timer_'s expiration time and we'd
|
||||
// refrain from setting the timer. So instead of simply calling
|
||||
// cancel(), reset the timer, which cancels the pending sleep AND sets
|
||||
// a new expiration time. This will cause us to spin the loop twice --
|
||||
// once for the operation_aborted handler, once for timer expiration
|
||||
// -- but that shouldn't be a big problem.
|
||||
suspend_timer_.expires_at( std::chrono::steady_clock::now() );
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user