How to prevent SIGPIPE or prevent the server from ending?
Asked Answered
A

3

19

A quite standard C++ TCP server program using pthreads, bind, listen and accept. I have the scenario that the server ends (read: crashes) when I kill a connected client.

The reason for the crash is that the write() call on the file fails, thus the program receives a SIGPIPE. And I guess, this makes the server exit.

I thought, "of course, unhandled signal means exit", so let's use signal():

signal(SIGPIPE, SIG_IGN);

because, taken from man 2 write:

EPIPE fd is connected to a pipe or socket whose reading end is closed. When this happens the writing process will also receive a SIGPIPE signal. (Thus, the write return value is seen only if the program catches, blocks or ignores this signal.)

Alas, no. Neither in the server thread nor the client threads does this seem to help.

So, how do I prevent the write() call from raising that signal, or (to be pragmatic) how do I stop the server from exiting.


My diagnostics are:

  • server thread started, binding, listening, accepting.
  • let a client connect (via telnet for example)
  • send a pkill telnet to crash the client

unwanted behavior: server exits, in gdb with

... in write () at ../sysdeps/unix/syscall-template.S:82
82      T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)

and the backtrace:

#0  ... in write () at ../sysdeps/unix/syscall-template.S:82
#1  ... in ClientHandler::mesg(std::string) ()
#2  ... in ClientHandler::handle() ()
#3  ... in start_thread (arg=<value optimized out>) at pthread_create.c:300
#4  ... in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112
#5  ... in ?? ()
Avalon answered 25/7, 2011 at 19:47 Comment(5)
I am guessing here, but maybe you need to make the file descriptor/socket with the O_NOCTTY flag?Gorgonian
@hexa: it is unlikely that O_NOCTTY has anything to do with this.Nippy
signal(SIGPIPE, SIG_IGN) is what you want. The real question is why it is not working for you. Perhaps some other code is reversing its effect by installing a separate signal handler? Or maybe it's just a case of your arguments to write() being incorrect, and causing a good old fashioned crash (not SIGPIPE-related)?Roeser
@Jeremy: No other signal handler, only a select(). And I will dbl-check the write-call().Avalon
@JeremyFriesner: man page for signal() says that "the effect of signal() in a multithreaded process are unspecified" and recommends to use sigaction() instead. (But the idea is the same.)Recuperate
C
12

Did you by any chance not do the signal ignore prior to spawning off any threads? If you waited until later one of the other threads could still pick up the signal and exit your app.

If that doesn't do it, you can always do a write poll/select before trying the write to make sure the socket is writable.

Coign answered 25/7, 2011 at 20:13 Comment(3)
Good point. So, I have to install the signal prior to any threading. Ok, I will make sure, and that narros down my try-and-error cases down to about 30%. Thx. The polling was something I was thinking about, but have never done that on sockets. Will investigate.Avalon
I think I got it: placing it at a very outer level still inside main() seemed to help. I but confusing was that gdb still stops at the signal over-and-over-again, but outside gdb it seems fine.Avalon
Note that the approach with poll/select could be successful yet writing could technically still fail due to a race condition -- the read end of the pipe closing between the call to poll/select and write.Oxycephaly
A
22

Late to the party, but just wanted to add to this for future reference: If you are debugging your code in gdb, don't forget that it overrides your signal handlers.

So if you have set a signal handler such as: signal(SIGPIPE, SIG_IGN) and it doesn't seem to be working, try running the code outside the debugger.

Or set handle SIGPIPE nostop (in gdb prompt) to prevent gdb stopping on the signal.

Aloe answered 4/10, 2011 at 1:11 Comment(3)
Thanks for the tip for the future. But the gdb pointed to the correct place for SIGPIPE. Maybe because its a "harmless" signal.Avalon
Wait, so are you saying the even if I ignore SIGPIPE, when my code runs under gdb I will still get the signal, making it appear that my directive to ignore the signal is not working?Kyrstin
To answer myself, yes. However, using handle SIGPIPE nostop noprint gdb can be instructed to ignore it.Kyrstin
M
12

When you ignore SIGPIPE, you no longer get a SIGPIPE signal, but write() gets a EPIPE error.

Mezzanine answered 25/7, 2011 at 19:58 Comment(0)
C
12

Did you by any chance not do the signal ignore prior to spawning off any threads? If you waited until later one of the other threads could still pick up the signal and exit your app.

If that doesn't do it, you can always do a write poll/select before trying the write to make sure the socket is writable.

Coign answered 25/7, 2011 at 20:13 Comment(3)
Good point. So, I have to install the signal prior to any threading. Ok, I will make sure, and that narros down my try-and-error cases down to about 30%. Thx. The polling was something I was thinking about, but have never done that on sockets. Will investigate.Avalon
I think I got it: placing it at a very outer level still inside main() seemed to help. I but confusing was that gdb still stops at the signal over-and-over-again, but outside gdb it seems fine.Avalon
Note that the approach with poll/select could be successful yet writing could technically still fail due to a race condition -- the read end of the pipe closing between the call to poll/select and write.Oxycephaly

© 2022 - 2024 — McMap. All rights reserved.