Re: NSTask is intermittently failing to return results.
Re: NSTask is intermittently failing to return results.
- Subject: Re: NSTask is intermittently failing to return results.
- From: Jason Harris <email@hidden>
- Date: Fri, 18 Mar 2011 23:32:35 +0100
On Mar 18, 2011, at 11:07 PM, Ken Thomases wrote:
> I would say that your "...IgnoringErrors" methods are masking a real problem, and you shouldn't have written them let alone used them. (By the way, you didn't post them except incidentally in your screenshot.)
(Ahh yep. I didn't want to spam the group with lots and lots of code and headers and other bits. I tried to pick the "relevant" code and then provided the link to the sourcefiles on bitbucket...)
And in answer to this I was grasping at straws and I didn't really write this code, I was trying this since it was recommended in some previous posts by others (I can post the references if you are interested). In the end I think this @try @catch just served to not crash the app and return nil for the result... But you are right I am keenly interested in what is going wrong and how to remedy it. (As I said previously the error would manifest itself every so often which was of course very annoying, but now moving to the Moriarty style of things, the errors suddenly became quite noticeable.)
> The exception is saying that the file descriptor backing the NSPipe/NSFileHandle has been closed. Retrying after some time can only result in the frameworks co-opting a new, unrelated file object that happened to get the same file descriptor. That would explain why your reads sometimes never get any data -- you're reading from a completely different file object.
I am sorry, but can you explain the paragraph in more detail? (Just to be clear sometimes I never get the exception and yet it still drops data... So the ugly bit of the @catch is never hit but it still was dropping the data...)
> I'm not sure why the file descriptor is being closed. It may be a framework bug having to do with garbage collection.
>
> I'd try creating the file descriptors manually with the pipe(2) system call. Then, construct NSFileHandles from them with -initWithFileDescriptor:theReadFD closeOnDealloc:NO.
Sorry just so I don't stuff this up can I ask you to give me the exact code here... or point me to an example of this?
> (Pass [NSFileHandle fileHandleWithNullDevice] to -setStandardInput:, while you're at it.) After you launch the task, be sure to close the write ends of your pipes. And, of course, when everything is finished up, close the read ends, too.
I'd like to try this thanks!
> By the way, there's nothing incorrect about getting the task-terminated notification before the end-of-file for the task output or error file handles. There's no guarantee about the order of those events.
Yes, I gathered that. I also gathered that you should always get all three things right? ie
(i) The NSTaskDidTerminateNotification
(ii) and the NULL for the NSFileHandleReadCompletionNotification for stdOut
(iii) and the NULL for the NSFileHandleReadCompletionNotification for stdErr
Well when it was dropping the data I was missing one of these notifications and my empirical observation was that more commonly I was receiving the NSTaskDidTerminateNotification first and then either of the other notifications.
Whereas when things were working it was uncommon or I didn't get the NSTaskDidTerminateNotification first.... It was just a data point I was trying to give everyone so it might shed some light on the problem.
> On Mar 17, 2011, at 4:01 AM, Jason Harris wrote:
>
>> - (BOOL) waitTillFinished
>> {
>> // wait for task to exit:
>> while (![self shouldFinishUp])
>> {
>> // If the task is terminated we should set up a pending termination which will terminate in a bit. This catches some
>> // zombie NSTasks where either the outputData or errorData of 0 were never posted..
>> BOOL terminated = ![task_ isRunning];
>> if (terminated && !pendingTermination_)
>> {
>> DebugLog(@"...Found terminated for %@ ...", [self commandLineString]);
>> [self setPendingTermination];
>> }
>>
>> BOOL runLoopRan = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
>> if (!runLoopRan)
>> break;
>> if (!isFinished_)
>> DebugLog(@"...waitTillFinished still waiting for %@ ...", [self commandLineString]);
>> }
>>
>> [self finishUp];
>> DebugLog(@"...Exiting waitTillFinished for %@ ...", [self commandLineString]);
>> return (result_ == 0);
>> }
>>
>>
>> + (ExecutionResult*) execute:(NSString*)cmd withArgs:(NSArray*)args onTask:(NSTask*)task
>> {
>> ShellTask* shellTask = [[ShellTask alloc] initWithCommand:cmd andArgs:args onTask:task];
>>
>> [shellTask->task_ launch]; // Start the process
>> DebugLog(@"launched %@", [shellTask commandLineString]);
>>
>> [shellTask waitTillFinished];
>>
>> DebugLog(@"Finished execute cmd for %@", [shellTask commandLineString]);
>>
>> NSString* outStr = [[NSString alloc] initWithData:shellTask->outputData_ encoding:NSUTF8StringEncoding];
>> NSString* errStr = [[NSString alloc] initWithData:shellTask->errorData_ encoding:NSUTF8StringEncoding];
>> ExecutionResult* result = [ExecutionResult resultWithCmd:cmd args:args result:shellTask->result_ outStr:outStr errStr:errStr];
>> result->theShellTask_ = shellTask;
>> return result;
>> }
>
>> BAD:
>> --------
>> 0x1014810/-[MacHgDocument initializeRepositoryData] Initializing log entry collection
>> 0x1250100/+[ShellTask execute:withArgs:onTask:] launched localhg combinedinfo --cwd /Volumes/QuickSilver/Development/sandbox/RhodeCode
>> 0x1250100/-[ShellTask gotExit:] ...got Exit for localhg combinedinfo --cwd /Volumes/QuickSilver/Development/sandbox/RhodeCode ...
>> 0x1250100/-[ShellTask gotError:] ...got NULL Error Output for localhg combinedinfo --cwd /Volumes/QuickSilver/Development/sandbox/RhodeCode ...
>> 0x1250100/-[ShellTask waitTillFinished] ...waitTillFinished still waiting for localhg combinedinfo --cwd /Volumes/QuickSilver/Development/sandbox/RhodeCode ...
>> 0x1250100/-[ShellTask finishUp] ...Finishing up for localhg combinedinfo --cwd /Volumes/QuickSilver/Development/sandbox/RhodeCode ...
>
>> Now a couple things to note is that if the [self performSelector:@selector(finishUp) withObject:nil afterDelay:10.0] fires in the BAD case then the outer call to waitTillFinished just sort of vanishes, since there is always a DebugLog call which prints "...Exiting waitTillFinished for ..." but is can be seen in the BAD transcript this just isn't present.
>
> I see no evidence that it has "vanished". It seems to me that -waitTillFinished is still executing, presumably blocked in the run loop. Note that, not only is the DebugLog at the end of -waitTillFinished not hit, but neither is the one immediately after the call to it in +execute:withArgs:onTask:. In other words, it just hasn't returned.
Ahh so you are saying that the loop is still executing? Well if I put a break point in the loop in waitTillFinished then it doesn't break on this breakpoint... Maybe this is an XCode bug or something? Also I would imagine though if this were the case then stepping up through the call stack would have been normal. Ie would have got back to the loop. Instead it was like I hit continue when I reached __CFRunLoopRun...
Thanks for the comments though!
Cheers,
Jas_______________________________________________
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