leak memory when NSRunloop and __block variable in background thread

I have block code:


dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    //__block BOOL waitingLocation = YES;
    //code abczyz


    while (!self.isFinished) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0f]];
    }
});


This code run no problem.

But when I open comment line __block BOOL waitingLocation = YES; and run. Memory continuous increase until self.isFinished = YES; (about 1s increase 10MB). Why memory increase when I open comment line __block BOOL waitingLocation = YES. Is use __block with NSRunLoop wrong in this case?

Accepted Reply

To give you a definitive answer I’d need to look at this in a lot more detail, and I just don’t have time to do that in the context of DevForums. If you’d like to go down that path, please open a DTS tech support incident.

My best guess right now is that you have autorelease problems. There’s two potential sources for this:

  • Within the run loop (A)

  • Within the dispatch queue (B)

With regards A, you can reasonable expect the run loop to include an autorelease pool inside the run loop. However, adding your own is unlikely to hurt, so it’s worth changing your look to look like this:

while (!self.isFinished) {
    @autoreleasepool {
        [[NSRunLoop currentRunLoop] runMode:…];
    }
}

With regards B, this is a particularly tricky issue. For a while now (OS X 10.7?) every thread has included an autorelease pool at the top of the thread, so you no longer just leak memory if you autorelease something without first setting up a pool. The gotcha relates to dispatch. Dispatch uses a set of worker threads to service its queues, and if a worker thread is busy it never shuts down and thus never releases its autorelease pool. Recently we added a bunch of infrastructure to help with this. Open up

<dispatch/queue.h>
and search for “autorelease” to learn more.

In your case you’re using dispatch directly so you might be able to avoid this problem using an autorelease pool at the top of your block. Like this:

dispatch_async(…, ^{  
    @autoreleasepool {
        …
    }
});

My recommendation is that you make both of these changes and see how that affects things.

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Reading through your question there’s a few points I’d like to clarify:

  • In your title you say “leak” but in the body of your post it seems like you’re talking about memory growth. Is this actually a leak, as shown by the Leaks instrument? Or is the memory recovered when the run loop ends and you return from the block?

  • With regards your

    __block
    variable, as you saying that:
    • The code with

      waitingLocation
      triggers the memory growth?
    • The code without

      waitingLocation
      triggers the memory growth?
  • What does:

    //code abczyz

    mean? Is that some other code that you’ve elided for the sake of brevity? If so, what does that code do?

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for your response.

1. This is not "leak". It's just memory growth.

2. With regards __block variable. The code with

waitingLocation
triggers the memory growth.

3. //code abczyz is unnecessary. You can delete it. and memory still growth.


I know. If I add a timer to currentRunLoop, memory growth is not occur (refer: https://stackoverflow.com/questions/10245365/nsrunloop-is-consuming-a-lot-of-cpu-and-memory). But I want explain why __block variable triggers the memory growth? If I remove line __block BOOL waitingLocation = YES, memory is not growth. Is it issue of objective-C?

To give you a definitive answer I’d need to look at this in a lot more detail, and I just don’t have time to do that in the context of DevForums. If you’d like to go down that path, please open a DTS tech support incident.

My best guess right now is that you have autorelease problems. There’s two potential sources for this:

  • Within the run loop (A)

  • Within the dispatch queue (B)

With regards A, you can reasonable expect the run loop to include an autorelease pool inside the run loop. However, adding your own is unlikely to hurt, so it’s worth changing your look to look like this:

while (!self.isFinished) {
    @autoreleasepool {
        [[NSRunLoop currentRunLoop] runMode:…];
    }
}

With regards B, this is a particularly tricky issue. For a while now (OS X 10.7?) every thread has included an autorelease pool at the top of the thread, so you no longer just leak memory if you autorelease something without first setting up a pool. The gotcha relates to dispatch. Dispatch uses a set of worker threads to service its queues, and if a worker thread is busy it never shuts down and thus never releases its autorelease pool. Recently we added a bunch of infrastructure to help with this. Open up

<dispatch/queue.h>
and search for “autorelease” to learn more.

In your case you’re using dispatch directly so you might be able to avoid this problem using an autorelease pool at the top of your block. Like this:

dispatch_async(…, ^{  
    @autoreleasepool {
        …
    }
});

My recommendation is that you make both of these changes and see how that affects things.

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for your support. 🙂