/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include "TimerThreadMonitor.h" #include "mozilla/Assertions.h" #include "mozilla/ProfilerThreadSleep.h" namespace mozilla { namespace { // Identifiers for our registrations on mKq. They only need to be unique per // filter. constexpr uint64_t kNotifyIdent = 1; constexpr uint64_t kTimerIdent = 2; void ApplyChange(int aKq, const struct kevent64_s& aChange) { [[maybe_unused]] const int rv = kevent64(aKq, &aChange, 1, nullptr, 0, 0, nullptr); MOZ_ASSERT(rv >= 0); } // Blocks until at least one event is delivered. Room for both of our knotes, // so the queue is always drained: a notification left behind by a wait that // woke on its timer would otherwise return the next wait immediately. void BlockOnKqueue(int aKq) { struct kevent64_s events[2]; while (kevent64(aKq, nullptr, 0, events, 2, 0, nullptr) < 0) { MOZ_RELEASE_ASSERT(errno == EINTR); } } } // namespace TimerThreadMonitor::TimerThreadMonitor(const char* aName) : mMutex(aName), mKq(kqueue()) { MOZ_RELEASE_ASSERT(mKq); struct kevent64_s change; EV_SET64(&change, kNotifyIdent, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0, 0, 0); MOZ_RELEASE_ASSERT(kevent64(mKq.get(), &change, 1, nullptr, 0, 0, nullptr) >= 0); } TimerThreadMonitor::~TimerThreadMonitor() { AssertNoWaiter(); } void TimerThreadMonitor::Wait() { BeginWait(); { AUTO_PROFILER_THREAD_SLEEP; Unlock(); BlockOnKqueue(mKq.get()); Lock(); } EndWait(); } void TimerThreadMonitor::Wait(TimeDuration aDuration, TimeDuration aTolerance) { if (aDuration == TimeDuration::Forever()) { Wait(); return; } BeginWait(); // NOTE_LEEWAY hands the kernel ext[1] as a coalescing budget. On its own // that budget is only a floor, and the system stays free to coalesce well // past it, most aggressively when idle. NOTE_CRITICAL asks it to observe the // budget strictly instead, at the cost of power. const uint32_t urgency = IsPreciseTolerance(aTolerance) ? NOTE_CRITICAL | NOTE_LEEWAY : NOTE_LEEWAY; struct kevent64_s change; EV_SET64(&change, kTimerIdent, EVFILT_TIMER, EV_ADD | EV_ONESHOT, NOTE_USECONDS | urgency, ToWaitMicroseconds(aDuration), 0, 0, ToWaitMicroseconds(aTolerance)); ApplyChange(mKq.get(), change); { AUTO_PROFILER_THREAD_SLEEP; Unlock(); BlockOnKqueue(mKq.get()); Lock(); } // EV_ONESHOT only removes the timer once it has fired, so drop it in case we // woke on the notification instead. This fails with ENOENT if it did fire. struct kevent64_s remove; EV_SET64(&remove, kTimerIdent, EVFILT_TIMER, EV_DELETE, 0, 0, 0, 0, 0); kevent64(mKq.get(), &remove, 1, nullptr, 0, 0, nullptr); EndWait(); } void TimerThreadMonitor::Notify() { struct kevent64_s change; EV_SET64(&change, kNotifyIdent, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0, 0, 0); ApplyChange(mKq.get(), change); } } // namespace mozilla