| Index: base/message_loop/message_pump_mac.mm
|
| diff --git a/base/message_loop/message_pump_mac.mm b/base/message_loop/message_pump_mac.mm
|
| index d924ead7cbd9b934341ab6314d059fe769c172fa..82d9f1e818b18c07af4b3ec2ac5e785de43af61d 100644
|
| --- a/base/message_loop/message_pump_mac.mm
|
| +++ b/base/message_loop/message_pump_mac.mm
|
| @@ -69,8 +69,49 @@ const CFTimeInterval kCFTimeIntervalMax =
|
| // Set to true if MessagePumpMac::Create() is called before NSApp is
|
| // initialized. Only accessed from the main thread.
|
| bool g_not_using_cr_app = false;
|
| +
|
| +// Various CoreFoundation definitions.
|
| +typedef struct __CFRuntimeBase {
|
| + uintptr_t _cfisa;
|
| + uint8_t _cfinfo[4];
|
| +#if __LP64__
|
| + uint32_t _rc;
|
| +#endif
|
| +} CFRuntimeBase;
|
| +
|
| +#if defined(__BIG_ENDIAN__)
|
| +#define __CF_BIG_ENDIAN__ 1
|
| +#define __CF_LITTLE_ENDIAN__ 0
|
| #endif
|
|
|
| +#if defined(__LITTLE_ENDIAN__)
|
| +#define __CF_LITTLE_ENDIAN__ 1
|
| +#define __CF_BIG_ENDIAN__ 0
|
| +#endif
|
| +
|
| +#define CF_INFO_BITS (!!(__CF_BIG_ENDIAN__)*3)
|
| +
|
| +#define __CFBitfieldMask(N1, N2) \
|
| + ((((UInt32)~0UL) << (31UL - (N1) + (N2))) >> (31UL - N1))
|
| +#define __CFBitfieldSetValue(V, N1, N2, X) \
|
| + ((V) = ((V) & ~__CFBitfieldMask(N1, N2)) | \
|
| + (((X) << (N2)) & __CFBitfieldMask(N1, N2)))
|
| +
|
| +// Marking timers as invalid at the right time by flipping their valid bit helps
|
| +// significantly reduce power use (see the explanation in
|
| +// RunDelayedWorkTimer()), however there is no public API for doing so.
|
| +// CFRuntime.h states that CFRuntimeBase can change from release to release
|
| +// and should not be accessed directly. The last known change of this struct
|
| +// occurred in 2008 in CF-476 / 10.5; unfortunately the source for 10.11 and
|
| +// 10.12 is not available for inspection at this time.
|
| +// CanInvalidateCFRunLoopTimers() will at least prevent us from invalidating
|
| +// timers if this function starts flipping the wrong bit on a future OS release.
|
| +void __ChromeCFRunLoopTimerSetValid(CFRunLoopTimerRef timer, bool valid) {
|
| + __CFBitfieldSetValue(((CFRuntimeBase*)timer)->_cfinfo[CF_INFO_BITS], 3, 3,
|
| + valid);
|
| +}
|
| +#endif // !defined(OS_IOS)
|
| +
|
| } // namespace
|
|
|
| // static
|
| @@ -94,6 +135,47 @@ class MessagePumpScopedAutoreleasePool {
|
| DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool);
|
| };
|
|
|
| +#if !defined(OS_IOS)
|
| +// This function uses private API to modify a test timer's valid state and
|
| +// uses public API to confirm that the private API changed the correct bit.
|
| +// static
|
| +bool MessagePumpCFRunLoopBase::CanInvalidateCFRunLoopTimers() {
|
| + CFRunLoopTimerContext timer_context = CFRunLoopTimerContext();
|
| + timer_context.info = nullptr;
|
| + ScopedCFTypeRef<CFRunLoopTimerRef> test_timer(
|
| + CFRunLoopTimerCreate(NULL, // allocator
|
| + kCFTimeIntervalMax, // fire time
|
| + kCFTimeIntervalMax, // interval
|
| + 0, // flags
|
| + 0, // priority
|
| + nullptr, &timer_context));
|
| + // Should be valid from the start.
|
| + if (!CFRunLoopTimerIsValid(test_timer)) {
|
| + return false;
|
| + }
|
| + // Confirm that the private API can mark the timer invalid.
|
| + __ChromeCFRunLoopTimerSetValid(test_timer, false);
|
| + if (CFRunLoopTimerIsValid(test_timer)) {
|
| + return false;
|
| + }
|
| + // Confirm that the private API can mark the timer valid.
|
| + __ChromeCFRunLoopTimerSetValid(test_timer, true);
|
| + return CFRunLoopTimerIsValid(test_timer);
|
| +}
|
| +#endif // !defined(OS_IOS)
|
| +
|
| +// static
|
| +void MessagePumpCFRunLoopBase::ChromeCFRunLoopTimerSetValid(
|
| + CFRunLoopTimerRef timer,
|
| + bool valid) {
|
| +#if !defined(OS_IOS)
|
| + static bool can_invalidate_timers = CanInvalidateCFRunLoopTimers();
|
| + if (can_invalidate_timers) {
|
| + __ChromeCFRunLoopTimerSetValid(timer, valid);
|
| + }
|
| +#endif // !defined(OS_IOS)
|
| +}
|
| +
|
| // Must be called on the run loop thread.
|
| MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase()
|
| : delegate_(NULL),
|
| @@ -243,6 +325,17 @@ void MessagePumpCFRunLoopBase::ScheduleDelayedWork(
|
| const TimeTicks& delayed_work_time) {
|
| TimeDelta delta = delayed_work_time - TimeTicks::Now();
|
| delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF();
|
| +
|
| + // Flip the timer's validation bit just before setting the new fire time. Do
|
| + // this now because CFRunLoopTimerSetNextFireDate() likely checks the validity
|
| + // of a timer before proceeding to set its fire date. Making the timer valid
|
| + // now won't have any side effects (such as a premature firing of the timer)
|
| + // because we're only flipping a bit.
|
| + //
|
| + // Please see the comment in RunDelayedWorkTimer() for more info on the whys
|
| + // of invalidation.
|
| + ChromeCFRunLoopTimerSetValid(delayed_work_timer_, true);
|
| +
|
| CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_);
|
| if (timer_slack_ == TIMER_SLACK_MAXIMUM) {
|
| CFRunLoopTimerSetTolerance(delayed_work_timer_, delta.InSecondsF() * 0.5);
|
| @@ -264,6 +357,31 @@ void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer,
|
| // The timer won't fire again until it's reset.
|
| self->delayed_work_fire_time_ = kCFTimeIntervalMax;
|
|
|
| + // The message pump's timer needs to fire at changing and unpredictable
|
| + // intervals. Creating a new timer for each firing time is very expensive, so
|
| + // the message pump instead uses a repeating timer with a very large repeat
|
| + // rate. After each firing of the timer, the run loop sets the timer's next
|
| + // firing time to the distant future, essentially pausing the timer until the
|
| + // pump sets the next firing time. This is the solution recommended by Apple.
|
| + //
|
| + // It turns out, however, that scheduling timers is also quite expensive, and
|
| + // that every one of the message pump's timer firings incurs two
|
| + // reschedulings. The first rescheduling occurs in ScheduleDelayedWork(),
|
| + // which sets the desired next firing time. The second comes after exiting
|
| + // this method (the timer's callback method), when the run loop sets the
|
| + // timer's next firing time to far in the future.
|
| + //
|
| + // The code in __CFRunLoopDoTimer() inside CFRunLoop.c calls the timer's
|
| + // callback, confirms that the timer is valid, and then sets its future
|
| + // firing time based on its repeat frequency. Flipping the valid bit here
|
| + // causes the __CFRunLoopDoTimer() to skip setting the future firing time.
|
| + // Note that there's public API to invalidate a timer but it goes beyond
|
| + // flipping the valid bit, making the timer unusable in the future.
|
| + //
|
| + // ScheduleDelayedWork() flips the valid bit back just before setting the
|
| + // timer's new firing time.
|
| + ChromeCFRunLoopTimerSetValid(self->delayed_work_timer_, false);
|
| +
|
| // CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources.
|
| // In order to establish the proper priority in which work and delayed work
|
| // are processed one for one, the timer used to schedule delayed work must
|
|
|