"broken pipe", but not when debugging...


Graham Cox
 

Hi,

I’m using NSTask to ‘talk to’ a command line executable that is embedded in my app. I use NSPipe as its stdIn and stdOut channels.
When I run the code under Xcode, it works fine. When I build the app into a standalone executable, it fails with a “broken pipe 13” error message and immediate termination.

Aug 11 09:01:23 The-New-iMac com.apple.xpc.launchd[1] (net.apptree.checkmate.Checkmate.61324[49384]): Service exited due to signal: Broken pipe: 13 sent by Checkmate[49384]

The code seems pretty straightforward, so I can’t see anything tricky. Any ideas what could be causing this?


NSBundle* mainBundle = [NSBundle mainBundle];
NSString* executablePath = [mainBundle pathForAuxiliaryExecutable:@"crafty"];
NSString* resourcePath = [mainBundle resourcePath];

mCrafty = [[NSTask alloc] init];

mCrafty.launchPath = executablePath;
mCrafty.arguments = @[@"ponder off",@"noise 1000000",@"sn 10",@"sd 10"];

NSMutableDictionary* environment = [NSMutableDictionary dictionary];

[environment setObject:resourcePath forKey:@"CRAFTY_BOOK_PATH"];

mCrafty.environment = environment;

[self initStdIn];
[self initStdOut];

NSLog(@"launching crafty");

[mCrafty launch];




- (void) initStdIn
{
NSPipe* inputToCrafty = [NSPipe pipe];

mCrafty.standardInput = inputToCrafty;

NSLog(@"stdIn initialized");
}



- (void) initStdOut
{
NSPipe* craftyOutput = [NSPipe pipe];

craftyOutput.fileHandleForReading.readabilityHandler = ^(NSFileHandle* fileHandle)
{
//[all the stuff that parses the output deleted for clarity]
};

mCrafty.standardOutput = craftyOutput;

NSLog(@"stdOut initialized");
}


Note: For the standalone executable, I don’t see any of the logged (NSLog) messages. I’m not sure why. One difficulty is that the revised Console app makes it a lot harder to figure out what the hell I’m looking at. system.log contains the SIGPIPE error, but none of the log messages. Where did they go?

—Graham


 


On Aug 10, 2017, at 4:12 PM, Graham Cox <graham@...> wrote:

When I run the code under Xcode, it works fine. When I build the app into a standalone executable, it fails with a “broken pipe 13” error message and immediate termination.

For some reason lost in ancient Unix history, if you write to a file-descriptor that’s been closed at the other end, a SIGPIPE signal is raised. This will by default kill your process. It’s annoying behavior but it’s long been standardized so it can’t be changed.

This doesn’t happen when run under the debugger because part of the debugger’s job is to catch all signals from your process, and I assume the debugger just ignores SIGPIPE.

Traditionally the workaround has been to call signal() to install a process-wide no-op handler for SIGPIPE, but on Apple platforms (and others?) you can also set a flag on the file descriptor telling it not to raise the signal. I was going to write that it’s an option to setsockopt, but then I remembered you’re using a pipe not a socket. So I’m not sure what system call you’d use; ioctl maybe?

—Jens


Marco S Hyman
 

On Aug 10, 2017, at 4:12 PM, Graham Cox <graham@...> wrote:


Note: For the standalone executable, I don’t see any of the logged (NSLog) messages. I’m not sure why. One difficulty is that the revised Console app makes it a lot harder to figure out what the hell I’m looking at. system.log contains the SIGPIPE error, but none of the log messages. Where did they go?
Before running your app open the Console app and put the process name of your app in the search bar. The run your app. I believe you'll see your output.

It seems with the new log system is you have to get ready looking for messages before running your app. At least that is the only way I’ve found to see messages. OK when debugging, but useless if trying to find info after the fact.


Graham Cox
 

On 11 Aug 2017, at 10:18 am, Jens Alfke <jens@...> wrote:

For some reason lost in ancient Unix history, if you write to a file-descriptor that’s been closed at the other end, a SIGPIPE signal is raised. This will by default kill your process. It’s annoying behavior but it’s long been standardized so it can’t be changed.

This doesn’t happen when run under the debugger because part of the debugger’s job is to catch all signals from your process, and I assume the debugger just ignores SIGPIPE.

OK, makes (some) sense, thanks. I’m wondering why the file descriptor would be ‘closed at the other end’ though - does that mean one of us (the command line tool is not my code but I am compiling from source) is doing something wrong? Or is it normal to close a file descriptor? The communication seems to work otherwise.

Traditionally the workaround has been to call signal() to install a process-wide no-op handler for SIGPIPE, but on Apple platforms (and others?) you can also set a flag on the file descriptor telling it not to raise the signal. I was going to write that it’s an option to setsockopt, but then I remembered you’re using a pipe not a socket. So I’m not sure what system call you’d use; ioctl maybe?

