How to prevent SIGPIPEs (or handle them properly)
Asked Answered
G

10

306

I have a small server program that accepts connections on a TCP or local UNIX socket, reads a simple command and (depending on the command) sends a reply.

The problem is that the client may have no interest in the answer and sometimes exits early. So writing to that socket will cause a SIGPIPE and make my server crash.

What's the best practice to prevent the crash here? Is there a way to check if the other side of the line is still reading? (select() doesn't seem to work here as it always says the socket is writable). Or should I just catch the SIGPIPE with a handler and ignore it?

Gildus answered 20/9, 2008 at 13:43 Comment(0)
T
313

You generally want to ignore the SIGPIPE and handle the error directly in your code. This is because signal handlers in C have many restrictions on what they can do.

The most portable way to do this is to set the SIGPIPE handler to SIG_IGN. This will prevent any socket or pipe write from causing a SIGPIPE signal.

To ignore the SIGPIPE signal, use the following code:

signal(SIGPIPE, SIG_IGN);

If you're using the send() call, another option is to use the MSG_NOSIGNAL option, which will turn the SIGPIPE behavior off on a per call basis. Note that not all operating systems support the MSG_NOSIGNAL flag.

Lastly, you may also want to consider the SO_SIGNOPIPE socket flag that can be set with setsockopt() on some operating systems. This will prevent SIGPIPE from being caused by writes just to the sockets it is set on.

Tantivy answered 20/9, 2008 at 13:46 Comment(9)
To make this explicit: signal(SIGPIPE, SIG_IGN);Phrensy
This may be necessary if you are getting an exit return code of 141 (128 + 13:SIGPIPE). SIG_IGN is the ignore signal handler.Belonging
For sockets, it's really easier to use send() with MSG_NOSIGNAL, as @talash said.Drumlin
What exactly happens if my program writes to a broken pipe (a socket in my case)? SIG_IGN makes the program ignore the SIG_PIPE, but then does that result in send() doing something undesirable?Amboceptor
@Belonging where did you find these codes? (128 + 13)?Stationary
NVM, found it: "The value 141 above is actually 128+13, where 13 is the numeric value of SIGPIPE and 128 is the value the shell adds to signal numbers to indicate that a process died due to a signal rather than exiting "normally". SIGPIPE might be different from 13 on other systems.", here: greenend.org.uk/rjk/tech/shellmistakes.htmlStationary
Worth mentioning, since I found it once, then forgot it later and got confused, then discovered it a second time. Debuggers (I know gdb does) override your signal handling, so ignored signals are not ignored! Test your code outside a debugger to ensure the SIGPIPE no longer occurs. #6821969Tiphany
The man page for signal recommends using sigaction instead, should this answer be updated?Duodenary
It is SO_NOSIGPIPE, not SO_SIGNOPIPE.Skipper
M
167

Another method is to change the socket so it never generates SIGPIPE on write(). This is more convenient in libraries, where you might not want a global signal handler for SIGPIPE.

On most BSD-based (MacOS, FreeBSD...) systems, (assuming you are using C/C++), you can do this with:

int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));

With this in effect, instead of the SIGPIPE signal being generated, EPIPE will be returned.

Medullated answered 16/1, 2009 at 11:25 Comment(5)
This sounds good, but SO_NOSIGPIPE doesn't seem to exist in Linux -- so it isn't a broadly portable solution.Campanulaceous
hi all, can you tell me where place that i have to put this code? i dont understand thank'sBeaumont
In Linux, I see MSG_NOSIGNAL option in flags of sendZippy
Works perfect on Mac OS XPassion
How do i set this flag that it works for socket, pipes and don't get an error when i set it on a file?Oswaldooswalt
S
123

I'm super late to the party, but SO_NOSIGPIPE isn't portable, and might not work on your system (it seems to be a BSD thing).

A nice alternative if you're on, say, a Linux system without SO_NOSIGPIPE would be to set the MSG_NOSIGNAL flag on your send(2) call.

