102

From my understanding, SIGPIPE can only occur as the result of a write(), which can (and does) return -1 and set errno to EPIPE... So why do we have the extra overhead of a signal? Every time I work with pipes I ignore SIGPIPE and have never felt any pain as a result, am I missing something?

user
  • 5,335
  • 7
  • 47
  • 63
Shea Levy
  • 5,237
  • 3
  • 31
  • 42

5 Answers5

129

I don't buy the previously-accepted answer. SIGPIPE is generated exactly when the write fails with EPIPE, not beforehand - in fact one safe way to avoid SIGPIPE without changing global signal dispositions is to temporarily mask it with pthread_sigmask, perform the write, then perform sigtimedwait (with zero timeout) to consume any pending SIGPIPE signal (which is sent to the calling thread, not the process) before unmasking it again.

I believe the reason SIGPIPE exists is much simpler: establishing sane default behavior for pure "filter" programs that continuously read input, transform it somehow, and write output. Without SIGPIPE, unless these programs explicitly handle write errors and immediately exit (which might not be the desired behavior for all write errors, anyway), they will continue running until they run out of input even if their output pipe has been closed. Sure you can duplicate the behavior of SIGPIPE by explicitly checking for EPIPE and exiting, but the whole purpose of SIGPIPE was to achieve this behavior by default when the programmer is lazy.

Ton van den Heuvel
  • 10,157
  • 6
  • 43
  • 82
R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 17
    +1. The clue's in the fact that SIGPIPE kills you by default - it's not designed to interrupt a system call, it's designed to terminate your program! If you're able to handle the signal in a signal handler, you could just as well handle the return code of `write`. – Nicholas Wilson Jan 19 '14 at 07:13
  • 2
    You're right, I don't know why I accepted that in the first place. This answer makes sense, though IMO it's odd that e.g. on Linux this laziness is achieved by kernel and not the libc. – Shea Levy Mar 19 '14 at 21:31
  • 5
    it sounds like this answer basically boils down to: "because we didn't have exceptions". However, people ignoring return codes in C is a much wider issue than just write() calls. What makes write so special it needs its own signal? perhaps the pure filter programs is a lot more common that I imagine. – Arvid Aug 28 '15 at 20:34
  • @Arvid SIGPIPE was invented by Unix people, to solve a problem they were having in their environment in which filter programs are extremely common, All we have to do is read the boot scripts that bring up the system. – Kaz May 15 '20 at 05:04
  • @SheaLevy Which Unix systems implement SIGPIPE purely in their libc? – Kaz May 15 '20 at 05:06
  • 2
    Why do we need this, just for `write()`, and just for pipe write failures? Wouldn't it be better if everybody just always checked the return values from write calls? The answer is that the original designers of Unix quite deliberately adopted a degree of pragmatism that can look pretty heretical when viewed through the lens of today's thou-shalt-take-no-shortcuts mentality. Checking the return value of every write call is a plain nuisance! If you ignore them all instead, almost nothing goes wrong, so it's okay to ignore them, and then SIGPIPE catches the one case that matters in practice. – Steve Summit Jan 16 '21 at 17:03
22

Because your program may be waiting for I/O or otherwise suspended. A SIGPIPE interrupts your program asynchronously, terminating the system call, and so can be handled immediately.

Update

Consider a pipeline A | B | C.

Just for definiteness, we'll assume that B is the canonical copy loop:

while((sz = read(STDIN,bufr,BUFSIZE))>=0)
    write(STDOUT,bufr,sz);

B is blocked on the read(2) call waiting for data from A when C terminates. If you wait for the return code from write(2), when will B see it? The answer, of course, is not until A writes more data (which could be a long wait -- what if A is blocked by something else?). Notice, by the way, that this also allows us a simpler, cleaner program. If you depended on the error code from write, you'd need something like:

while((sz = read(STDIN,bufr,BUFSIZE))>=0)
    if(write(STDOUT,bufr,sz)<0)
        break;

Another update

Aha, you're confused about the behavior of the write. You see, when the file descriptor with the pending write is closed, the SIGPIPE happens right then. While the write will return -1 eventually, the whole point of the signal is to notify you asynchronously that the write is no longer possible. This is part of what makes the whole elegant co-routine structure of pipes work in UNIX.

Now, I could point you to a whole discussion in any of several UNIX system programming books, but there's a better answer: you can verify this yourself. Write a simple B program[1] -- you've got the guts already, all you need is a main and some includes -- and add a signal handler for SIGPIPE. Run a pipeline like

cat | B | more

and in another terminal window, attach a debugger to B and put a breakpoint inside the B signal handler.

Now, kill the more and B should break in your signal handler. examine the stack. You'll find that the read is still pending. let the signal handler proceed and return, and look at the result returned by write -- which will then be -1.

[1] Naturally, you'll write your B program in C. :-)

