tags:

views:

463

answers:

3

I've got a bit of code that handles exporting data from my application. It takes in an NSString full of XML and runs it through a PHP script to generate HTMl, RTF, etc. It works well unless a user has a large list. This is apparently due to it overrunning the 8k or so buffer of NSPipe.

I worked around it (I think) in the readPipe and readHandle, but I'm not sure how to handle it in the writeHandle/writePipe. The application will beachball at [writeHandle writeData:[in... unless I break on it in gdb, wait a few seconds and and then continue.

Any help on how I can workaround this in my code?

- (NSString *)outputFromExporter:(COExporter *)exporter input:(NSString *)input {
  NSString *exportedString = nil;
  NSString *path = [exporter path];
  NSTask *task = [[NSTask alloc] init];

  NSPipe *writePipe = [NSPipe pipe];
  NSFileHandle *writeHandle = [writePipe fileHandleForWriting];
  NSPipe *readPipe = [NSPipe pipe];
  NSFileHandle *readHandle = [readPipe fileHandleForReading];

  NSMutableData *outputData = [[NSMutableData alloc] init];
  NSData *readData = nil;

  // Set the launch path and I/O for the task
  [task setLaunchPath:path];
  [task setStandardInput:writePipe];
  [task setStandardOutput:readPipe];

  // Launch the exporter, it will convert the raw OPML into HTML, Plaintext, etc
  [task launch];

  // Write the raw OPML representation to the exporter's input stream
  [writeHandle writeData:[input dataUsingEncoding:NSUTF8StringEncoding]];
  [writeHandle closeFile];

  while ((readData = [readHandle availableData]) && [readData length]) {
    [outputData appendData:readData];
  }

  exportedString = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding];
  return exportedString;
}
A: 

To be clear, this isn't just slow but it's actually freezing until you break in with a debugger? It's not a problem with your sub-process?

One would expect NSFileHandle to handle any data you throw at it, but perhaps you can split your data into smaller chunks using -subdataWithRange: to see what effect that has. You could also grab the fileDescriptor and use the POSIX APIs (fdopen, fwrite, etc.) to write to the stream. The POSIX APIs would provide more flexibility, if indeed that's what you need.

Darren
No, it's actually locking up. What I believe is happening is that the amount of data being passed through to NSTask is too much for its buffer which is causing it to block. I just wasn't sure if there was a way around that.
Justin Williams
Probably not NSTask's buffer (I don't think it has one); more probably the pipe's buffer. See `PIPE_BUF` in sys/syslimits.h.
Peter Hosey
A: 

The simple, painful truth is that writing a lot of data to a subprocess and then reading a lot of data back from it is not something you can do in a single function or method without blocking the UI.

The solution is just as simple, and is certainly a painful-looking prospect: Make the export asynchronous. Write data as you can, and read data as you can. Not only are you then not blocking the UI, you also gain the ability to update a progress indicator for a really long export, and to do multiple exports in parallel (e.g., from separate documents).

It's work, but the UI payoffs are big, and the result is a cleaner design both internally and externally.

Peter Hosey
Are you recommending shying away from the subprocesses route entirely (in my case, a few PHP scripts) and handling it natively? That was my plan long-term, but I was hoping there would be a band-aid I could put on in the interim to get the existing method to work properly.
Justin Williams
No. Subprocesses are an easy and safe way to get parallelism (as long as they're doing all the work); they're a good thing. But you can't do the necessary I/O with them synchronously on the main thread or you *will* block the UI. You need to do it asynchronously or on another thread, and asynchrony is easier to do safely.
Peter Hosey
A: 

I believe what was happening was I was running into a deadlock caused by the [writeHandle writeData:[input dataUsingEncoding:NSUTF8StringEncoding]]; line filling overfilling its buffer and causing the application to hang until it is (never) emptied.

I worked around it by dispatching the write operation off to a separate thread.

Justin Williams