Example replacing write(...) by send(...,MSG_NOSIGNAL) (see nobar's comment)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );
Straphanger answered 10/11, 2009 at 4:58 Comment(5)
In other words, use send(...,MSG_NOSIGNAL) as a replacement for write() and you won't get SIGPIPE. This should work well for sockets (on supported platforms), but send() seems to be limited to use with sockets (not pipes), so this isn't a general solution to the SIGPIPE problem.Campanulaceous
@Ray2k Who on earth is still developing applications for Linux < 2.2? That's actually a semi-serious question; most distros ship with at least 2.6.Pyrrha
@Parthian Shot: You should rethink your answer. Maintenance of old embedded systems is a valid reason to care for older Linux versions.Calathus
@Calathus I'm not the OP- I was just curious.Pyrrha
@Straphanger this worked for me perfectly. I'm using a Qt QTcpSocket object to wrap sockets usage, I had to replace write method call by a OS send (using socketDescriptor method). Anyone knows a cleaner option to set this option in a QTcpSocket class?Wiese
F
31

In this post I described possible solution for Solaris case when neither SO_NOSIGPIPE nor MSG_NOSIGNAL is available.

Instead, we have to temporarily suppress SIGPIPE in the current thread that executes library code. Here's how to do this: to suppress SIGPIPE we first check if it is pending. If it does, this means that it is blocked in this thread, and we have to do nothing. If the library generates additional SIGPIPE, it will be merged with the pending one, and that's a no-op. If SIGPIPE is not pending then we block it in this thread, and also check whether it was already blocked. Then we are free to execute our writes. When we are to restore SIGPIPE to its original state, we do the following: if SIGPIPE was pending originally, we do nothing. Otherwise we check if it is pending now. If it does (which means that out actions have generated one or more SIGPIPEs), then we wait for it in this thread, thus clearing its pending status (to do this we use sigtimedwait() with zero timeout; this is to avoid blocking in a scenario where malicious user sent SIGPIPE manually to a whole process: in this case we will see it pending, but other thread may handle it before we had a change to wait for it). After clearing pending status we unblock SIGPIPE in this thread, but only if it wasn't blocked originally.

Example code at https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156

Fellah answered 27/2, 2010 at 15:30 Comment(0)
T
22

Handle SIGPIPE Locally

It's usually best to handle the error locally rather than in a global signal event handler since locally you will have more context as to what's going on and what recourse to take.

I have a communication layer in one of my apps that allows my app to communicate with an external accessory. When a write error occurs I throw and exception in the communication layer and let it bubble up to a try catch block to handle it there.

Code:

The code to ignore a SIGPIPE signal so that you can handle it locally is:

// We expect write failures to occur but we want to handle them where 
// the error occurs rather than in a SIGPIPE handler.
signal(SIGPIPE, SIG_IGN);

This code will prevent the SIGPIPE signal from being raised, but you will get a read / write error when trying to use the socket, so you will need to check for that.

Tasse answered 27/1, 2012 at 16:4 Comment(5)
should this call be made after connecting a socket or before that ? where is it best to place. ThanksFerland
@Ferland I personally put it in applicationDidFinishLaunching:. The main thing is that it should be before interacting with a socket connection.Tasse
but you will get a read / write error when trying to use the socket, so you will need to check for that. - May I ask how this is possible? signal(SIGPIPE, SIG_IGN) works for me but under debug mode it returns an error is it possible to also ignore that error?Conchoid
@Dreyfus15 I believe I just wrapped calls using the socket in a try / catch block to handle it locally. It's been awhile since I've seen this.Tasse
In a library signal(SIGPIPE, SIG_IGN) cannot be used because it modify the users process environment.Bohr
S
15

You cannot prevent the process on the far end of a pipe from exiting, and if it exits before you've finished writing, you will get a SIGPIPE signal. If you SIG_IGN the signal, then your write will return with an error - and you need to note and react to that error. Just catching and ignoring the signal in a handler is not a good idea -- you must note that the pipe is now defunct and modify the program's behaviour so it does not write to the pipe again (because the signal will be generated again, and ignored again, and you'll try again, and the whole process could go on for a long time and waste a lot of CPU power).

Steinway answered 20/9, 2008 at 14:45 Comment(0)
B
5

Or should I just catch the SIGPIPE with a handler and ignore it?

I believe that is right on. You want to know when the other end has closed their descriptor and that's what SIGPIPE tells you.

Sam

Bragg answered 20/9, 2008 at 13:45 Comment(0)
R
5

Under a modern POSIX system (i.e. Linux), you can use the sigprocmask() function.

#include <signal.h>

void block_signal(int signal_to_block /* i.e. SIGPIPE */ )
{
    sigset_t set;
    sigset_t old_state;

    // get the current state
    //
    sigprocmask(SIG_BLOCK, NULL, &old_state);

    // add signal_to_block to that existing state
    //
    set = old_state;
    sigaddset(&set, signal_to_block);

    // block that signal also
    //
    sigprocmask(SIG_BLOCK, &set, NULL);

    // ... deal with old_state if required ...
}

If you want to restore the previous state later, make sure to save the old_state somewhere safe. If you call that function multiple times, you need to either use a stack or only save the first or last old_state... or maybe have a function which removes a specific blocked signal.

For more info read the man page.

Rorke answered 12/2, 2016 at 2:57 Comment(4)
It's not necessary to read-modify-write the set of blocked signals like this. sigprocmask adds to the set of blocked signals, so you can do all this with a single call.Urethroscope
I don't read-modify-write, I read to save the current state which I keep in old_state so that way I can restore it later, if I choose to. If you know you won't need to ever restore the state, there is indeed no need to read and store it in this way.Rorke
But you do: the first call to sigprocmask() reads the old state, the call to sigaddset modifies it, the second call to sigprocmask() writes it. You could remove to first call, initialize with sigemptyset(&set) and change the second call to sigprocmask(SIG_BLOCK, &set, &old_state).Urethroscope
Ah! Ha ha! You're right. I do. Well... in my software, I do not know which signals I've already blocked or not blocked. So I do it this way. However, I agree that if you have only one "set signals this way" then a simple clear + set is enough.Rorke
R
4

What's the best practice to prevent the crash here?

Either disable sigpipes as per everybody, or catch and ignore the error.

Is there a way to check if the other side of the line is still reading?

Yes, use select().

select() doesn't seem to work here as it always says the socket is writable.

You need to select on the read bits. You can probably ignore the write bits.

When the far end closes its file handle, select will tell you that there is data ready to read. When you go and read that, you will get back 0 bytes, which is how the OS tells you that the file handle has been closed.

The only time you can't ignore the write bits is if you are sending large volumes, and there is a risk of the other end getting backlogged, which can cause your buffers to fill. If that happens, then trying to write to the file handle can cause your program/thread to block or fail. Testing select before writing will protect you from that, but it doesn't guarantee that the other end is healthy or that your data is going to arrive.

Note that you can get a sigpipe from close(), as well as when you write.

Close flushes any buffered data. If the other end has already been closed, then close will fail, and you will receive a sigpipe.

If you are using buffered TCPIP, then a successful write just means your data has been queued to send, it doesn't mean it has been sent. Until you successfully call close, you don't know that your data has been sent.

Sigpipe tells you something has gone wrong, it doesn't tell you what, or what you should do about it.

Rambling answered 15/9, 2015 at 23:57 Comment(3)
Sigpipe tells you something has gone wrong, it doesn't tell you what, or what you should do about it. Exactly. Virtually the only purpose of SIGPIPE is to tear down piped command line utilities when the next stage no longer needs the input. If your program is doing networking, you should usually just block or ignore SIGPIPE program-wide.Amblyoscope
Precisely. SIGPIPE is intended for situations like "find XXX | head". Once head has matched its 10 lines, there's no point continuing with the find. So head exits, and the next time find tries to talk to it, it gets a sigpipe and knows that it too can exit.Rambling
Really, the only purpose of SIGPIPE is to allow programs to be sloppy and not check for errors on writes!Selfidentity
T
2

Linux manual said:

EPIPE The local end has been shut down on a connection oriented socket. In this case the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.

But for Ubuntu 12.04 it isn't right. I wrote a test for that case and I always receive EPIPE withot SIGPIPE. SIGPIPE is genereated if I try to write to the same broken socket second time. So you don't need to ignore SIGPIPE if this signal happens it means logic error in your program.

Tertia answered 26/3, 2014 at 14:31 Comment(1)
I most definitely receive SIGPIPE without first seeing EPIPE on a socket in linux. This sounds like a kernel bug.Xantha

© 2022 - 2024 — McMap. All rights reserved.