summaryrefslogtreecommitdiffstats
path: root/base/message_pump_mac.mm
diff options
context:
space:
mode:
authordmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-30 23:05:27 +0000
committerdmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-30 23:05:27 +0000
commit16808e0de6bf05d814563d4d6712e52d6edaef54 (patch)
tree60a990e6035887ee14cbf7ee7fbd12a6dbb38558 /base/message_pump_mac.mm
parent7644b965283ce3fe0374d5af51ee9710ab86f8db (diff)
downloadchromium_src-16808e0de6bf05d814563d4d6712e52d6edaef54.zip
chromium_src-16808e0de6bf05d814563d4d6712e52d6edaef54.tar.gz
chromium_src-16808e0de6bf05d814563d4d6712e52d6edaef54.tar.bz2
Cleans up our autorelease handling so that we don't create a layered
autorelease pool in our run loop source if there is one already on the stack above us. This allows Cocoa to clean up all the objects at the same time as it expects to do. There may be more "interesting" code that can be removed now that this is in. Initially we were going to implement it by checking the nesting levels of the runloops, but it turns out by the time sendEvent is called at the upper level we are already out of the "CFRunloopRun" call so our nesting count isn't a valid indicator of our state. TEST=1) See bug 25857. 2) Start up. Open 3+ windows. Quit. BUG=25857 Review URL: http://codereview.chromium.org/343024 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30647 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/message_pump_mac.mm')
-rw-r--r--base/message_pump_mac.mm283
1 files changed, 252 insertions, 31 deletions
diff --git a/base/message_pump_mac.mm b/base/message_pump_mac.mm
index b08bdad..fa6c4bd 100644
--- a/base/message_pump_mac.mm
+++ b/base/message_pump_mac.mm
@@ -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 below it in the
+// 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 @@ void NoOp(void* info) {
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 ScopedNSAutoreleasePool because having
+// ScopedNSAutoreleasePool(NSAutoreleasePool*) would just clutter
+// ScopedNSAutoreleasePool'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 @@ bool MessagePumpCFRunLoopBase::RunWork() {
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 @@ bool MessagePumpCFRunLoopBase::RunDelayedWork() {
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 @@ bool MessagePumpCFRunLoopBase::RunIdleWork() {
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::PowerStateNotification(void* info,
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 @@ void MessagePumpNSRunLoop::Quit() {
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,17 +709,121 @@ void MessagePumpNSApplication::Quit() {
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. The event is sent just as the
+ // 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
@@ -698,4 +835,88 @@ MessagePump* MessagePumpMac::Create() {
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_wake_up_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