3

I have an application that runs in the background only (by specifying LSBackgroundOnly in the info.plist file). The problem is, that all blocks that I run on parallel queues are not being released. The code is executed in a memory-managed environment - no GC is involved.

The (simplified) code looks like below. Blubber is just some dummy class that holds an NSDate for testing. Also, it overwrites retain, release, and dealloc to do some logging:

NSOperationQueue *concurrentQueue = [[NSOperationQueue alloc] init];
[concurrentQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount];

Blubber *aBlubber = [[Blubber alloc] init]; 
aBlubber.aDate = [NSDate date];

[concurrentQueue addOperationWithBlock:^{       
NSAutoreleasePool *blockPool = [[NSAutoreleasePool alloc] init];
    NSDate *test = [aBlubber aDate];
    NSLog(@"Block DONE");
    [blockPool release];    
}];

[aBlubber release];

[concurrentQueue release];

If I change the application to be a normal (i.e. non-backgound) application, I can observe the blocks being released whenever any input is made via the UI (even changing the focus to another window is sufficient). Since my backgorund app receives input directly over the HID USB driver and it does not have a window or menu bar this does not happen.

Is there any way to manually force the runloop or whatever is responsible to telling the queues to release the finished blocks?

(All other objects that had been retained by the blocks are also not released, creating huge memory leaks. These leaks cannot be spottet by the Leaks or ObjectAllocations tools but the memory consumption can be observed skyrocketing using top.)

Thorsten Karrer
  • 1,345
  • 9
  • 19
  • Have you tried using C functions (libDispatch) instead of NSOperations? – NSSplendid Aug 25 '10 at 13:15
  • I would think that it yields the same result, as NSBlockOperation is documented as using libDispatch internally. Thanks for the suggestion, anyway, I'll give it a whirl ASAP. – Thorsten Karrer Aug 25 '10 at 13:19

2 Answers2

2

One common "gotcha" for autorelease pools is that if the app is building up memory without receiving events, the outermost pool (the one managed by the event loop) won't be draining.

I don't think that should apply here since you're managing your own pool... but just in case, you could try this:

...
//When no events are coming in (i.e. the user is away from their computer), the runloop doesn't iterate, and we accumulate autoreleased objects
[[NSTimer scheduledTimerWithTimeInterval:60.0f target:self selector:@selector(kickRunLoop:) userInfo:nil repeats:YES] retain];
...
- (void) kickRunLoop:(NSTimer *)dummy
{
// 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];
}
Catfish_Man
  • 41,261
  • 11
  • 67
  • 84
0

It looks like you are using a stack based block that is used after the block has gone out of scope. The block needs to be copied. The code should work if it is changed to this:

[concurrentQueue addOperationWithBlock:[[^{       
    NSAutoreleasePool *blockPool = [[NSAutoreleasePool alloc] init];
    NSDate *test = [aBlubber aDate];
    NSLog(@"Block DONE");
    [blockPool release];    
}copy]autorelease]];

Take a look at this post for a full writeup on blocks: http://gkoreman.com/blog/2011/02/27/blocks-in-c-and-objective-c/

gkoreman
  • 86
  • 1
  • 2
  • Thank you for the suggestion. I am pretty sure, though, that the NSBlockOperation which is wrapping the block when I use addOperationWithBlock is taking care of transferring the block to the appropriate storage. My problem is not that the block does not execute or that it behaves strangely; my problem is that the block continues to hug memory even though it has been put into the autorelease pool if the App is UI-less. I am still experimenting but it seems like @Catfish_Man had hit the spot with his answer. – Thorsten Karrer Feb 28 '11 at 13:06