diff options
author | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-22 01:47:49 +0000 |
---|---|---|
committer | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-22 01:47:49 +0000 |
commit | 8670fe5e294c77abcd2d488bfaf06da7edf8e4bc (patch) | |
tree | 6a59432d7cc26980febba517355d67d8b3714e30 /base/message_pump_mac.mm | |
parent | 8a425b86da0ee610854f00e0900326f34dfbd363 (diff) | |
download | chromium_src-8670fe5e294c77abcd2d488bfaf06da7edf8e4bc.zip chromium_src-8670fe5e294c77abcd2d488bfaf06da7edf8e4bc.tar.gz chromium_src-8670fe5e294c77abcd2d488bfaf06da7edf8e4bc.tar.bz2 |
Change the strategy used to attempt nesting-deferred work to account equally
well for nested run loops under our own control (those started by Run) and
those beyond our control (native event loops).
Previously, upon any exit from a nested native loop, we would schedule
nesting-deferred work for processing. However, some nested native loops do
not execute in a single loop; rather, they start and stop the CFRunLoop
rapidly. In such cases, each exit from the CFRunLoop would cause
nesting-deferred work to be scheduled, and on each new entry to the CFRunLoop,
we would attempt to process it. This rapid-fire action meant that we'd never
sleep. Nested loops managed by Run were exempt from these problems since
r28811, because we could defer scheduling nesting-deferred work until
returning to Run.
The new strategy is to detect whether any nested loops (native or managed by
Run) had run before the run loop goes to sleep or exits. If any nested loops
did run, nesting-deferred work is scheduled for processing.
BUG=24968
TEST=1. Test case from bug 24968, printing: open any page, press command-P,
leave the dialog up, and check Chrome's CPU usage. No Chrome process
should be monopolizing any CPU. This tests nested native run loops.
2. Test case from bug 24337, JS alerts: open Gmail, start composing a new
message in a new window, address it to yourself, move focus to the
subject field, click the Discard button, and click "OK" at the alert.
The alert and composition window should close.
3. Test case from bug 24383, JS alerts: no Chrome processes should use
100% CPU after visiting javascript:alert("hi"). The JS alert cases
test nested run loops managed by Run.
4. Test case from bug 13468 comment 5, autocomplete: autocomplete should
still work after using "Back" from a contextual menu. This tests
that nesting-deferred work is processed after leaving a nested run
loop.
5. First run UI test case (no bug). Remove or move aside the profile
directory (~/Library/Application Support/Chromium or
~/Library/Application Support/Google/Chrome) and launch the
application. There should be first-run UI and it should be usable.
Upon clicking "Start (application)", the application should start and
be usable as normal. This tests delegateless run loops and
delegateless work redispatch.
6. base_unittests --gtest_filter='MessageLoopTest.*'
Review URL: http://codereview.chromium.org/300044
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@29749 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/message_pump_mac.mm')
-rw-r--r-- | base/message_pump_mac.mm | 145 |
1 files changed, 87 insertions, 58 deletions
diff --git a/base/message_pump_mac.mm b/base/message_pump_mac.mm index 8ed7491..3c448a0 100644 --- a/base/message_pump_mac.mm +++ b/base/message_pump_mac.mm @@ -22,13 +22,13 @@ namespace base { // Must be called on the run loop thread. MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() - : nesting_level_(0), + : delegate_(NULL), + nesting_level_(0), run_nesting_level_(0), - delegate_(NULL), + deepest_nesting_level_(0), delegateless_work_(false), delegateless_delayed_work_(false), - delegateless_idle_work_(false) - { + delegateless_idle_work_(false) { run_loop_ = CFRunLoopGetCurrent(); CFRetain(run_loop_); @@ -134,28 +134,26 @@ void MessagePumpCFRunLoopBase::Run(Delegate* delegate) { Delegate* last_delegate = delegate_; delegate_ = delegate; - // If any work showed up but could not be dispatched for want of a delegate, - // set it up for dispatch again now that a delegate is available. - if (delegateless_work_) { - CFRunLoopSourceSignal(work_source_); - delegateless_work_ = false; - } - if (delegateless_delayed_work_) { - CFRunLoopSourceSignal(delayed_work_source_); - delegateless_delayed_work_ = false; - } - if (delegateless_idle_work_) { - CFRunLoopSourceSignal(idle_work_source_); - delegateless_idle_work_ = false; + if (delegate) { + // If any work showed up but could not be dispatched for want of a + // delegate, set it up for dispatch again now that a delegate is + // available. + if (delegateless_work_) { + CFRunLoopSourceSignal(work_source_); + delegateless_work_ = false; + } + if (delegateless_delayed_work_) { + CFRunLoopSourceSignal(delayed_work_source_); + delegateless_delayed_work_ = false; + } + if (delegateless_idle_work_) { + CFRunLoopSourceSignal(idle_work_source_); + delegateless_idle_work_ = false; + } } DoRun(delegate); - // If this was an inner Run invocation, arrange to run nesting-deferred work - // when the stack has unwound to an outer invocation. - if (nesting_level_) - CFRunLoopSourceSignal(nesting_deferred_work_source_); - // Restore the previous state of the object. delegate_ = last_delegate; run_nesting_level_ = last_run_nesting_level; @@ -220,9 +218,11 @@ bool MessagePumpCFRunLoopBase::RunWork() { return false; } - // If we're on the main event loop, the NSApp runloop won't clean up the - // autorelease pool until there is a UI event, so use a local one for any - // autoreleased objects to ensure they go away sooner. + // The NSApplication-based run loop only drains the autorelease pool at each + // UI event (NSEvent). The autorelease pool is not drained for each + // CFRunLoopSource target that's run. Use a local pool for any autoreleased + // objects to ensure they're released promptly even in the absence of UI + // events. ScopedNSAutoreleasePool autorelease_pool; // Call DoWork once, and if something was done, arrange to come back here @@ -252,9 +252,11 @@ bool MessagePumpCFRunLoopBase::RunDelayedWork() { return false; } - // If we're on the main event loop, the NSApp runloop won't clean up the - // autorelease pool until there is a UI event, so use a local one for any - // autoreleased objects to ensure they go away sooner. + // The NSApplication-based run loop only drains the autorelease pool at each + // UI event (NSEvent). The autorelease pool is not drained for each + // CFRunLoopSource target that's run. Use a local pool for any autoreleased + // objects to ensure they're released promptly even in the absence of UI + // events. ScopedNSAutoreleasePool autorelease_pool; Time next_time; @@ -294,9 +296,11 @@ bool MessagePumpCFRunLoopBase::RunIdleWork() { return false; } - // If we're on the main event loop, the NSApp runloop won't clean up the - // autorelease pool until there is a UI event, so use a local one for any - // autoreleased objects to ensure they go away sooner. + // The NSApplication-based run loop only drains the autorelease pool at each + // UI event (NSEvent). The autorelease pool is not drained for each + // CFRunLoopSource target that's run. Use a local pool for any autoreleased + // objects to ensure they're released promptly even in the absence of UI + // events. ScopedNSAutoreleasePool autorelease_pool; // Call DoIdleWork once, and if something was done, arrange to come back here @@ -347,6 +351,20 @@ bool MessagePumpCFRunLoopBase::RunNestingDeferredWork() { return true; } +// Called before the run loop goes to sleep or exits. +void MessagePumpCFRunLoopBase::MaybeScheduleNestingDeferredWork() { + // deepest_nesting_level_ is set as run loops are entered. If the deepest + // level encountered is deeper than the current level (about to sleep or + // exit), a nested loop (relative to the current level) ran since the last + // time nesting-deferred work was scheduled. When that situation is + // encountered, schedule nesting-deferred work in case any work was deferred + // because nested work was disallowed. + if (deepest_nesting_level_ > nesting_level_) { + deepest_nesting_level_ = nesting_level_; + CFRunLoopSourceSignal(nesting_deferred_work_source_); + } +} + // Called from the run loop. // static void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer, @@ -356,6 +374,12 @@ void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer, // Attempt to do some idle work before going to sleep. self->RunIdleWork(); + + // The run loop is about to go to sleep. If any of the work done since it + // started or woke up resulted in a nested run loop running, + // nesting-deferred work may have accumulated. Schedule it for processing + // if appropriate. + self->MaybeScheduleNestingDeferredWork(); } // Called from the run loop. @@ -368,24 +392,29 @@ void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, switch (activity) { case kCFRunLoopEntry: ++self->nesting_level_; + if (self->nesting_level_ > self->deepest_nesting_level_) { + self->deepest_nesting_level_ = self->nesting_level_; + } break; case kCFRunLoopExit: - // After decrementing self->nesting_level_, it will be one less than - // self->run_nesting_level_ if the loop that is now exiting was directly - // started by a DoRun call. + // 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 + // to CFRunLoopRunInMode expires, the run loop may proceed directly from + // handling sources to exiting without any sleep. This most commonly + // occurs when CFRunLoopRunInMode is passed a timeout of 0, causing it + // to make a single pass through the loop and exit without sleep. Some + // native loops use CFRunLoop in this way. Because PreWaitObserver will + // not be called in these case, MaybeScheduleNestingDeferredWork needs + // to be called here, as the run loop exits. + // + // MaybeScheduleNestingDeferredWork consults self->nesting_level_ + // to determine whether to schedule nesting-deferred work. It expects + // the nesting level to be set to the depth of the loop that is going + // to sleep or exiting. It must be called before decrementing the + // value so that the value still corresponds to the level of the exiting + // loop. + self->MaybeScheduleNestingDeferredWork(); --self->nesting_level_; - - if (self->nesting_level_ >= self->run_nesting_level_ && - self->nesting_level_) { - // It's possible that some work was not performed because it was - // inappropriate to do within a nested loop. When leaving any inner - // loop not directly supervised by a DoRun call, such as nested native - // loops, signal the nesting-deferred work source to ensure that such - // work be afforded an opportunity to be processed if appropriate. - // This is not done for loops being run directly by Run/DoRun because - // it can be done directly as Run exits. - CFRunLoopSourceSignal(self->nesting_deferred_work_source_); - } break; default: break; @@ -420,15 +449,15 @@ void MessagePumpCFRunLoop::DoRun(Delegate* delegate) { // Must be called on the run loop thread. void MessagePumpCFRunLoop::Quit() { // Stop the innermost run loop managed by this MessagePumpCFRunLoop object. - if (nesting_level_ == run_nesting_level_) { + if (nesting_level() == run_nesting_level()) { // This object is running the innermost loop, just stop it. - CFRunLoopStop(run_loop_); + CFRunLoopStop(run_loop()); } else { // There's another loop running inside the loop managed by this object. - // In other words, someone else called CFRunLoopRun on the same thread, - // higher on the stack than our highest Run call. Don't preempt other - // run loops, just mark the object to quit our innermost run loop as soon - // as the other inner loops we don't manage are done. + // In other words, someone else called CFRunLoopRunInMode on the same + // thread, deeper on the stack than the deepest Run call. Don't preempt + // other run loops, just mark this object to quit the innermost Run as + // soon as the other inner loops not managed by Run are done. quit_pending_ = true; } } @@ -436,13 +465,13 @@ void MessagePumpCFRunLoop::Quit() { // Called by MessagePumpCFRunLoopBase::EnterExitObserver. void MessagePumpCFRunLoop::EnterExitRunLoop(CFRunLoopActivity activity) { if (activity == kCFRunLoopExit && - nesting_level_ == run_nesting_level_ && + nesting_level() == run_nesting_level() && quit_pending_) { // Quit was called while loops other than those managed by this object // were running further inside a run loop managed by this object. Now // that all unmanaged inner run loops are gone, stop the loop running // just inside Run. - CFRunLoopStop(run_loop_); + CFRunLoopStop(run_loop()); quit_pending_ = false; } } @@ -454,11 +483,11 @@ MessagePumpNSRunLoop::MessagePumpNSRunLoop() quit_source_ = CFRunLoopSourceCreate(NULL, // allocator 0, // priority &source_context); - CFRunLoopAddSource(run_loop_, quit_source_, kCFRunLoopCommonModes); + CFRunLoopAddSource(run_loop(), quit_source_, kCFRunLoopCommonModes); } MessagePumpNSRunLoop::~MessagePumpNSRunLoop() { - CFRunLoopRemoveSource(run_loop_, quit_source_, kCFRunLoopCommonModes); + CFRunLoopRemoveSource(run_loop(), quit_source_, kCFRunLoopCommonModes); CFRelease(quit_source_); } @@ -475,7 +504,7 @@ void MessagePumpNSRunLoop::DoRun(Delegate* delegate) { void MessagePumpNSRunLoop::Quit() { keep_running_ = false; CFRunLoopSourceSignal(quit_source_); - CFRunLoopWakeUp(run_loop_); + CFRunLoopWakeUp(run_loop()); } MessagePumpNSApplication::MessagePumpNSApplication() @@ -519,7 +548,7 @@ void MessagePumpNSApplication::Quit() { // Send a fake event to wake the loop up. [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined - location:NSMakePoint(0,0) + location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 |