diff options
author | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-28 14:09:31 +0000 |
---|---|---|
committer | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-28 14:09:31 +0000 |
commit | a9527c20280a76efa015cbc0343e8a32375dab5b (patch) | |
tree | 94656099ed4a36384d27bd75092d0c2d9ff7b57f /base/message_pump_mac.mm | |
parent | a14b16bd4b6d7fc3c1e3e83f16d1d59bc05f3dab (diff) | |
download | chromium_src-a9527c20280a76efa015cbc0343e8a32375dab5b.zip chromium_src-a9527c20280a76efa015cbc0343e8a32375dab5b.tar.gz chromium_src-a9527c20280a76efa015cbc0343e8a32375dab5b.tar.bz2 |
Reset the Mac message pump's delayed work timer upon waking from system sleep.
CFRunLoopTimers are implemented in terms of kernel ticks, a time base which is
suspended while the system is sleeping. In order to properly perform delayed
work at the proper initially-scheduled time (or at system wake if the
scheduled time passed while the system was asleep), the associated timer needs
to be reset to the most recently scheduled next-fire time on wake from sleep.
BUG=22508
TEST= - Make Chrome the active application.
- Close all Chrome windows.
- Close the laptop lid.
- Wait for it to begin sleeping, indicated by the pulsing light. NOTE:
wait until the light actually begins pulsing, not just until it turns
on. The disk and fans should all be stopped.
- Reopen the laptop. If needed, authenticate to the screen saver.
- Click the Chrome icon in the dock. A new window should open on the
New Tab page.
- Quickly type "google.com/" into the omnibox.
- The page should load, its favicon should be visible, and the tab title
should read "Google". This should happen as instantly as a normal
page load. The tab title should not "stick" on "New Tab" and the icon
load should not be delayed for any amount of time.
Review URL: http://codereview.chromium.org/342011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30335 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/message_pump_mac.mm')
-rw-r--r-- | base/message_pump_mac.mm | 100 |
1 files changed, 98 insertions, 2 deletions
diff --git a/base/message_pump_mac.mm b/base/message_pump_mac.mm index 9af77fa..b08bdad 100644 --- a/base/message_pump_mac.mm +++ b/base/message_pump_mac.mm @@ -6,6 +6,8 @@ #import <AppKit/AppKit.h> #import <Foundation/Foundation.h> +#include <IOKit/IOMessage.h> +#include <IOKit/pwr_mgt/IOPMLib.h> #include <limits> @@ -27,6 +29,7 @@ namespace base { // Must be called on the run loop thread. MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() : delegate_(NULL), + delayed_work_fire_time_(kCFTimeIntervalMax), nesting_level_(0), run_nesting_level_(0), deepest_nesting_level_(0), @@ -103,12 +106,33 @@ MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() EnterExitObserver, &observer_context); CFRunLoopAddObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes); + + root_power_domain_ = IORegisterForSystemPower(this, + &power_notification_port_, + PowerStateNotification, + &power_notification_object_); + if (root_power_domain_ != MACH_PORT_NULL) { + CFRunLoopAddSource( + run_loop_, + IONotificationPortGetRunLoopSource(power_notification_port_), + kCFRunLoopCommonModes); + } } // Ideally called on the run loop thread. If other run loops were running // lower on the run loop thread's stack when this object was created, the // same number of run loops must be running when this object is destroyed. MessagePumpCFRunLoopBase::~MessagePumpCFRunLoopBase() { + if (root_power_domain_ != MACH_PORT_NULL) { + CFRunLoopRemoveSource( + run_loop_, + IONotificationPortGetRunLoopSource(power_notification_port_), + kCFRunLoopCommonModes); + IODeregisterForSystemPower(&power_notification_object_); + IOServiceClose(root_power_domain_); + IONotificationPortDestroy(power_notification_port_); + } + CFRunLoopRemoveObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes); CFRelease(enter_exit_observer_); @@ -198,9 +222,9 @@ void MessagePumpCFRunLoopBase::ScheduleDelayedWork( exploded.minute, seconds }; - CFAbsoluteTime fire_time = CFGregorianDateGetAbsoluteTime(gregorian, NULL); + delayed_work_fire_time_ = CFGregorianDateGetAbsoluteTime(gregorian, NULL); - CFRunLoopTimerSetNextFireDate(delayed_work_timer_, fire_time); + CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_); } // Called from the run loop. @@ -209,6 +233,9 @@ void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer, void* info) { MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); + // The timer won't fire again until it's reset. + self->delayed_work_fire_time_ = kCFTimeIntervalMax; + // CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources. // In order to establish the proper priority where delegate_->DoDelayedWork // can only be called if delegate_->DoWork returns false, the timer used @@ -427,6 +454,7 @@ void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, self->deepest_nesting_level_ = self->nesting_level_; } break; + case kCFRunLoopExit: // Not all run loops go to sleep. If a run loop is stopped before it // goes to sleep due to a CFRunLoopStop call, or if the timeout passed @@ -447,6 +475,7 @@ void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, self->MaybeScheduleNestingDeferredWork(); --self->nesting_level_; break; + default: break; } @@ -454,6 +483,73 @@ void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, self->EnterExitRunLoop(activity); } +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::PowerStateNotification(void* info, + io_service_t service, + uint32_t message_type, + void* message_argument) { + // CFRunLoopTimer (NSTimer) is scheduled in terms of CFAbsoluteTime, which + // measures the number of seconds since 2001-01-01 00:00:00.0 Z. It is + // implemented in terms of kernel ticks, as in mach_absolute_time. While an + // offset and scale factor can be applied to convert between the two time + // bases at any time after boot, the kernel clock stops while the system is + // asleep, altering the offset. (The offset will also change when the + // real-time clock is adjusted.) CFRunLoopTimers are not readjusted to take + // this into account when the system wakes up, so any timers that were + // pending while the system was asleep will be delayed by the sleep + // duration. + // + // The MessagePump interface assumes that scheduled delayed work will be + // performed at the time ScheduleDelayedWork was asked to perform it. The + // delay caused by the CFRunLoopTimer not firing at the appropriate time + // results in a stall of queued delayed work when the system wakes up. + // With this limitation, scheduled work would not be performed until + // (system wake time + scheduled work time - system sleep time), while it + // would be expected to be performed at (scheduled work time). + // + // To work around this problem, when the system wakes up from sleep, if a + // delayed work timer is pending, it is rescheduled to fire at the original + // time that it was scheduled to fire. + // + // This mechanism is not resilient if the real-time clock does not maintain + // stable time while the system is sleeping, but it matches the behavior of + // the various other MessagePump implementations, and MessageLoop seems to + // be limited in the same way. + // + // References + // - Chris Kane, "NSTimer and deep sleep," cocoa-dev@lists.apple.com, + // http://lists.apple.com/archives/Cocoa-dev/2002/May/msg01547.html + // - Apple Technical Q&A QA1340, "Registering and unregistering for sleep + // and wake notifications," + // http://developer.apple.com/mac/library/qa/qa2004/qa1340.html + // - Core Foundation source code, CF-550/CFRunLoop.c and CF-550/CFDate.c, + // http://www.opensource.apple.com/ + + MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); + + switch (message_type) { + case kIOMessageSystemWillPowerOn: + if (self->delayed_work_fire_time_ != kCFTimeIntervalMax) { + CFRunLoopTimerSetNextFireDate(self->delayed_work_timer_, + self->delayed_work_fire_time_); + } + break; + + case kIOMessageSystemWillSleep: + case kIOMessageCanSystemSleep: + // The system will wait for 30 seconds before entering sleep if neither + // IOAllowPowerChange nor IOCancelPowerChange are called. That would be + // pretty antisocial. + IOAllowPowerChange(self->root_power_domain_, + reinterpret_cast<long>(message_argument)); + break; + + default: + break; + } +} + // Called by MessagePumpCFRunLoopBase::EnterExitRunLoop. The default // implementation is a no-op. void MessagePumpCFRunLoopBase::EnterExitRunLoop(CFRunLoopActivity activity) { |