Is it possible to disable the raising of a signal (SIGPIPE
) when writing to a pipe()
FD, without installing my own signal handler or disabling/masking the signal globally?
I'm working on a small library that occasionally creates a pipe, and fork()
s a temporary child/dummy process that waits for a message from the parent. When the child process receives the message from the parent, it dies (intentionally).
The child process, for circumstances beyond my control, runs code from another (third party) library that is prone to crashing, so I can't always be certain that the child process is alive before I write()
to the pipe.
This results in me sometimes attempting to write()
to the pipe with the child process' end already dead/closed, and it raises a SIGPIPE
in the parent process. I'm in a library other customers will be using, so my library must be as self-contained and transparent to the calling application as possible. Installing a custom signal handler could break the customer's code.
I've got around this issue with sockets by using setsockopt(..., MSG_NOSIGNAL)
, but I can't find anything functionally equivalent for pipes. I've looked at temporarily installing a signal handler to catch the SIGPIPE
, but I don't see any way to limit its scope to the calling function in my library rather than the entire process (and it's not atomic).
I've also found a similar question here on SO that is asking the same thing, but unfortunately, using poll()
/select()
won't be atomic, and there's the remote (but possible) chance that the child process dies between my select()
and write()
calls.
Is there any way to accomplish what I'm attempting here, or to atomically check-and-write to a pipe without triggering the behavior that will generate the SIGPIPE
? Additionally, is it possible to achieve this and know if the child process crashed? Knowing if it crashed lets me build a case for the vendor that supplied the "crashy" library, and lets them know how often it's failing.
Is it possible to disable the raising of a signal (
SIGPIPE
) when writing to apipe()
FD [...]?
The parent process can keep its copy of the read end of the pipe open. Then there will always be a reader, even if it doesn't actually read, so the condition for a SIGPIPE
will never be satisfied.
The problem with that is it's a deadlock risk. If the child dies and the parent afterward performs a blocking write that cannot be accommodated in the pipe's buffer, then you're toast. Nothing will ever read from the pipe to free up any space, and therefore the write can never complete. Avoiding this problem is one of the purposes of SIGPIPE
in the first place.
You can also test whether the child is still alive before you try to write, via a waitpid()
with option WNOHANG
. But that introduces a race condition, because the child could die between waitpid()
and the write.
However, if your writes are consistently small, and if you get sufficient feedback from the child to be confident that the pipe buffer isn't backing up, then you could combine those two to form a reasonably workable system.
After going through all the possible ways to tackle this issue, I discovered there were only two venues to tackle this problem:
socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)
, in place of pipes.fork()
which is allowed to crash if SIGPIPE
is raised.I went the socketpair
route. I didn't want to, since it involved re-writing a fair bit of pipe logic, but it's wasn't too painful.
Thanks!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With