Re: NSTask in separate thread is leaking memory
Re: NSTask in separate thread is leaking memory
- Subject: Re: NSTask in separate thread is leaking memory
- From: Oleg Krupnov <email@hidden>
- Date: Wed, 4 Feb 2009 15:25:42 +0200
Marvelous! Thank you very much, Ken. All of your advices worked like a charm.
As always, it's been a good idea to get rid of thread spawning. I've
done so using the asynchronous readToEndOfFileInBackgroundAndNotify
method of the reading file handle of the pipe. It is indeed that I
used the synchronous readDataToEndOfFile method before, my main thread
was blocked. It has been also the reason why the
NSTaskDidTerminateNotification did not arrive. Anyway, I don't
actually need this notification, but instead I used
NSFileHandleReadToEndOfFileCompletionNotification.
So the problem is solved for now, but one question still left open for
the future is the following: would it be thread-safe to use NSTask in
the way I originally attempted? If yes, why did it leak? The leak was
reported somewhere inside NSTask internal initialization code. Funny
enough, the leak disappeared if I made one superfluous -release call
on NSTask.
On Wed, Feb 4, 2009 at 1:30 PM, Ken Thomases <email@hidden> wrote:
> On Feb 4, 2009, at 4:49 AM, Oleg Krupnov wrote:
>
>> I use an NSTask to collect system info from system_profiler. Because
>> it takes a while to complete, I launch that task in a separate thread,
>> to avoid UI from freezing.
>
> Both NSTask and NSFileHandle provide asynchronous interfaces, so it is not
> necessary to use a separate thread.
>
>
>> - (void)systemProfilerThread:(id)ignored
>> {
>> NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
>>
>> NSString *configuration = nil;
>>
>> NSPipe* inputPipe = [NSPipe pipe];
>
> You do nothing with inputPipe, so you should just get rid of it. If you
> want the task to not inherit your standard input, do [scriptTask
> setStandardInput:[NSFileHandle fileHandleWithNullDevice]].
>
>> NSPipe* outputPipe = [NSPipe pipe];
>>
>> NSTask* scriptTask = [[[NSTask alloc] init] autorelease];
>>
>> [scriptTask setLaunchPath:@"/usr/sbin/system_profiler"];
>> [scriptTask setArguments:[NSArray arrayWithObjects:@"-detailLevel",
>> @"mini", nil]];
>> [scriptTask setStandardOutput:outputPipe];
>> [scriptTask launch];
>>
>> [[inputPipe fileHandleForWriting] closeFile];
>> configuration = [[[NSString alloc] initWithData:[[outputPipe
>> fileHandleForReading] readDataToEndOfFile]
>>
>> encoding:NSUTF8StringEncoding] autorelease];
>> if (!m_isCanceled)
>> {
>> [m_target performSelectorOnMainThread:m_action
>> withObject:configuration waitUntilDone:NO];
>> }
>>
>> [pool drain];
>> }
>>
>> The problem is, according to the Leaks tool, is that there's a memory
>> leak in -systemProfilerThread. The leak disappears if I launch the
>> task in the main thread.
>
> You don't say what is leaking or where it was allocated from.
>
>
>> I've read in docs that NSTask is NOT thread safe, but does it also
>> apply to the above case, when the NSTask object is created and
>> released within a single thread, though not the main thread? If NSTask
>> cannot be used this way, then which way I should use it?
>
> Create and launch the task from the main thread. Register for the task
> termination notification, if you're interested.
>
> Also register for notification of read-to-end-of-file completion on the
> output file handle, and invoke -readToEndOfFileInBackgroundAndNotify on it.
> In the handler for that notification is where you can decode/parse the
> data.
>
> After you have registered for these notifications and launched the task,
> return immediately. Don't block waiting for either the task to terminate or
> data to arrive from the file handle.
>
>
>> Also, what puzzles me is why, first and foremost, the main thread is
>> blocked until the task is complete? I do not call wait -waitUntilExit.
>> I would expect that the main thread is not blocked, but I receive the
>> NSTaskDidTerminateNotification, but it does not happens with the code
>> above.
>
> I assume you mean the main thread is blocked if you _don't_ detach a
> separate thread, right? In that case, it's because you're using
> -readDataToEndOfFile, which is synchronous. It doesn't return until it has
> read to the end of the output from system_profiler -- that is, when
> system_profiler terminates and closes its standard output.
>
> Also, where are you registering for NSTaskDidTerminateNotification? I don't
> see it above. In any case, regardless of on which thread you register for
> that notification, I would expect it to be posted on the thread which
> created the NSTask. Although notifications are not normally passed through
> the run loop, this particular notification is the result of the framework
> "noticiing" that the task has completed (an event which is, of course,
> external to your program and the framework's direct knowledge). In order to
> notice that, the framework needs the run loop of the thread to run. You
> never run the run loop for your thread in the code above.
>
> I suspect, in fact, that that's the cause of the memory leak. The framework
> had some housekeeping to do that relies on the run loop running.
>
> Cheers,
> Ken
>
>
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden