NSTask stealth bug in readDataOfLength!! :(

  • I'm trying to debug this strange problem with NSTask:

        NSTask* t = [NSTask new];
        [t setLaunchPath:@"/usr/bin/curl"];
        [t setArguments:array];

        NSPipe* pope = [NSPipe pipe];
        NSFileHandle* fh = [pope fileHandleForReading];

        [t setStandardOutput:pope];
        [t launch];

        NSData* data = [fh readDataToEndOfFile];

    Now readDataToEndOfFile seems to call through to readDataOfLength.
    Upon running, this gives me an
    "_NSfileHandleRaiseOperatioNException". No idea why.

    But if I step through the debugger, it works just fine!!!

    So if i try to debug it, the bug goes away. This is why I call it a
    stealth bug. It's making no sense. Any answers anyone??

    Why is the task raising an exception anyhow? Is it because the task
    hasn't loaded yet or what?

    This is driving me crazy! I follow the examples here http://
    www.cocoadev.com/index.pl?WrappingUnixApps and it doesn't work!! :(

    --
    http://elfdata.com/plugin/
  • Nevermind.

    I solved the problem.

    Lesson learnt? NEVER USE NSTASK! It sucks. I used system() instead :(

    I spent about 2 hours trying to figure out the bugs in that bloody
    NSTask. And spend 30 mins or so converting it to system() which works
    just fine. I did have to pipe the results into a file, but even the
    overhead of reading that file back out, is better than dealing with
    the fact that NSTask sucks. :(

    On 25 Oct 2006, at 18:19, Theodore H. Smith wrote:

    > I'm trying to debug this strange problem with NSTask:
    >
    > NSTask* t = [NSTask new];
    > [t setLaunchPath:@"/usr/bin/curl"];
    > [t setArguments:array];
    >
    >
    > NSPipe* pope = [NSPipe pipe];
    > NSFileHandle* fh = [pope fileHandleForReading];
    >
    > [t setStandardOutput:pope];
    > [t launch];
    >
    > NSData* data = [fh readDataToEndOfFile];
    >
    >
    > Now readDataToEndOfFile seems to call through to readDataOfLength.
    > Upon running, this gives me an
    > "_NSfileHandleRaiseOperatioNException". No idea why.
    >
    > But if I step through the debugger, it works just fine!!!
    >
    > So if i try to debug it, the bug goes away. This is why I call it a
    > stealth bug. It's making no sense. Any answers anyone??
    >
    > Why is the task raising an exception anyhow? Is it because the task
    > hasn't loaded yet or what?
    >
    > This is driving me crazy! I follow the examples here http://
    > www.cocoadev.com/index.pl?WrappingUnixApps and it doesn't work!! :(
    >
    >
    >
    >
    > --
    > http://elfdata.com/plugin/
    >
    >
    >

    --
    http://elfdata.com/plugin/
  • On 10/25/06, Theodore H. Smith <delete...> wrote:
    > Nevermind.
    >
    > I solved the problem.
    >
    > Lesson learnt? NEVER USE NSTASK! It sucks. I used system() instead :(

    NSTask works just fine... your usage of it is faulty. If you can give
    a more complete example of what you had a problem with we might be
    able to help.

    -Shawn
  • Many others have used NSTask successfully, so there's a strong
    possibility that you just weren't using the API correctly.
    NSTask supports a lot more functionality than system() so it's not
    surprising that it isn't as easy to use.

    On Oct 25, 2006, at 11:05 AM, Theodore H. Smith wrote:

    > Nevermind.
    >
    > I solved the problem.
    >
    > Lesson learnt? NEVER USE NSTASK! It sucks. I used system() instead :(
    >
    > I spent about 2 hours trying to figure out the bugs in that bloody
    > NSTask. And spend 30 mins or so converting it to system() which
    > works just fine. I did have to pipe the results into a file, but
    > even the overhead of reading that file back out, is better than
    > dealing with the fact that NSTask sucks. :(
    >
    > On 25 Oct 2006, at 18:19, Theodore H. Smith wrote:
    >
    >> I'm trying to debug this strange problem with NSTask:
    >>
    >> NSTask* t = [NSTask new];
    >> [t setLaunchPath:@"/usr/bin/curl"];
    >> [t setArguments:array];
    >>
    >>
    >> NSPipe* pope = [NSPipe pipe];
    >> NSFileHandle* fh = [pope fileHandleForReading];
    >>
    >> [t setStandardOutput:pope];
    >> [t launch];
    >>
    >> NSData* data = [fh readDataToEndOfFile];
    >>
    >>
    >> Now readDataToEndOfFile seems to call through to readDataOfLength.
    >> Upon running, this gives me an
    >> "_NSfileHandleRaiseOperatioNException". No idea why.
    >>
    >> But if I step through the debugger, it works just fine!!!
    >>
    >> So if i try to debug it, the bug goes away. This is why I call it
    >> a stealth bug. It's making no sense. Any answers anyone??
    >>
    >> Why is the task raising an exception anyhow? Is it because the
    >> task hasn't loaded yet or what?
    >>
    >> This is driving me crazy! I follow the examples here http://
    >> www.cocoadev.com/index.pl?WrappingUnixApps and it doesn't work!! :(
    >>
    >>
    >>
    >>
    >> --
    >> http://elfdata.com/plugin/
    >>
    >>
    >>
    >
    > --
    > http://elfdata.com/plugin/
    >
    >
    >
    > _______________________________________________
    > Do not post admin requests to the list. They will be ignored.
    > Cocoa-dev mailing list      (<Cocoa-dev...>)
    > Help/Unsubscribe/Update your Subscription:
    > http://lists.apple.com/mailman/options/cocoa-dev/jstiles%
    > 40blizzard.com
    >
    > This email sent to <jstiles...>
  • > Lesson learnt? NEVER USE NSTASK! It sucks. I used system() instead :(

    I'm pretty sure that NSTask doesn't suck, and that you're just
    incorrectly using a perfectly good API.  I've had no problems using
    NSTask in this way:

    process = [[NSTask alloc] init];
    [process setLaunchPath:exec];
    [process setCurrentDirectoryPath:[exec stringByDeletingLastPathComponent]];
    [process setArguments:args];
    [process setEnvironment:env];
    [process setStandardError:[NSPipe pipe]];
    [process setStandardInput:[NSPipe pipe]];
    [process setStandardOutput:[NSPipe pipe]];
    [process launch];
    NSFileHandle *fh = [[process standardOutput] fileHandleForReading];
    [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(processData:)
    name:NSFileHandleReadCompletionNotification object:fh];
    [fh readInBackgroundAndNotify];

    Note that this is for a long-running process that gets both read from
    and written to, but the principle is the same regardless.  I can't
    reproduce your specific error, but I can attest that there is really
    nothing wrong with NSTask.  If it's crashing, it's because you're
    doing something wrong.

    Stephen Deken
    <stephen.deken...>
  • On Oct 25, 2006, at 6:19 PM, Theodore H. Smith wrote:

    > I'm trying to debug this strange problem with NSTask:
    >
    > NSTask* t = [NSTask new];
    > [t setLaunchPath:@"/usr/bin/curl"];
    > [t setArguments:array];
    >
    >
    > NSPipe* pope = [NSPipe pipe];
    > NSFileHandle* fh = [pope fileHandleForReading];
    >
    > [t setStandardOutput:pope];
    > [t launch];
    >
    > NSData* data = [fh readDataToEndOfFile];
    >
    >
    > Now readDataToEndOfFile seems to call through to readDataOfLength.
    > Upon running, this gives me an
    > "_NSfileHandleRaiseOperatioNException". No idea why.
    >
    > But if I step through the debugger, it works just fine!!!
    >
    > So if i try to debug it, the bug goes away. This is why I call it a
    > stealth bug. It's making no sense. Any answers anyone??

    My guess is that what's probably happening is that your program is
    getting a signal (probably SIGCHILD or SIGPIPE or something), which
    is causing the read() call inside -readDataOfLength: to return
    prematurely with EINTR, hence the exception you're seeing.  It's
    quite annoying that the -readData methods don't handle this better.
    I think in at least one place in our code we intentionally avoid
    using NSFileHandle's -read methods for this very reason.

    I'm sure we must have filed a bug report about this, but I can't find
    anything.  Maybe my colleague Chris did, I'm not sure.

    Kind regards,

    Alastair.

    --
    http://alastairs-place.net
  • On 25 Oct 2006, at 20:28, Stephen Deken wrote:

    >> If it's a perfectly good API, then why did Alastair say this:
    >
    > Ok.  It's a perfectly good API that has at least one bug.  Actually,
    > if it really is a SIGCHLD or SIGPIPE that's causing it, it's more
    > properly a 'gotcha', since it's working as designed but trips up lots
    > of developers.  If this is the problem, you can mask the signals out
    > during the call and get on with it.

    Err... OK. That'd be a new one to me. Also too late as I already got
    a solution using system() and using the > operator to pipe to a file.

    >> Also, I find the API very awkward. Using 5 classes (NSPipe, NSArray,
    >> NSFileHandle, NSData, NSTask), just to get a string? Ideally, we'd
    >> have a simple function like this:
    >
    > I agree, if all you need is the contents of stdout, then NSTask is
    > unwieldy.  But it can do a lot more than just that, which is why the
    > class is structured the way it is.  But you can mitigate the mess it
    > creates in your code by adding a category.
    >
    >> NSString* NSSystem( NSString* PathArgumentsAndEverything );
    >> it would return the stdout :)
    >
    > What happens if the executable returns an error code?  What happens if
    > the executable crashes?  What happens if the executable gets thrown
    > into an infinite loop?  What happens if the executable outputs an
    > infinite stream of data?  What happens if the executable is not found?
    > What happens if the executable prints to stderr instead of stdout?
    > What happens if the executable needs input?

    Then you use a more complete API?

    I like APIs that are easy for easy things, and give more features for
    more complex things, but don't impose them.

    OK ideally, we could just use one class:

    NSTask* t = [NSTask taskWithString: @"/usr/bin/curl --url bla"];
    NSData* d = [t launchAndResultData];

    If you got an error code, you'd read it like this:

    int Error = [t errorCode];

    If it goes into an infinite loop... well then you'd use the standard
    notification thingy you use at the moment.

    If there's no executable it would return nil from taskWithString.

    If it prints to stderr, you'd use the current API for redirecting
    stderr. Same with with input, use what it's currently got.

    Just two more methods would cut out 3 classes required!!!
  • On Oct 25, 2006, at 8:49 PM, Theodore H. Smith wrote:

    > On 25 Oct 2006, at 20:28, Stephen Deken wrote:
    >
    >>> If it's a perfectly good API, then why did Alastair say this:
    >>
    >> Ok.  It's a perfectly good API that has at least one bug.  Actually,
    >> if it really is a SIGCHLD or SIGPIPE that's causing it, it's more
    >> properly a 'gotcha', since it's working as designed but trips up lots
    >> of developers.  If this is the problem, you can mask the signals out
    >> during the call and get on with it.

    A better solution might be to set the signal in question so that the
    system call is automatically restarted by using the SA_RESTART flag
    with sigaction().  Or to just write the read() call directly
    yourself, which I think is what we did.

    > Err... OK. That'd be a new one to me. Also too late as I already
    > got a solution using system() and using the > operator to pipe to a
    > file.

    You might consider popen() as an alternative to using a temporary file.

    I have to say, though, that the Cocoa API is *a lot* more convenient
    than the usual little dance with fork(), exec(), pipe(), dup2() (and
    possibly close(), maybe a few others too depending on what you're
    doing exactly).  The fault here is in NSFileHandle I think rather
    than the task API.

    The other thing I remember doing wrong a few times with the NSTask
    API is passing [inPipe fileHandleForReading] or [outPipe
    fileHandleForWriting] to the methods that set up the input/output
    redirection.  You are, of course, supposed to just pass the NSPipe
    objects directly, and if you don't you can get some odd behaviour.
    That could probably be highlighted better in the docs, as it's quite
    an important point.

    Kind regards,

    Alastair.

    --
    http://alastairs-place.net
  • > My guess is that what's probably happening is that your program is
    > getting a signal (probably SIGCHILD or SIGPIPE or something), which
    > is causing the read() call inside -readDataOfLength: to return
    > prematurely with EINTR, hence the exception you're seeing.  It's
    > quite annoying that the -readData methods don't handle this
    > better.  I think in at least one place in our code we intentionally
    > avoid using NSFileHandle's -read methods for this very reason.

    It's straightforward to trigger by killing an running, lengthy async
    task and use something like the Moriarty sample code to read all data...

    Regards,
    Tom_E
  • > My guess is that what's probably happening is that your program is
    > getting a signal (probably SIGCHILD or SIGPIPE or something), which
    > is causing the read() call inside -readDataOfLength: to return
    > prematurely with EINTR, hence the exception you're seeing.  It's
    > quite annoying that the -readData methods don't handle this
    > better.  I think in at least one place in our code we intentionally
    > avoid using NSFileHandle's -read methods for this very reason.
    >
    > I'm sure we must have filed a bug report about this, but I can't
    > find anything.  Maybe my colleague Chris did, I'm not sure.

    I did file a bug.

    I also changed all our code to use:

    @implementation NSFileHandle (CSFileHandleExtensions)

    - (NSData *)availableDataOrError:(NSException **)returnError;
    {
      for (;;) {
        @try {
          return [self availableData];
        } @catch (NSException *e) {
          if ([[e name] isEqualToString:NSFileHandleOperationException]) {
    if ([[e reason] isEqualToString:
         @"*** -[NSConcreteFileHandle availableData]: Interrupted system
    call"]) {
         continue;
    }
    if (returnError)
         *returnError = e;
    return nil;
          }

          @throw;
        }
      }
    }

    @end

    Not particularly pleasant, I'll admit.

    - Chris
previous month october 2006 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today