12
12
#include " base/mac/mach_logging.h"
13
13
#include " base/mac/scoped_nsautorelease_pool.h"
14
14
#include " base/posix/eintr_wrapper.h"
15
+ #include " base/time/time_override.h"
15
16
16
17
namespace base {
17
18
@@ -21,10 +22,22 @@ namespace {
21
22
// port sets. MessagePumpKqueue will directly use Mach ports in the kqueue if
22
23
// it is possible.
23
24
bool KqueueNeedsPortSet () {
24
- static bool kqueue_needs_port_set = mac::IsAtMostOS10_11 ();
25
+ static const bool kqueue_needs_port_set = mac::IsAtMostOS10_11 ();
25
26
return kqueue_needs_port_set;
26
27
}
27
28
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
+
28
41
int ChangeOneEvent (const ScopedFD& kqueue, kevent64_s* event) {
29
42
return HANDLE_EINTR (kevent64 (kqueue.get (), event, 1 , nullptr , 0 , 0 , nullptr ));
30
43
}
@@ -365,25 +378,13 @@ bool MessagePumpKqueue::DoInternalWork(Delegate* delegate,
365
378
366
379
bool poll = next_work_info == nullptr ;
367
380
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 ));
387
388
388
389
PCHECK (rv >= 0 ) << " kevent64" ;
389
390
return ProcessEvents (delegate, rv);
@@ -448,6 +449,25 @@ bool MessagePumpKqueue::ProcessEvents(Delegate* delegate, int count) {
448
449
auto scoped_do_native_work = delegate->BeginNativeWork ();
449
450
controller->watcher ()->OnMachMessageReceived (port);
450
451
}
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_;
451
471
} else {
452
472
NOTREACHED () << " Unexpected event for filter " << event->filter ;
453
473
}
@@ -456,4 +476,47 @@ bool MessagePumpKqueue::ProcessEvents(Delegate* delegate, int count) {
456
476
return did_work;
457
477
}
458
478
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
+
459
522
} // namespace base
0 commit comments