SIGPIPE, EPIPE

If you run the following shell script, it will terminate:

seq | head -n 1

But why?

The command from above creates two processes, which are connected by a <man:pipe(2)>. seq writes its infinite sequence of numbers to STDOUT, while head reads the other end of the pipe as STDIN. It reads the first line and then exits. But what stops seq from running until the collapse of the universe?

The Linux kernel only allocates a finite sized buffer for that pipe. The size of that buffer changed over time from 4 KiB to 64 KiB to configurable, but still defaults to 1 MiB. See <man:pipe(7)> for more details about the getting the size.

After seq filled up that buffer its next call to <man:write(2)> will block until the reader has read some data and thus has freed some space in the buffer. But as soon as head terminated, there will never be any other reader who can do that. The Linux kernel thus sends SIGPIPE to seq to signal it, that no reader is left. The default action for that signal is terminate the process.

Inheritance

Now what happens when we do that instead:

seq | ( sleep 1m & head -n 1)

Eventually that will also terminate:

  • Both processes sleep and head (and also the intermediate $SHELL) inherited the pipe as their STDIN.
  • The head process will terminate after it has done its job of reading and printing the first line.
  • But the sleep process will stay around for one minute and will remain an possible reader.
  • As such the seq process will only get killed through SIGPIPE when all readers finally terminated.

This is important when you write a daemon, which forks into the background: It should close all inherited file descriptors as to not function as a potential reader to any inherited pipe. (I fixed at least one such problem in the Apache Web Server, where it did not do that correctly: It kept open a pipe used by the SysV init script used to start it.)

Sockets

Now lets take a look at some simple TCP server:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', 12345))
s.listen(1)
conn, addr = s.accept()  # blocks
conn.shutdown(socket.SHUT_RDWR)
conn.close()
s.close()

And here the corresponding TCP client:

import time
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 12345))
time.sleep(1)
s.sendall('Hello' * 1000)

You might already have guessed it: It will also terminate with the Python equivalent of SIGPIPE:

socket.error: [Errno 32] Broken pipe

This also can happen when you use some libraries, which communicate with servers and that connection suddenly breaks. One example is Debian Bug #190072: On a system using LDAP as back-end for <man:getent(1)> NSS uses libldap to open a connection to the LDAP server. Internally it uses liblber for serializing the data, which uses <man:write(2)> to send its data. If something goes wrong their, your process will get SIGPIPE and terminate. (I recently fixed such a bug in Univention Directory Notifier, where the process got terminated when the OpenLDAP server closed the connection due to some timeout.)

Mitigation

  1. You can use <man:send(2)> instead of <man:write(2)>, where you can specify flags=MSG_NOSIGNAL to get errno=EPIPE instead of being killed by SIGPIPE. (liblber cannot be changed as it also is supposed to work on local files, where <man:send(2)> would not work.)

  2. <man:pipe(7)> has this interesting sentence:

    If the calling process is ignoring SIGPIPE, then <man:write(2)> fails with the error EPIPE.

    So using the following code should work:

    #include <signal.h>
    
    struct sigaction oldact, act = {
        .sa_handler = SIG_IGN,
        .sa_flags = 0,
    };
    sigaction(SIGPIPE, &act, &oldact);
    // call LDAP functions here ...
    sigaction(SIGPIPE, &oldact, NULL);
    

    (I skipped calling <man:sigemptyset(3)> here as the structure is zero-filled by the C compiler.)

    This has the draw-back that also all other calls to <man:write(2)> will return EPIPE instead of getting a SIGPIPE. So you have to be careful if you use other libraries in between or write lots of output and expect your program to get terminated when used in a pipe.

Example

Here is some simple C program for your tests. It defaults to signal handling, but ignores SIGPIPE when called with any command line argument not equal 0.

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

static void handler(int sig) {
	struct sigaction act = {
		.sa_handler = SIG_DFL,
		.sa_flags = 0,
	};
	sigaction(sig, &act, NULL);
	printf("Got signal %d\n", sig);
	kill(getpid(), sig);
}

int main(int argc, char **argv) {
	int pipefd[2];
	bool ignore = argc > 1 && argv[1][0] != '0';

	struct sigaction oldact, act = {
		.sa_handler = ignore ? SIG_IGN : handler,
		.sa_flags = 0,
	};
	sigaction(SIGPIPE, &act, &oldact);

	pipe(pipefd);
	close(pipefd[0]); // reader
	if (write(pipefd[1], "Hello", 6) < 0)
		perror("write()");

	sigaction(SIGPIPE, &oldact, NULL);
	return 0;
}
Written on January 17, 2019