Chromium Code Reviews| 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..5556be3f6cd4ff94deb8034493c6e4e7f3a2bf80 100644 |
| --- a/base/message_loop/message_pump_mac.mm |
| +++ b/base/message_loop/message_pump_mac.mm |
| @@ -69,6 +69,74 @@ 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))) |
| + |
| +void ChromeCFSetValid(void* cf) { |
| + __CFBitfieldSetValue(((CFRuntimeBase*)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 1); |
| +} |
| + |
| +void ChromeCFUnsetValid(void* cf) { |
|
Mark Mentovai
2017/02/21 17:41:22
Might be better as a single function that takes a
shrike
2017/02/22 00:57:52
Acknowledged.
|
| + __CFBitfieldSetValue(((CFRuntimeBase*)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 0); |
| +} |
| + |
| +// Marking timers as invalid at the right time helps significantly reduce power |
| +// use (see the explanation in RunDelayedWorkTimer()), however there is no |
| +// public API for doing so. CFRuntime.h states that CFRuntimeBase, upon which |
| +// the above timer invalidation functions are based, can change from release to |
| +// release and should not be accessed directly (this struct last changed in |
| +// 2008 in CF-476). |
|
Mark Mentovai
2017/02/21 18:10:57
Call out 10.5 by name too.
But this is kind of fu
shrike
2017/02/22 00:57:52
I added a unit test to make sure there's no side e
Mark Mentovai
2017/02/23 02:34:36
I’m mean that we’re flipping an internal bit that
shrike
2017/02/23 17:51:11
OK, I agree that there is a small chance of what y
|
| +// |
| +// 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 right bit. |
| +bool CanInvalidateTimers() { |
| + CFRunLoopTimerContext timer_context = CFRunLoopTimerContext(); |
| + timer_context.info = NULL; |
|
Mark Mentovai
2017/02/21 17:41:22
Use nullptr in new Chrome code whenever you have N
shrike
2017/02/22 00:57:52
Thank you. Fixed.
|
| + ScopedCFTypeRef<CFRunLoopTimerRef> test_timer( |
| + CFRunLoopTimerCreate(NULL, // allocator |
| + kCFTimeIntervalMax, // fire time |
| + kCFTimeIntervalMax, // interval |
| + 0, // flags |
| + 0, // priority |
| + NULL, &timer_context)); |
| + // Should be valid from the start. |
| + if (!CFRunLoopTimerIsValid(test_timer)) { |
| + return false; |
| + } |
| + // Confirm that the private API can mark the timer invalid. |
| + ChromeCFUnsetValid(test_timer); |
| + if (CFRunLoopTimerIsValid(test_timer)) { |
| + return false; |
| + } |
| + // Confirm that the private API can mark the timer valid. |
| + ChromeCFSetValid(test_timer); |
| + return CFRunLoopTimerIsValid(test_timer); |
| +} |
| #endif |
| } // namespace |
| @@ -99,11 +167,16 @@ MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() |
| : delegate_(NULL), |
| delayed_work_fire_time_(kCFTimeIntervalMax), |
| timer_slack_(base::TIMER_SLACK_NONE), |
| + can_invalidate_timers_(false), |
|
Mark Mentovai
2017/02/21 17:41:22
Might as well wrap this and the member variable in
shrike
2017/02/22 00:57:52
This has been removed, per your feedback below.
|
| nesting_level_(0), |
| run_nesting_level_(0), |
| deepest_nesting_level_(0), |
| delegateless_work_(false), |
| delegateless_idle_work_(false) { |
| +#if !defined(OS_IOS) |
| + can_invalidate_timers_ = CanInvalidateTimers(); |
| +#endif // !defined(OS_IOS) |
| + |
| run_loop_ = CFRunLoopGetCurrent(); |
| CFRetain(run_loop_); |
| @@ -243,6 +316,12 @@ void MessagePumpCFRunLoopBase::ScheduleDelayedWork( |
| const TimeTicks& delayed_work_time) { |
| TimeDelta delta = delayed_work_time - TimeTicks::Now(); |
| delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF(); |
| +#if !defined(OS_IOS) |
| + // Please see the comment in RunDelayedWorkTimer() for more info. |
| + if (can_invalidate_timers_) { |
|
Mark Mentovai
2017/02/21 17:41:22
Actually, I think that we can make this unconditio
shrike
2017/02/22 00:57:52
Got it. That's clean :-).
|
| + ChromeCFSetValid(delayed_work_timer_); |
|
Mark Mentovai
2017/02/21 17:41:22
Should this happen before (as done here) or after
shrike
2017/02/22 00:57:52
Done.
|
| + } |
| +#endif // !defined(OS_IOS) |
| CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_); |
| if (timer_slack_ == TIMER_SLACK_MAXIMUM) { |
| CFRunLoopTimerSetTolerance(delayed_work_timer_, delta.InSecondsF() * 0.5); |
| @@ -264,6 +343,35 @@ void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer, |
| // The timer won't fire again until it's reset. |
| self->delayed_work_fire_time_ = kCFTimeIntervalMax; |
| +#if !defined(OS_IOS) |
| + // 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 invaildate 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. |
| + if (self->can_invalidate_timers_) { |
| + ChromeCFUnsetValid(self->delayed_work_timer_); |
| + } |
| +#endif // !defined(OS_IOS) |
| + |
| // 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 |