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.
This commit is contained in:
Max Kellermann 2021-03-02 00:25:04 +01:00 committed by Max Kellermann
parent 0091c4e12b
commit f5f296b13a
3 changed files with 34 additions and 12 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -64,6 +64,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
@ -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