//[asio_rr_suspend_until void suspend_until( std::chrono::steady_clock::time_point const& abs_time) noexcept { // 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); // 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 ScheduleExpire() { expire_timer.expires_from_now(std::chrono::minutes(5)); expire_timer.async_wait([this](const boost::system::error_code &ec){ if (ec) return; clients.Expire(expire_timer.expires_at() - std::chrono::minutes(10)); if (!clients.empty()) ScheduleExpire(); }); }
//[asio_rr_notify 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() ); }
// Only call on io_service void ResetTimer() { const auto now = std::chrono::steady_clock::now(); UniqueLock unique_lock(mutex_); while (running_ && !data_.empty()) { auto map_front_it = data_.begin(); const auto timeout = map_front_it->first; if ( timeout <= now ) { // Effeciently extract the value so we can move it. auto event = std::move( map_front_it->second ); data_.erase(map_front_it); // Unlock incase callback calls a TimedActions method. unique_lock.unlock(); event_processor_(std::move(event), std::error_code()); unique_lock.lock(); } else { timeout_timer_.expires_at(timeout); timeout_timer_.async_wait( boost::bind( &TimedEvents<Event>::HandleTimeout, this->shared_from_this(), boost::asio::placeholders::error ) ); break; } } }