Charlie Martin
  • 110,348
  • 25
  • 193
  • 263
  • But won't the write just return -1 if the read-end of the pipe is closed during the write? – Shea Levy Dec 03 '11 at 17:31
  • But isn't that an implementation concern? Couldn't a POSIX-compliant OS return from the write() immediately in the case of a closed reader, and similarly wait a bit before sending the SIGPIPE? – Shea Levy Dec 03 '11 at 17:41
  • 3
    Why would B see C's termination any sooner with SIGPIPE? B will remain blocked on the read until something is written to its STDIN, at which point it will call the write() and only then will SIGPIPE be raised/-1 be returned. – Shea Levy Dec 03 '11 at 17:48
  • I've finally tested this. I do cat | B | more, type a few lines that are copied as expected, then attach gdb, set a breakpoint in the handler, and continue. I then kill more. B doesn't enter the signal handler until after I then input another character, so it's not immediate like you claim. – Shea Levy Dec 30 '11 at 18:34
  • Hm... Could it have something to do with buffered output? With a BUFSIZE of 1, SIGPIPE is raised after any character is input whereas -1 is only returned after I send a newline through the pipeline. – Shea Levy Dec 31 '11 at 11:36
  • So my current guess is that SIGPIPE is not strictly necessary for unbuffered blocking writes, but that for buffered (and possibly for non-blocking?) writes the asynchronicity is needed. Is that correct? – Shea Levy Dec 31 '11 at 11:38
  • 1
    Aha, and that's probably the difference in my test program too. Note my code uses a BUFSIZ buffer. If the writes are unbuffered, they complete with the first byte -- but if they're buffered, you could go on putting bytes for some time with no where for them to go. Sending SIGPIPE allows the syscall to preserve the principle of no surprise: you don't imagine you've written what could not be written. – Charlie Martin Dec 31 '11 at 17:32
  • 2
    I really like the answer: SIGPIPE lets death propagate back from the output end of the pipeline instantly. Without this it takes up to one cycle of the copy program for each of N elements of the pipe to kill the pipeline, and causes the input side to generate N lines that never reach the end. – Yttrill Feb 13 '12 at 13:01
  • 25
    This answer is incorrect. `SIGPIPE` is *not* delivered during the read, but during the `write`. You don't need to write a C program to test it, just run `cat | head`, and `pkill head` in a separate terminal. You'll see that `cat` happily lives on waiting in its `read()`—only when you type something in and press enter does `cat` die with a broken pipe, exactly because it tried to write output. – user4815162342 Jan 17 '13 at 03:18
  • 5
    -1 `SIGPIPE` can't be delivered to `B` while `B` is blocked on `read` because `SIGPIPE` isn't generated until `B` attempts the `write`. No thread can "be waiting for I/O or otherwise suspended" while calling `write` at the same time. – Dan Moulding Mar 22 '13 at 16:07
  • 4
    Can you post a full program that shows `SIGPIPE` being raised while blocked on a `read`? I cannot reproduce that behavior at all (and am not actually sure why I accepted this in the first place) – Shea Levy Mar 19 '14 at 21:30
7

https://www.gnu.org/software/libc/manual/html_mono/libc.html

This link says:

A pipe or FIFO has to be open at both ends simultaneously. If you read from a pipe or FIFO file that doesn't have any processes writing to it (perhaps because they have all closed the file, or exited), the read returns end-of-file. Writing to a pipe or FIFO that doesn't have a reading process is treated as an error condition; it generates a SIGPIPE signal, and fails with error code EPIPE if the signal is handled or blocked.

— Macro: int SIGPIPE

Broken pipe. If you use pipes or FIFOs, you have to design your application so that one process opens the pipe for reading before another starts writing. If the reading process never starts, or terminates unexpectedly, writing to the pipe or FIFO raises a SIGPIPE signal. If SIGPIPE is blocked, handled or ignored, the offending call fails with EPIPE instead.

Pipes and FIFO special files are discussed in more detail in Pipes and FIFOs.

Ankur Agarwal
  • 23,692
  • 41
  • 137
  • 208
5

I think it is to get the error handling correct without requiring a lot of code in everything writing to a pipe.

Some programs ignore the return value of write(); without SIGPIPE they would uselessly generate all output.

Programs that check the return value of write() likely print an error message if it fails; this is inappropriate for a broken pipe as it is not really an error for the whole pipeline.

jilles
  • 10,509
  • 2
  • 26
  • 39
2

Machine info:

Linux 3.2.0-53-generic #81-Ubuntu SMP Thu Aug 22 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)

I wrote this code below:

// Writes characters to stdout in an infinite loop, also counts 
// the number of characters generated and prints them in sighandler
// writestdout.c

# include <unistd.h>
# include <stdio.h>
# include <signal.h>
# include <string.h>

int writeCount = 0;    
void sighandler(int sig) {
    char buf1[30] ;
    sprintf(buf1,"signal %d writeCount %d\n", sig, writeCount);
    ssize_t leng = strlen(buf1);
    write(2, buf1, leng);
    _exit(1);

}

int main() {

    int i = 0;
    char buf[2] = "a";

    struct sigaction ss;
    ss.sa_handler = sighandler;

    sigaction(13, &ss, NULL);

    while(1) {

        /* if (writeCount == 4) {

            write(2, "4th char\n", 10);

        } */

        ssize_t c = write(1, buf, 1);
        writeCount++;

    }

}

// Reads only 3 characters from stdin and exits
// readstdin.c

# include <unistd.h>
# include <stdio.h>

int main() {

    ssize_t n ;        
    char a[5];        
    n = read(0, a, 3);
    printf("read %zd bytes\n", n);
    return(0);

}

Output:

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11486

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 429

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 281

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 490

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 433

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 318

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 468

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11866

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 496

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 284

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 271

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 416

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11268

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 427

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 8812

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 394

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 10937

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 10931

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 3554

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 499

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 283

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11133

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 451

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 493

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 233

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11397

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 492

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 547

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 441

You can see that in every instance SIGPIPE is only received after more than 3 characters are (tried to be) written by the writing process.

Does this not prove that SIGPIPE is not generated immediately after reading process terminates but after an attempt to write some more data to a closed pipe?

oHo
  • 51,447
  • 27
  • 165
  • 200
Ankur Agarwal
  • 23,692
  • 41
  • 137
  • 208