OK, I’ll look into doing something like this.

—Graham


Graham Cox
 

On 11 Aug 2017, at 1:42 pm, Graham Cox <graham@...> wrote:

Traditionally the workaround has been to call signal() to install a process-wide no-op handler for SIGPIPE, but on Apple platforms (and others?) you can also set a flag on the file descriptor telling it not to raise the signal. I was going to write that it’s an option to setsockopt, but then I remembered you’re using a pipe not a socket. So I’m not sure what system call you’d use; ioctl maybe?

OK, I’ll look into doing something like this.

Well, this is so far a blind alley.

I can’t find any mention of functions called signal() or ioctl() in the current (Xcode 8) documentation browser. But then, it seems we’ve lost the ability to view man pages. WTF, why are we going backwards with every “update”?

Assume I’m only 5. Where to I find those functions and some sort of documentation for them?

—Graham


Jack Brindle
 

A broken pipe usually means that one end of the connection has been closed for one reason or another. You don’t show the actual
read and write code, but that is most likely where the problem occurs. Be sure and put the data write into a try-catch block in order
to catch the broken pipe (assuming it occurs on a write). This is actually documented, but I’ll have to go find it to say. My notes indicate the
docs to say: This method raises an exception if the file descriptor is closed or is not valid, if the receiver represents an unconnected pipe
or socket endpoint, if no free space is left on the file system, or if any other writing error occurs. The catch block should set pretty much all the
handles to nil, especially the read handle.readabilityHandler. It is pretty easy and useful to set up the readabilityHandler block to get the
data coming from the executable.

This is a two-way street, and it is likely the other end is participating in the broken pipe, if not actually throwing it. If your pipes are not set
up properly and it tries to write to its stdout, you will see this issue.

Also, it might be helpful to debug the son an El Capitan system where we still have a good logging system.

