Skip to content

Commit 162efe9

Browse files
sigurasgChromium LUCI CQ
authored and
Chromium LUCI CQ
committed
Reland "Use a timer instead of a sleep loop."
This is a reland of ec21b4b Original change's description: > Use a timer instead of a sleep loop. > > This paves the way for implementing timer slack on the kqueue > message pump. > > Bug: 1181297 > Change-Id: I74fdb63e85cf726000ee94b5851f84b06de314ae > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2713848 > Auto-Submit: Sigurður Ásgeirsson <siggi@chromium.org> > Reviewed-by: Mark Mentovai <mark@chromium.org> > Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org> > Reviewed-by: Robert Sesek <rsesek@chromium.org> > Commit-Queue: Sigurður Ásgeirsson <siggi@chromium.org> > Cr-Commit-Position: refs/heads/master@{#857894} Bug: 1181297 Change-Id: I63287f8d53f0aee9833d090ef6d32aa94698c692 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2753412 Reviewed-by: Robert Sesek <rsesek@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org> Commit-Queue: Sigurður Ásgeirsson <siggi@chromium.org> Cr-Commit-Position: refs/heads/master@{#862125}
1 parent e34caac commit 162efe9

File tree

2 files changed

+91
-20
lines changed

2 files changed

+91
-20
lines changed

base/message_loop/message_pump_kqueue.cc

+83-20
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "base/mac/mach_logging.h"
1313
#include "base/mac/scoped_nsautorelease_pool.h"
1414
#include "base/posix/eintr_wrapper.h"
15+
#include "base/time/time_override.h"
1516

