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: Sat, 19 Mar 2011 18:52:08 +0100
On Mar 19, 2011, at 1:27 PM, Ken Thomases wrote:
> On Mar 18, 2011, at 5:32 PM, Jason Harris wrote:
>
>> On Mar 18, 2011, at 11:07 PM, Ken Thomases wrote:
>>
>>> 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?
>
> Well, are you familiar with file descriptors? When you open a file, pipe, socket, or whatever, you get a file descriptor by which to refer to it. A file descriptor is just a number, like 5. When you use Cocoa's NSPipe or NSFileHandle, these details are hidden behind the scenes.
>
> Anyway, something may be closing the file descriptor. When that happens, _for a while_ any code which tries to use that file descriptor (in this case, 5) with the system calls for reading from the file will get an error, EBADF, indicating that the file descriptor doesn't refer to an open file. However, and this is very important, it is very likely that future requests to open files, pipes, sockets, etc. will reuse the file descriptor. That is, some other part of the code will open a file and receive the file descriptor 5 for its open file. Once that happens, the original code which had been use file descriptor 5 can't tell that this new file descriptor 5 is not the one it used to own. It will keep trying to use it, and that may seem to succeed, but it's accessing a completely different file than it should be. It may be trying to read from a file which will never receive any data, which would explain why you don't receive the output from your task.
I was sort of aware of this but its very nice to have it explained in the context of my problem. Thank you!
>> (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...)
>
> Note that there's no guarantee that your attempt to read from the file descriptor will happen during the window when the file descriptor is invalid. The file descriptor may be closed and then nearly-immediately reused for a different file, and your first attempt to read from it will access a different, unrelated file. That would be bad and wrong, but would not cause the exception resulting from an EBADF error.
>
>
>>> 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?
>
> Well, this is sketched in mail. By the way, I reviewed the docs and the closeOnDealloc:NO part is not necessary since that's the default:
>
> int out_pipe_fds[2];
> // Creates a pipe consisting of two file descriptors. The [0] element gets the read end of the
> // pipe; the [1] element gets the write end.
> if (pipe(out_pipe_fds) != 0)
> /* handle error */;
> NSFileHandle* outReadHandle = [[NSFileHandle alloc] initWithFileDescriptor:out_pipe_fds[0]];
> NSFileHandle* outWriteHandle = [[NSFileHandle alloc] initWithFileDescriptor:out_pipe_fds[1]];
> [task setStandardOutput:outWriteHandle];
>
> // Initiate reading on the read handle, including observing the appropriate notification
>
> // Repeat for stderr
>
> [task launch];
> [outWriteHandle release];
> close(out_pipe_fds[1]);
> // Repeat for stderr
>
> // ...
>
> // After you've received the end-of-file indication:
> [outReadHandle release];
> close(out_pipe_fds[0]);
Thanks!
>
>>> 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...
>
> The run loop is still executing in the sense of flow of execution has not returned back to your code. You are still blocked in the -runMode:untilDate: call. So, your breakpoint won't be hit because your breakpoint is outside of that method in the caller, but that's never reached.
>
> Keep in mind that -runMode:untilDate: won't necessarily return for _everything_ that happens within it. In particular, see this note in the documentation:
>
>> Note: A timer is not considered an input source and may fire multiple times while waiting for this method to return
>
> I believe that 'a timer' includes those -performSelector... methods with a delay, like you had been using in -setPendingTermination. So, your selector might be performed but that would not necessarily give -waitTillFinished the opportunity to notice it.
ok that very likely explains that as well...
>
>> 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...
>
> Xcode does have its share of bugs with stepping through code. However, I suspect this was just behaving normally. You had suggested that this whole task execution process was happening on a background thread. So, I suspect that background thread was blocked in the run loop and was not reaching your breakpoint (nor the implicit breakpoint involved in stepping over code). However, it appeared that you had just hit continue because the other threads of your program were running just fine. The debugger would have stopped the next time a real run loop source were to fire, but that never happened because there were no active sources scheduled.
Ok again that very nicely likely explains what is going on.
I am going back to have a play with the code. I have a much better idea now what is likely going wrong, but previously I tried a gizzillion different combinations and none of them worked. In any case given the extremely simple use of [task_ waitUntilExit] (once the pipes and notifications are set up) is now working it will likely be the approach I go with.
Thank you again for taking the time to give this detailed response!
Cheers,
Jason_______________________________________________
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