One thing that does look slightly strange is the way the arguments are set up. NSTask really would like for each item to be in a separate
entry (i.e. @[@“ponder”, @“off”, @“noise”, @“1000000”…]. I don’t know if it makes any difference, but is worth trying.

This stuff works pretty well, but there is a lot of opportunity to get broken pipes when one side shuts down for any reason, or simply
closes their side of the connection. Try-catch is definitely your friend here, on both sides of the connection.

- Jack

On Aug 10, 2017, at 4:12 PM, Graham Cox <graham@...> wrote:

Hi,

I’m using NSTask to ‘talk to’ a command line executable that is embedded in my app. I use NSPipe as its stdIn and stdOut channels.
When I run the code under Xcode, it works fine. When I build the app into a standalone executable, it fails with a “broken pipe 13” error message and immediate termination.

Aug 11 09:01:23 The-New-iMac com.apple.xpc.launchd[1] (net.apptree.checkmate.Checkmate.61324[49384]): Service exited due to signal: Broken pipe: 13 sent by Checkmate[49384]

The code seems pretty straightforward, so I can’t see anything tricky. Any ideas what could be causing this?


NSBundle* mainBundle = [NSBundle mainBundle];
NSString* executablePath = [mainBundle pathForAuxiliaryExecutable:@"crafty"];
NSString* resourcePath = [mainBundle resourcePath];

mCrafty = [[NSTask alloc] init];

mCrafty.launchPath = executablePath;
mCrafty.arguments = @[@"ponder off",@"noise 1000000",@"sn 10",@"sd 10"];

NSMutableDictionary* environment = [NSMutableDictionary dictionary];

[environment setObject:resourcePath forKey:@"CRAFTY_BOOK_PATH"];

mCrafty.environment = environment;

[self initStdIn];
[self initStdOut];

NSLog(@"launching crafty");

[mCrafty launch];




- (void) initStdIn
{
NSPipe* inputToCrafty = [NSPipe pipe];

mCrafty.standardInput = inputToCrafty;

NSLog(@"stdIn initialized");
}



- (void) initStdOut
{
NSPipe* craftyOutput = [NSPipe pipe];

craftyOutput.fileHandleForReading.readabilityHandler = ^(NSFileHandle* fileHandle)
{
//[all the stuff that parses the output deleted for clarity]
};

mCrafty.standardOutput = craftyOutput;

NSLog(@"stdOut initialized");
}


Note: For the standalone executable, I don’t see any of the logged (NSLog) messages. I’m not sure why. One difficulty is that the revised Console app makes it a lot harder to figure out what the hell I’m looking at. system.log contains the SIGPIPE error, but none of the log messages. Where did they go?

—Graham






Jonathan Prescott
 

Look at fcntl() (man fcntl). It has an operation parameter of F_SETNOSIGPIPE and F_GETNOSIGPIPE to manage the SIGPIPE signal handling on a file descriptor. Won’t find it in the Xcode documentation, but it is in the BSD man pages.

Jonathan

On Aug 11, 2017, at 12:30 AM, Graham Cox <graham@...> wrote:


On 11 Aug 2017, at 1:42 pm, Graham Cox <graham@...> wrote:

Traditionally the workaround has been to call signal() to install a process-wide no-op handler for SIGPIPE, but on Apple platforms (and others?) you can also set a flag on the file descriptor telling it not to raise the signal. I was going to write that it’s an option to setsockopt, but then I remembered you’re using a pipe not a socket. So I’m not sure what system call you’d use; ioctl maybe?

OK, I’ll look into doing something like this.

Well, this is so far a blind alley.

I can’t find any mention of functions called signal() or ioctl() in the current (Xcode 8) documentation browser. But then, it seems we’ve lost the ability to view man pages. WTF, why are we going backwards with every “update”?

Assume I’m only 5. Where to I find those functions and some sort of documentation for them?

—Graham





 


On Aug 10, 2017, at 9:30 PM, Graham Cox <graham@...> wrote:

I can’t find any mention of functions called signal() or ioctl() in the current (Xcode 8) documentation browser. But then, it seems we’ve lost the ability to view man pages. WTF, why are we going backwards with every “update”?

It does seem absurd that the most fundamental APIs — the C standard library and POSIX/Darwin system calls — aren’t supported by Xcode’s documentation features. Especially since the header files give zero information, usually not even parameter names. I assume this is something hundreds of people have already filed bug reports about over the years. 

I just end up typing “man foo” in a Terminal window, or using the awesome Dash app.


Jack Brindle wrote:

Be sure and put the data write into a try-catch block in order
to catch the broken pipe (assuming it occurs on a write). This is actually documented, but I’ll have to go find it to say. My notes indicate the
docs to say: This method raises an exception if the file descriptor is closed or is not valid, if the receiver represents an unconnected pipe
or socket endpoint, if no free space is left on the file system, or if any other writing error occurs.

I think this is a separate (but related) issue. NSTask is one of the few Cocoa APIs that will throw exceptions in ‘normal’ use to indicate a runtime error, so your advice is correct. But the SIGPIPE crash is different — signals are not exceptions, they’re an earlier lower-level mechanism.

—Jens


Jack Brindle
 

Jens;
Thank you for the opportunity to expound on this. See below…

On Aug 11, 2017, at 9:48 AM, Jens Alfke <jens@...> wrote:


On Aug 10, 2017, at 9:30 PM, Graham Cox <graham@...> wrote:

I can’t find any mention of functions called signal() or ioctl() in the current (Xcode 8) documentation browser. But then, it seems we’ve lost the ability to view man pages. WTF, why are we going backwards with every “update”?

It does seem absurd that the most fundamental APIs — the C standard library and POSIX/Darwin system calls — aren’t supported by Xcode’s documentation features. Especially since the header files give zero information, usually not even parameter names. I assume this is something hundreds of people have already filed bug reports about over the years. 

I just end up typing “man foo” in a Terminal window, or using the awesome Dash app.


Jack Brindle wrote:

Be sure and put the data write into a try-catch block in order
to catch the broken pipe (assuming it occurs on a write). This is actually documented, but I’ll have to go find it to say. My notes indicate the
docs to say: This method raises an exception if the file descriptor is closed or is not valid, if the receiver represents an unconnected pipe
or socket endpoint, if no free space is left on the file system, or if any other writing error occurs.

I think this is a separate (but related) issue. NSTask is one of the few Cocoa APIs that will throw exceptions in ‘normal’ use to indicate a runtime error, so your advice is correct. But the SIGPIPE crash is different — signals are not exceptions, they’re an earlier lower-level mechanism.

A look at the discussion for NSFileHandle’s writeData: method provides the following:
"This method raises an exception if the file descriptor is closed or is not valid, if the receiver represents an unconnected pipe or socket endpoint, if no free space is left on the file system, or if any other writing error occurs.

The exception is caused directly by the broken pipe. The exception handler is picking it up so that a try-catch block can directly handle and correct for it. The same text is in the availableData method. I tend to be very defensive in my code, especially in areas where
external forces can cause problems. This is one such area. After I started using try-catch blocks around these methods the crash issues disappeared.

I believe there are many other issues that the Cocoa frameworks handle with exceptions that we used to handle otherwise - that is with signal handling. I still have some signal handling code, but for the most part it has mostly gone away as we move more into the
try-catch exception handling world. Perhaps this is why it is more difficult to find docs for this things. Having said that, ioctl is so important that it must be available.

In any case, learning to use try-catch has been very beneficial!

- Jack


—Jens