Commit f5f296b1 authored by Max Kellermann's avatar Max Kellermann Committed by Max Kellermann

event/TimerWheel: add a "ready" list as a special case

This reduces delays of zero-duration timers from up to 1 second to zero. libavahi-client schedules zero-duration timers often.
parent 0091c4e1
......@@ -145,7 +145,7 @@ EventLoop::AbandonFD(SocketEvent &event) noexcept
void
EventLoop::Insert(CoarseTimerEvent &t) noexcept
{
coarse_timers.Insert(t);
coarse_timers.Insert(t, SteadyNow());
again = true;
}
......
......@@ -46,14 +46,18 @@ TimerWheel::TimerWheel() noexcept
TimerWheel::~TimerWheel() noexcept = default;
void
TimerWheel::Insert(CoarseTimerEvent &t) noexcept
TimerWheel::Insert(CoarseTimerEvent &t,
Event::TimePoint now) noexcept
{
/* if this timer's due time is already in the past, don't
insert it into an older bucket because Run() won't look at
it in this iteration */
const auto due = std::max(t.GetDue(), last_time);
assert(now >= last_time);
buckets[BucketIndexAt(due)].push_back(t);
auto &list = t.GetDue() > now
? buckets[BucketIndexAt(t.GetDue())]
/* if this timer is already due, insert it into the
"ready" list to be invoked without delay */
: ready;
list.push_back(t);
empty = false;
}
......@@ -101,6 +105,10 @@ TimerWheel::GetNextDue(const std::size_t bucket_index,
inline Event::Duration
TimerWheel::GetSleep(Event::TimePoint now) const noexcept
{
/* note: not checking the "ready" list here because this
method gets called only from Run() after the "ready" list
has been processed already */
if (empty)
return Event::Duration(-1);
......@@ -117,6 +125,11 @@ TimerWheel::GetSleep(Event::TimePoint now) const noexcept
Event::Duration
TimerWheel::Run(const Event::TimePoint now) noexcept
{
/* invoke the "ready" list unconditionally */
ready.clear_and_dispose([&](auto *t){
t->Run();
});
/* check all buckets between the last time we were invoked and
now */
const std::size_t start_bucket = BucketIndexAt(last_time);
......
......@@ -65,6 +65,13 @@ class TimerWheel final {
std::array<List, N_BUCKETS> buckets;
/**
* A list of timers which are already ready. This can happen
* if they are scheduled with a zero duration or scheduled in
* the past.
*/
List ready;
/**
* The last time Run() was invoked. This is needed to
* determine the range of buckets to be checked, because we
* can't rely on getting a caller for every bucket; there may
......@@ -87,13 +94,15 @@ public:
~TimerWheel() noexcept;
bool IsEmpty() const noexcept {
return std::all_of(buckets.begin(), buckets.end(),
[](const auto &list){
return list.empty();
});
return ready.empty() &&
std::all_of(buckets.begin(), buckets.end(),
[](const auto &list){
return list.empty();
});
}
void Insert(CoarseTimerEvent &t) noexcept;
void Insert(CoarseTimerEvent &t,
Event::TimePoint now) noexcept;
/**
* Invoke all expired #CoarseTimerEvent instances and return
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment