I used NSTask and NSPipe in iPhoto2Gmail from the very beginning... it is how all of the "hard work" is done. All information I send and receive from "the cloud" (as it is now called I'm told) is via NSTasks. I use the standard method of setting up an NSPipe to stdout and stderr and then a file handle on that pipe for reading.
This worked great until a few days ago when I got two consecutive bug reports about ip2g hanging when retrieving... hrmmm... Thousands of downloads for version v0.4 and only three folks contacted me with a similar hang. But still, this bugged me. A lot. I wrapped the php code for retrieving contacts in an AppleScript and sent it to a couple of willing guys for testing. Surprisingly, they both reported that the contacts came down just fine.
Darn, I was hoping it would be a simple php issue, I really didn't feel like debugging NSTasks and NSPipes! I went back and searched my emails to see what these guys might have in common and it was only one thing: both had G4 based Macs. Could it be an architecture issue? Nah. Then a light came on -- kind of dim, but there: single processor! I shut down one of the cores on my mini and busted out the debugger. Sure enough, NSTask was hanging.
Many hours later scouring mailing list archives, google, and cocoadev and I had what I thought was a working solution... then I added some contacts to my Gmail Contact list and ran into the hang again!
By this time, I was well aware that NSPipes block when they get full, and when paired with a waitUntilExit call on the NSTask you get yourself a nasty deadlock when calling readToEndOfFile or availableData on the file handle. This condition wasn't straightforward for me to fix because in most cases the deadlock would_not_ occur with the presence of the second core; or when my internet connection was slow enough that the pipe never got full before the first pass of the availableData loop.
When I implemented a while (availableData) loop like the Apple Moriarity example, I kept getting [NSConcreteFileHandle availableData]: Interrupted system call"] exceptions. Argh! Thankfully, Chris Suter was awesome enough to post his workaround for this to Cocoa-dev mailing list for devs like me to find and rejoice over.
The final solution for emptying the pipe as it becomes full on machines with any number of cores and any network bandwidth looked like this:
{
...
data = [self availableDataOrError: file ];
while ( [data length] > 0) {
myString = [myString stringByAppendingString: [[[NSString alloc]
initWithData:data encoding:NSUTF8StringEncoding] autorelease]];
data = [self availableDataOrError: file ];
}
[file closeFile];
[task release];
return lines;
}
//slightly modified version of Chris Suter's category function
//used as a private method
-(NSData *) availableDataOrError: (NSFileHandle *)file {
for (;;) {
@try {
return [file availableData];
} @catch (NSException *e) {
if ([[e name] isEqualToString:NSFileHandleOperationException]) {
if ([[e reason] isEqualToString: @"*** -[NSConcreteFileHandle availableData]: Interrupted system call"]) {
continue;
}
return nil;
}
@throw;
}
}
}
Note that I no longer needed to waitUntilExit on the NSTask, since this is a synchronous operation.