Is creation of autorelease pool in non main threads not required anymore?

Hello!


The documentation on NSAutoreleasePool contains the following assertion:


If you are making Cocoa calls outside of the Application Kit’s main thread—for example if you create a Foundation-only application or if you detach a thread—you need to create your own autorelease pool.


It looks like yet it is not required anymore. For instance, the look at the code below:

#import <Cocoa/Cocoa.h>
#include <pthread.h>

@interface MyObject : NSObject
@property (copy, nonatomic) NSString* str;
@end
@implementation MyObject


- init {
    self = [super init];
    NSLog(@"init %@", self);
    return self;
}


- (void) dealloc {
    NSLog(@"dealloc %@", self);
    self.str = nil;
    [super dealloc];
}
@end

void* thread_main (void* ptr)
{
    MyObject* obj = (__bridge id)ptr;
    CFAutorelease(ptr);
    NSLog(@"%p:  %@", pthread_self(), obj.str);
    return 0;
}

int main(int argc, const char* argv[argc])
{
    MyObject* obj = [MyObject new];
    obj.str = [@"test string" copy];

    NSLog(@"1: %@", obj.str);

    pthread_t pth;
    if (pthread_create (&pth, NULL, thread_main, (__bridge void*)obj) 
        || pthread_join(pth, NULL))
    {
        perror("pthread: ");
    }
    NSLog(@"2: %@", obj.str);

    return 0;
}


iIf compile it with as following (with MRC):

clang -gfull -fno-objc-arc -o test_autorelease test_autorelease.m -framework Cocoa


it prints

2019-02-20 17:11:40.814 test_autorelease[52994:2010949] init <MyObject: 0x7f8ed760b820>
2019-02-20 17:11:40.814 test_autorelease[52994:2010949] 1: test string
2019-02-20 17:11:40.815 test_autorelease[52994:2010951] 0x700005f17000:  test string
2019-02-20 17:11:40.815 test_autorelease[52994:2010951] dealloc <MyObject: 0x7f8ed760b820>
2019-02-20 17:11:40.815 test_autorelease[52994:2010949] 2: (null)


So, it is clear that after printing the test string in background thread, the object was released and dealloced. If to comment line 27 with call to CFAutorelease(), the ouput is following:

2019-02-20 17:16:03.505 test_autorelease[53053:2015162] init <MyObject: 0x7fccbfc006d0>
2019-02-20 17:16:03.505 test_autorelease[53053:2015162] 1: test string
2019-02-20 17:16:03.505 test_autorelease[53053:2015164] 0x700003be3000:  test string
2019-02-20 17:16:03.505 test_autorelease[53053:2015162] 2: test string


So the object is not deallocated.

It looks like either documentation is not updated, or there is something I've missed.


Could anybody please comment on this?

Post not yet marked as solved Up vote post of ilowry Down vote post of ilowry
2.5k views

Replies

By logging [NSThread callStackSymbols] in the -dealloc method, I see that the current autorelease pool for the thread is tracked using (roughly) pthread_key_create(), pthread_setspecific(), and pthread_getspecific(). Further, a destructor function was apparently registered during the pthread_key_create() call which cleans up the thread's autorelease pools.


I would guess that CFAutorelease() and -[NSObject autorelease] end up creating the autorelease pool on demand and then it gets cleaned up on thread exit by that destructor function.


All of that said, though, this is an implementation detail and is not in the design contract. So, you must code as though it were not true. If you believe it's just an oversight that the documentation hasn't been updated, then file a bug against the docs.

Some time back in the 10.7 days we started automatically setting up an autorelease pool for threads. It’s clear that the documentation hasn’t been updated accordingly, and I’d appreciate you filing a bug about that. Please post your bug number, just for the record.

Ken Thomases wrote:

All of that said, though, this is an implementation detail and is not in the design contract.

In this case I think the intention was for folks to rely on it, and thus this is just a documentation bug.

You still have to be careful here. The autorelease pool doesn’t drain until the thread terminates, and if you create your own long-lived threads then you can suffer from an unbounded amount of autorelease pool buildup. For example, if you have something like this:

static void * mySelectLoop(void *) {
    … set up for select …
    while (1) {
        int readyCount = select(…);
        … dispatch to various select handlers …
    }
}

and the select handlers generate autorelease traffic, you will eventually run out of memory.

Another edge case relates to Dispatch, where the same worker thread is used over and over again to run blocks scheduled on queues. If those blocks create autorelease traffic, that will only be released when the worker thread terminates, which can take a while if there’s work to be done. Thus, when working with Dispatch you should either:

  • Create an autorelease pool inside every block you schedule

  • Learn to love Dispatch constructs like

    DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL
    and
    dispatch_queue_attr_make_with_autorelease_frequency

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
  • I only use dispatch_sync(dispatch_get_main_queue(), ^{...}) and dispatch_async(dispatch_get_main_queue(), ^{...}). So basically I 'm only dispatching to the main thread which should be already configured to auto release objects.

    Do I still need to wrap the block with @autoreleasepool ?

Add a Comment

Hi Ken,


Thank you for this check. I think I should follow your and eskimo's advise and file a bug.

Thank you!

Your answer has sorted things out. The point about a dispatch was are relly helpful, as I missed that. So in my case, it looks like I still should create a pool and can not just rely this behavior.


Here is bug id: 48309206.