Chromium Code Reviews| Index: base/message_pump_mac.mm |
| =================================================================== |
| --- base/message_pump_mac.mm (revision 30592) |
| +++ base/message_pump_mac.mm (working copy) |
| @@ -1,4 +1,4 @@ |
| -// Copyright (c) 2008 The Chromium Authors. All rights reserved. |
| +// Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| @@ -11,9 +11,30 @@ |
| #include <limits> |
| +#include "base/logging.h" |
| #include "base/scoped_nsautorelease_pool.h" |
| +#include "base/scoped_nsobject.h" |
| +#include "base/scoped_ptr.h" |
| #include "base/time.h" |
| +// This pool passes the objects stored in it to the pool above it in the |
|
Mark Mentovai
2009/10/30 21:47:04
pool above or pool below? Stacks are usually desc
|
| +// autorelease pool stack when -drain is called. See its implementation |
| +// for all the gory details. |
| +@interface DeferredAutoreleasePool : NSAutoreleasePool { |
| + @private |
| + NSMutableArray* deferredPool_; // Strong. |
| +} |
| +@end |
| + |
| +// Informs the message pump that it has been drained. |
| +@interface MessagePumpNSAppDeferredAutoReleasePool : DeferredAutoreleasePool { |
| + @private |
| + base::MessagePumpNSApplication* pump_; // Weak. |
| +} |
| + |
| +- (id)initWithPump:(base::MessagePumpNSApplication*)pump; |
| +@end |
| + |
| namespace { |
| void NoOp(void* info) { |
| @@ -22,6 +43,24 @@ |
| const CFTimeInterval kCFTimeIntervalMax = |
| std::numeric_limits<CFTimeInterval>::max(); |
| +// Class for dealing with scoping autorelease pools when they need to be a |
| +// special implementation of NSAutoreleasePool. |
| +// This class is independant from scoped_nsautoreleasepool because having |
|
Mark Mentovai
2009/10/30 21:47:04
The name of the class is actually ScopedNSAutorele
|
| +// scoped_nsautoreleasepool(NSAutoreleasePool*) would just clutter |
| +// scoped_nsautoreleasepool's interface, potentially leading to misuse, and the |
| +// case where using an externally created instance of a kind of |
| +// NSAutoreleasePool is so rare. |
| +class AutoreleasePoolOwner { |
| + public: |
| + explicit AutoreleasePoolOwner(NSAutoreleasePool *pool) : pool_(pool) {} |
| + ~AutoreleasePoolOwner() { |
| + [pool_ drain]; |
| + } |
| + private: |
| + NSAutoreleasePool *pool_; |
| + DISALLOW_COPY_AND_ASSIGN(AutoreleasePoolOwner); |
| +}; |
| + |
| } // namespace |
| namespace base { |
| @@ -261,12 +300,7 @@ |
| return false; |
| } |
| - // 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; |
| + AutoreleasePoolOwner pool(CreateAutoreleasePool()); |
| // Call DoWork once, and if something was done, arrange to come back here |
| // again as long as the loop is still running. |
| @@ -295,12 +329,7 @@ |
| return false; |
| } |
| - // 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; |
| + AutoreleasePoolOwner pool(CreateAutoreleasePool()); |
| Time next_time; |
| delegate_->DoDelayedWork(&next_time); |
| @@ -339,12 +368,7 @@ |
| return false; |
| } |
| - // 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; |
| + AutoreleasePoolOwner pool(CreateAutoreleasePool()); |
| // Call DoIdleWork once, and if something was done, arrange to come back here |
| // again as long as the loop is still running. |
| @@ -555,6 +579,14 @@ |
| void MessagePumpCFRunLoopBase::EnterExitRunLoop(CFRunLoopActivity activity) { |
| } |
| +// Called by MessagePumpCFRunLoopBase::RunWork, |
| +// MessagePumpCFRunLoopBase::RunDelayedWork and |
| +// MessagePumpCFRunLoopBase::RunIdleWork. Default implementation is a standard |
| +// NSAutoreleasePool that can be used for eash source as it fires. |
| +NSAutoreleasePool* MessagePumpCFRunLoopBase::CreateAutoreleasePool() { |
| + return [[NSAutoreleasePool alloc] init]; |
| +} |
| + |
| MessagePumpCFRunLoop::MessagePumpCFRunLoop() |
| : quit_pending_(false) { |
| } |
| @@ -638,7 +670,8 @@ |
| MessagePumpNSApplication::MessagePumpNSApplication() |
| : keep_running_(true), |
| - running_own_loop_(false) { |
| + running_own_loop_(false), |
| + needs_event_loop_wake_up_(false) { |
| } |
| void MessagePumpNSApplication::DoRun(Delegate* delegate) { |
| @@ -676,19 +709,123 @@ |
| keep_running_ = false; |
| } |
| - // Send a fake event to wake the loop up. |
| - [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined |
| - location:NSMakePoint(0, 0) |
| - modifierFlags:0 |
| - timestamp:0 |
| - windowNumber:0 |
| - context:NULL |
| - subtype:0 |
| - data1:0 |
| - data2:0] |
| - atStart:NO]; |
| + WakeUpEventLoop(); |
| } |
| +// Called by MessagePumpCFRunLoopBase::EnterExitObserver. |
| +void MessagePumpNSApplication::EnterExitRunLoop(CFRunLoopActivity activity) { |
| + if (activity == kCFRunLoopExit && needs_event_loop_wake_up_) { |
| + WakeUpEventLoop(); |
| + needs_event_loop_wake_up_ = false; |
| + } |
| +} |
| + |
| +NSAutoreleasePool* MessagePumpNSApplication::CreateAutoreleasePool() { |
| + // Return an instance of a special autorelease pool that deals |
| + // correctly with nested autorelease pools in run loops. This is used by |
| + // MessagePumpNSApplication instead of a standard NSAutoreleasePool |
| + // because of how NSApplication interacts with CFRunLoops. |
| + // |
| + // A standard NSApplication event loop looks something like this: |
| + // |
| + // - (void)run { |
| + // while ([self isRunning]) { |
| + // NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; |
| + // NSEvent* event = [self nextEventMatchingMask:NSAnyEventMask |
| + // untilDate:[NSDate distantFuture] |
| + // inMode:NSDefaultRunLoopMode |
| + // dequeue:YES]; |
| + // [self sendEvent:event]; |
| + // [self updateWindows]; |
| + // [pool release]; |
| + // } |
| + // } |
| + // |
| + // Notice that the autorelease pool only gets cleaned up when an NSEvent gets |
| + // returned by nextEventMatchingMask:untilDate:inMode:dequeue: as this is an |
| + // an important point in getting objects released in a timely manner. |
| + // |
| + // Message pumps have a variety of CFRunLoopSources attached to their |
| + // CFRunLoops. These sources can cause actions to run from within |
| + // [NSApplication -nextEventMatchingMask:untilDate:inMode:dequeue:]. These |
| + // actions can call into Objective C code when they fire and add things to the |
| + // autorelease pool. |
| + // |
| + // The problem is that the sources don't necessarily cause |
| + // an NSEvent to be generated and therefore the autorelease pool in |
| + // [NSApplication run] is not necessarily released in a timely fashion. This |
| + // potentially leaves a lot of objects that can be cleaned up sitting around |
| + // taking up memory until something, such as an NSEvent, causes [NSApplication |
| + // run] to release its pool. |
| + // |
| + // The easy answer would seem to be to wrap all of the calls from the |
| + // sources in generic NSAutoreleasePools. Unfortunately, there is a nested |
| + // case where some bit of code can run its own event loop causing the sources |
| + // to fire. In this case, code below the secondary event loop on the stack may |
| + // be depending on objects above the secondary event loop on the stack. These |
| + // objects above the event loop may have autorelease called on them, and then |
| + // they will be released when the source completes. The code below the |
| + // secondary event loop will now be pointing to freed memory. |
| + // |
| + // eg: |
| + // (Several stack frames elided for clarity) |
| + // |
| + // #0 [NSWindowController autorelease] |
| + // #1 DoAClose |
| + // #2 MessagePumpCFRunLoopBase::DoWork() |
| + // #3 [NSRunLoop run] |
| + // #4 [NSButton performClick:] |
| + // #5 [NSWindow sendEvent:] |
| + // #6 [NSApp sendEvent:] |
| + // #7 [NSApp run] |
| + // |
| + // PerformClick: spins a nested run loop. If the pool created in DoWork is a |
| + // standard NSAutoreleasePool, it will release the objects that have been |
| + // autoreleased into it once DoWork releases it. This will cause the window |
| + // controller, which autoreleased itself in frame #0, to release itself, and |
| + // possibly free itself. Unfortunately this window controller controls the |
| + // window in frame #5. When the stack is unwound to frame #5, window no longer |
| + // exists and crashes can occur. |
| + // |
| + // The current solution is to have a deferred autorelease pool that collects |
| + // objects and then passes them up to the next autorelease pool on the |
| + // autorelease pool stack when -drain is called on it. This is handled by the |
| + // code in DeferredAutoReleasePool. This moves the objects to the proper |
| + // autorelease pool, but doesn't drain them from that pool. |
| + // |
| + // The problem then becomes getting the NSApplication event loop to spin to |
| + // release its autorelease pool. This is done by sending an empty event |
| + // through the event loop. This is handled by |
| + // MessagePumpNSAppDeferredAutoReleasePool setting |
| + // a flag in MessagePumpNSApplication, which is acted on by |
| + // MessagePumpNSApplication::EnterExitRunLoop. We send the event just as the |
|
Mark Mentovai
2009/10/30 21:47:04
Final we.
|
| + // MessagePumpNSApplication's run loop is exiting so that the event isn't |
| + // unintentionally swallowed by one of the other sources spinning its own |
| + // event loop. |
| + return [[MessagePumpNSAppDeferredAutoReleasePool alloc] initWithPump:this]; |
| +} |
| + |
| +void MessagePumpNSApplication::WakeUpEventLoop() { |
| + // If there is already an event waiting in the queue, there is no need |
| + // to post another one. |
| + NSString* currentMode = [[NSRunLoop currentRunLoop] currentMode]; |
| + if (![NSApp nextEventMatchingMask:NSAnyEventMask |
| + untilDate:nil |
| + inMode:currentMode |
| + dequeue:NO]) { |
| + NSEvent* wakeUpEvent = [NSEvent otherEventWithType:NSApplicationDefined |
| + location:NSZeroPoint |
| + modifierFlags:0 |
| + timestamp:0 |
| + windowNumber:0 |
| + context:NULL |
| + subtype:0 |
| + data1:0 |
| + data2:0]; |
| + [NSApp postEvent:wakeUpEvent atStart:NO]; |
| + } |
| +} |
| + |
| // static |
| MessagePump* MessagePumpMac::Create() { |
| if ([NSThread isMainThread]) { |
| @@ -698,4 +835,89 @@ |
| return new MessagePumpNSRunLoop; |
| } |
| +// A trampoline to get around problem where ObjC classes/methods cannot be |
| +// friends of C++ classes. |
| +void SetNeedsEventLoopWakeUpTrue(MessagePumpNSApplication* pump) { |
| + pump->set_needs_event_loop_wakeup_true(); |
| +} |
| + |
| } // namespace base |
| + |
| +@implementation DeferredAutoreleasePool |
| + |
| +- (void)addObject:(id)anObject { |
| + // If the GarbageCollector is running, none of this should be necessary |
| + // and it can be tossed out. |
| + DCHECK([NSGarbageCollector defaultCollector] == nil); |
| + if (!deferredPool_) { |
| + // Create an array to use as a store for autoreleased objects. Not created |
| + // in init because the existence of the pool as a flag is used to |
| + // determine if the event loop needs to be sent an event to cause |
| + // the app to release its autorelease pool. |
| + deferredPool_ = [[NSMutableArray alloc] init]; |
| + } |
| + |
| + // Store the object in deferredPool_. Don't call [super addObject] because the |
| + // retain/release is taken care of here. |
| + // When -addObject is called the retain count on anObject is 'n'. When -drain |
| + // is called on a normal NSAutoreleasePool the user expects the retain count |
| + // of anObject to become 'n - 1'. When anObject is added to deferredPool_ the |
| + // array calls -retain so the retain count becomes 'n + 1'. Release is |
| + // called to return it to 'n' so that when anObject is eventually released |
| + // by the NSAutoreleasePool above self in the autorelease pool stack the |
| + // retain count will become 'n - 1' as expected. |
| + [deferredPool_ addObject:anObject]; |
| + [anObject release]; |
| + |
| + // TODO(dmaclach): Is a call to |
| + // NSRecordAllocationEvent(NSObjectAutoreleasedEvent, anObject) needed? I did |
| + // some testing and set some breakpoints and nothing seems to call it. |
| +} |
| + |
| +- (void)drain { |
| + // Store off the address of deferredPool_ because -[super drain] calls |
| + // free on "self" and deferredPool_ is needed after drain. NSAutoreleasePools |
| + // are interesting in that dealloc is never called on them and instead they |
| + // override release and call free directly. This means that deferredPool_ is |
| + // still alive, and would be leaked if we didn't release it, but the memory |
| + // owned by self is dead so even though what deferredPool_ pointed to before |
| + // calling -drain is still valid, the actual pointer value of deferredPool_ |
| + // is potentially garbage. By caching a copy of the pointer to deferredPool_ |
| + // on the stack before calling drain a good reference to deferredPool_ is |
| + // available to be used post [super drain]. |
| + NSMutableArray* deferredPool = deferredPool_; |
| + [super drain]; |
| + |
| + // This autorelease pool is now off the autorelease stack. Calling |
| + // autorelease on deferredPool effectively transfers ownership of the |
| + // objects in the array to the outer autorelease pool. |
| + [deferredPool autorelease]; |
| +} |
| + |
| +// Must call drain on DeferredAutoreleasePools. |
| +- (oneway void)release { |
| + CHECK(false); |
| +} |
| + |
| +@end |
| + |
| + |
| +@implementation MessagePumpNSAppDeferredAutoReleasePool |
| + |
| +- (id)initWithPump:(base::MessagePumpNSApplication*)pump { |
| + if ((self = [super init])) { |
| + pump_ = pump; |
| + } |
| + return self; |
| +} |
| + |
| +- (void)drain { |
| + // Set a flag in pump_ so that it wakes up the event loop when exiting its |
| + // run loop. Calling through a trampoline to get around problem where |
| + // ObjC classes/methods cannot be friends of C++ classes. |
| + base::SetNeedsEventLoopWakeUpTrue(pump_); |
| + pump_ = nil; |
| + [super drain]; |
| +} |
| + |
| +@end |