Reputation: 351
I'm working on an OS X app which runs all the time and handles everything placed in his queue. These jobs log their process in stdout. I'm trying to get this info out of stdout realtime with a while loop which runs in a background thread. Problem is that after running 12 hours the memory usage went up from 25MB to >750MB. The app is using ARC so I'm a little lost here. Anyone any clue how to solve this?
NSFileHandle *fileStd = [stdPipe fileHandleForReading];
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: cmd];
[task setArguments: arguments];
NSPipe *stdPipe = [NSPipe pipe];
NSPipe *errPipe = [NSPipe pipe];
[task setStandardOutput: stdPipe];
[task setStandardError: errPipe];
NSFileHandle *fileStd = [stdPipe fileHandleForReading];
[task launch];
NSData *readData;
NSString *logString;
while ((readData = [fileStd availableData]) && [readData length]){
logString = [[NSString alloc] initWithData: readData encoding: NSUTF8StringEncoding];
[self parseLog:logString];
}
readData = nil;
}
[task waitUntilExit];
And the parseLog method:
+ (void) parseLog:(NSString *)output {
NSArray *chunks = [output componentsSeparatedByString: @"\r"];
NSString *lastLine = [chunks lastObject];
[lastLine writeToFile:@"/tmp/status.log" atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSRange textRange = [lastLine rangeOfString:@"<TEST> "];
if(textRange.location != NSNotFound) {
NSMutableDictionary *info = [NSMutableDictionary dictionary];
[info setObject:lastLine forKey:@"test"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"handleNotification" object:nil userInfo:info];
}
}
Upvotes: 1
Views: 957
Reputation: 86651
ARC still uses reference counting "under the hood". In particular, it still uses autorelease pools and you nowhere drain any autorelease pools. Try enclosing the body of your while loop with an @autoreleasepool
block.
while ((readData = [fileStd availableData]) && [readData length]){
@autoreleasepool
{
logString = [[NSString alloc] initWithData: readData encoding: NSUTF8StringEncoding];
[self parseLog:logString];
}
}
That's the modern equivalent of creating an autorelease pool at the start of the loop and draining it at the end.
EDIT
As bbum said, readData is put in an autorelease pool on each iteration but it's outside the @autoreleasepool
bit, so the code will need restructuring:
bool dataToProcess = true;
while (dataToProcess){
@autoreleasepool
{
NSData* readData = [fileStd availableData];
dataToProcess = [readData length] > 0;
if (dataToProcess)
{
logString = [[NSString alloc] initWithData: readData encoding: NSUTF8StringEncoding];
[self parseLog:logString];
}
}
}
Upvotes: 3
Reputation: 162712
Use the Allocations Instrument to see why your heap is growing.
In particular, you can use Heapshot analysis to figure out problems like these quite efficiently.
In short:
Repeat that for a while. In your case, you might want to press the mark heap button once every 20 minutes to an hour.
Jeremy almost got it right. You need to restructure your loop a bit:
while (1) {
@autoreleasepool {
if (readData = [fileStd availableData]) && [readData length]){
logString = [[NSString alloc] initWithData: readData encoding: NSUTF8StringEncoding];
[self parseLog:logString];
}
readData = nil;
} else {
break;
}
}
readData
needs to be in the pool.
Upvotes: 4