1617
namespace base {
1718

@@ -21,10 +22,22 @@ namespace {
2122
// port sets. MessagePumpKqueue will directly use Mach ports in the kqueue if
2223
// it is possible.
2324
bool KqueueNeedsPortSet() {
24-
static bool kqueue_needs_port_set = mac::IsAtMostOS10_11();
25+
static const bool kqueue_needs_port_set = mac::IsAtMostOS10_11();
2526
return kqueue_needs_port_set;
2627
}
2728

29+
#if DCHECK_IS_ON()
30+
// Prior to macOS 10.14, kqueue timers may spuriously wake up, because earlier
31+
// wake ups race with timer resets in the kernel. As of macOS 10.14, updating a
32+
// timer from the thread that reads the kqueue does not cause spurious wakeups.
33+
// Note that updating a kqueue timer from one thread while another thread is
34+
// waiting in a kevent64 invocation is still (inherently) racy.
35+
bool KqueueTimersSpuriouslyWakeUp() {
36+
static const bool kqueue_timers_spuriously_wakeup = mac::IsAtMostOS10_13();
37+
return kqueue_timers_spuriously_wakeup;
38+
}
39+
#endif
40+
2841
int ChangeOneEvent(const ScopedFD& kqueue, kevent64_s* event) {
2942
return HANDLE_EINTR(kevent64(kqueue.get(), event, 1, nullptr, 0, 0, nullptr));
3043
}
@@ -365,25 +378,13 @@ bool MessagePumpKqueue::DoInternalWork(Delegate* delegate,
365378

366379
bool poll = next_work_info == nullptr;
367380
int flags = poll ? KEVENT_FLAG_IMMEDIATE : 0;
368-
bool indefinite =
369-
next_work_info != nullptr && next_work_info->delayed_run_time.is_max();
370-
371-
int rv = 0;
372-
do {
373-
timespec timeout{};
374-
if (!indefinite && !poll) {
375-
if (rv != 0) {
376-
// The wait was interrupted and made |next_work_info|'s view of
377-
// TimeTicks::Now() stale. Refresh it before doing another wait.
378-
next_work_info->recent_now = TimeTicks::Now();
379-
}
380-
timeout = next_work_info->remaining_delay().ToTimeSpec();
381-
}
382-
// This does not use HANDLE_EINTR, since retrying the syscall requires
383-
// adjusting the timeout to account for time already waited.
384-
rv = kevent64(kqueue_.get(), nullptr, 0, events_.data(), events_.size(),
385-
flags, indefinite ? nullptr : &timeout);
386-
} while (rv < 0 && errno == EINTR);
381+
if (!poll && scheduled_wakeup_time_ != next_work_info->delayed_run_time) {
382+
UpdateWakeupTimer(next_work_info->delayed_run_time);
383+
DCHECK_EQ(scheduled_wakeup_time_, next_work_info->delayed_run_time);
384+
}
385+
386+
int rv = HANDLE_EINTR(kevent64(kqueue_.get(), nullptr, 0, events_.data(),
387+
events_.size(), flags, nullptr));
387388

388389
PCHECK(rv >= 0) << "kevent64";
389390
return ProcessEvents(delegate, rv);
@@ -448,6 +449,25 @@ bool MessagePumpKqueue::ProcessEvents(Delegate* delegate, int count) {
448449
auto scoped_do_native_work = delegate->BeginNativeWork();
449450
controller->watcher()->OnMachMessageReceived(port);
450451
}
452+
} else if (event->filter == EVFILT_TIMER) {
453+
// The wakeup timer fired.
454+
#if DCHECK_IS_ON()
455+
// On macOS 10.13 and earlier, kqueue timers may spuriously wake up.
456+
// When this happens, the timer will be re-scheduled the next time
457+
// DoInternalWork is entered, which means this doesn't lead to a
458+
// spinning wait.
459+
// When clock overrides are active, TimeTicks::Now may be decoupled from
460+
// wall-clock time, and can therefore not be used to validate whether the
461+
// expected wall-clock time has passed.
462+
if (!KqueueTimersSpuriouslyWakeUp() &&
463+
!subtle::ScopedTimeClockOverrides::overrides_active()) {
464+
// Given the caveats above, assert that the timer didn't fire early.
465+
DCHECK_LE(scheduled_wakeup_time_, base::TimeTicks::Now());
466+
}
467+
#endif
468+
DCHECK_NE(scheduled_wakeup_time_, base::TimeTicks::Max());
469+
scheduled_wakeup_time_ = base::TimeTicks::Max();
470+
--event_count_;
451471
} else {
452472
NOTREACHED() << "Unexpected event for filter " << event->filter;
453473
}
@@ -456,4 +476,47 @@ bool MessagePumpKqueue::ProcessEvents(Delegate* delegate, int count) {
456476
return did_work;
457477
}
458478

479+
void MessagePumpKqueue::UpdateWakeupTimer(const base::TimeTicks& wakeup_time) {
480+
DCHECK_NE(wakeup_time, scheduled_wakeup_time_);
481+
482+
// The ident of the wakeup timer. There's only the one timer as the pair
483+
// (ident, filter) is the identity of the event.
484+
constexpr uint64_t kWakeupTimerIdent = 0x0;
485+
if (wakeup_time == base::TimeTicks::Max()) {
486+
// Clear the timer.
487+
kevent64_s timer{};
488+
timer.ident = kWakeupTimerIdent;
489+
timer.filter = EVFILT_TIMER;
490+
timer.flags = EV_DELETE;
491+
492+
int rv = ChangeOneEvent(kqueue_, &timer);
493+
PCHECK(rv == 0) << "kevent64, delete timer";
494+
--event_count_;
495+
} else {
496+
// Set/reset the timer.
497+
kevent64_s timer{};
498+
timer.ident = kWakeupTimerIdent;
499+
timer.filter = EVFILT_TIMER;
500+
// This updates the timer if it already exists in |kqueue_|.
501+
timer.flags = EV_ADD | EV_ONESHOT;
502+
// Specify the sleep in microseconds to avoid undersleeping due to
503+
// numeric problems. The sleep is computed from TimeTicks::Now rather than
504+
// NextWorkInfo::recent_now because recent_now is strictly earlier than
505+
// current wall-clock. Using an earlier wall clock time to compute the
506+
// delta to the next wakeup wall-clock time would guarantee oversleep.
507+
// If wakeup_time is in the past, the delta below will be negative and the
508+
// timer is set immediately.
509+
timer.fflags = NOTE_USECONDS;
510+
timer.data = (wakeup_time - base::TimeTicks::Now()).InMicroseconds();
511+
int rv = ChangeOneEvent(kqueue_, &timer);
512+
PCHECK(rv == 0) << "kevent64, set timer";
513+
514+
// Bump the event count if we just added the timer.
515+
if (scheduled_wakeup_time_ == base::TimeTicks::Max())
516+
++event_count_;
517+
}
518+
519+
scheduled_wakeup_time_ = wakeup_time;
520+
}
521+
459522
} // namespace base

base/message_loop/message_pump_kqueue.h

+8
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ class BASE_EXPORT MessagePumpKqueue : public MessagePump,
137137
// true if work was done, or false if no work was done.
138138
bool ProcessEvents(Delegate* delegate, int count);
139139

140+
// Sets the wakeup timer to |wakeup_time|, or clears it if |wakeup_time| is
141+
// base::TimeTicks::Max(). Updates |scheduled_wakeup_time_| to follow.
142+
void UpdateWakeupTimer(const base::TimeTicks& wakeup_time);
143+
140144
// Receive right to which an empty Mach message is sent to wake up the pump
141145
// in response to ScheduleWork().
142146
mac::ScopedMachReceiveRight wakeup_;
@@ -160,6 +164,10 @@ class BASE_EXPORT MessagePumpKqueue : public MessagePump,
160164
// Whether the pump has been Quit() or not.
161165
bool keep_running_ = true;
162166

167+
// The currently scheduled wakeup, if any. If no wakeup is scheduled,
168+
// contains base::TimeTicks::Max().
169+
base::TimeTicks scheduled_wakeup_time_{base::TimeTicks::Max()};
170+
163171
// The number of events scheduled on the |kqueue_|. There is always at least
164172
// 1, for the |wakeup_| port (or |port_set_|).
165173
size_t event_count_ = 1;

0 commit comments

Comments
 (0)