How to Handle a Broken Pipe (Sigpipe) in Python

How to handle a broken pipe (SIGPIPE) in python?

Read up on the try: statement.

try:
# do something
except socket.error, e:
# A socket error
except IOError, e:
if e.errno == errno.EPIPE:
# EPIPE error
else:
# Other error

Python - How to catch a broken pipe

Since you are writing such a small amount of data, it is all buffered and nothing is actually written to the pipe until the file is closed. During the close, an attempt is made to write data to the pipe, which fails, but your try/except clause is already done. If you flush stdout during your try/except, you should catch the error. (Although, since you are writing to the pipe in the except clause, you won't see it!)

How to prevent errno 32 broken pipe?

Your server process has received a SIGPIPE writing to a socket. This usually happens when you write to a socket fully closed on the other (client) side. This might be happening when a client program doesn't wait till all the data from the server is received and simply closes a socket (using close function).

In a C program you would normally try setting to ignore SIGPIPE signal or setting a dummy signal handler for it. In this case a simple error will be returned when writing to a closed socket. In your case a python seems to throw an exception that can be handled as a premature disconnect of the client.

Catch Broken Pipe in Python 2 AND Python 3

If all you care about are broken pipe errors, then you might want to catch socket.error and simply check whether it's indeed a broken pipe error.

You can do so using the exception's errno attribute, which is present in both Python 2 and Python 3, which means, you don't need different Python 2 vs. 3 logic (I'd argue the intent is a little clearer this way):

import socket
import errno

try:
do_something()
except socket.error as e:
if e.errno != errno.EPIPE:
# Not a broken pipe
raise
do_something_about_the_broken_pipe()

If you do care about more than broken pipes, thefourtheye's answer is appropriate and idiomatic.

IOError: [Errno 32] Broken pipe when piping: `prog.py | othercmd`

I haven't reproduced the issue, but perhaps this method would solve it: (writing line by line to stdout rather than using print)

import sys
with open('a.txt', 'r') as f1:
for line in f1:
sys.stdout.write(line)

You could catch the broken pipe? This writes the file to stdout line by line until the pipe is closed.

import sys, errno
try:
with open('a.txt', 'r') as f1:
for line in f1:
sys.stdout.write(line)
except IOError as e:
if e.errno == errno.EPIPE:
# Handle error

You also need to make sure that othercommand is reading from the pipe before it gets too big - https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer

How to prevent Brokenpipe error in my server script

There was a bunch of things I had to do to make this work.

First, the Broken pipe can occur at two different places in the server code. First is when a new client connects and the server tries to send message New Client Connected to all the clients and second is when an existing client sends a message. So, we need to handle the exception at both the places.

So, we put try/except on both the blocks. where it says if c != conn.

Now, about how to handle the exception.

As I first thought, simply removing the client c from the list on clients will work, but the for loop, for c in clients will throw out runtime error as we try to modify the set clients during iteration.

I tried different methods to get over this problem, but this is the most efficient working method I got.

I changed clients from a set() to an empty list [ ]

Then I changed clients.add to clients.append

Then I changed the for loop to for c in range(len(clients)) and used clients[c] to access clients.

But when I tried this, I saw that the if statement if clients[c] != conn may throw out list index out of bounds error if the program tries to go over a non existant client after removal. So I put it too in a try/except block and let the program continue on exception.

for c in range(len(clients)):
try:
if clients[c] != conn:
try:
message = connection_message.encode(FORMAT)
msg_length = len(message)
send_length = str(msg_length).encode(FORMAT)
send_length += b' ' * (HEADER - len(send_length))
clients[c].sendall(send_length)
clients[c].sendall(message)
except:
clients.remove(clients[c])
except:
continue

The last problem was that even after removal of the client, the thread is still alive, so the active thread count returns more than the number of clients connected. So, instead of printing out the number of active connection as number of alive threads - 1, I print len(clients) + 1, + 1 because on connection of a new client, it prints this line before appending the client to list.

print(f"[ACTIVE CONNECTIONS] {len(clients) + 1}")

So, the entire program now is:

import socket
import threading

HEADER = 64
PORT = 5050
SERVER = socket.gethostbyname(socket.gethostname())
ADDR = (SERVER, PORT)
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ADDR)

clients = []
clients_lock = threading.Lock()

def handle_client(conn, addr):
name = conn.recv(HEADER).decode(FORMAT)
if name:
name = int(name)
msg_name = conn.recv(name).decode(FORMAT)
print(f"[NEW CONNECTION] {msg_name} connected.")
connection_message = f"{msg_name} connected."
with clients_lock:
for c in range(len(clients)):
try:
if clients[c] != conn:
try:
message = connection_message.encode(FORMAT)
msg_length = len(message)
send_length = str(msg_length).encode(FORMAT)
send_length += b' ' * (HEADER - len(send_length))
clients[c].sendall(send_length)
clients[c].sendall(message)
except:
clients.remove(clients[c])

except:
continue

with clients_lock:
clients.append(conn)

connected = True
try:
while connected:
msg_length = conn.recv(HEADER).decode(FORMAT)
if msg_length:
msg_length = int(msg_length)
msg1 = conn.recv(msg_length).decode(FORMAT)
msg = f"{msg_name}: {msg1}"
if msg1 == DISCONNECT_MESSAGE:
connected = False
print(f"{msg}")
with clients_lock:
for c in range(len(clients)):
try:
if clients[c] != conn:
try:
message = msg.encode(FORMAT)
msg_length = len(message)
send_length = str(msg_length).encode(FORMAT)
send_length += b' ' * (HEADER - len(send_length))
clients[c].sendall(send_length)
clients[c].sendall(message)
except:
clients.remove(clients[c])
except:
continue
msg = f"You: {msg1}"
message = msg.encode(FORMAT)
msg_length = len(message)
send_length = str(msg_length).encode(FORMAT)
send_length += b' ' * (HEADER - len(send_length))
conn.send(send_length)
conn.send(message)

finally:
with clients_lock:
clients.remove(conn)
conn.close()

def start():
server.listen()
print(f"[LISTENING] Server is listening on {SERVER}")
while True:
conn, addr = server.accept()
thread = threading.Thread(target=handle_client, args=(conn, addr))
thread.daemon = True
thread.start()
print(f"[ACTIVE CONNECTIONS] {len(clients) + 1}")

print("[STARTING] server is starting...")
start()

Broken pipe error when trying to send data from server to client in Python sockets

After trying out the code, I think the biggest problem you have is that the server is trying to send on the wrong socket. i.e. this line:

s.send("hey".encode())

should be rewritten like this:

conn.send("hey".encode())

As it is, you are trying to send() on the TCP accepting-socket rather than on the TCP connection to the client, which doesn't make sense. On my (MacOS/X) system, the server process prints this error output:

Jeremys-Mac-mini-2:~ jaf$ python server.py
('address: ', '127.0.0.1')
Traceback (most recent call last):
File "server.py", line 18, in <module>
server()
File "server.py", line 14, in server
s.send("hey".encode())
socket.error: [Errno 57] Socket is not connected

Why does python generate sigpipe exception on closing a fifo file?

It is because Python won't clear the write buffer when fifo.write fails. So the buffer will be wrote to the broken pipe again when executing fifo.close, which causes the second SIGPIPE.

I found the reason with the help of strace. Here are some details.

First, modify a small part of that Python code, as following,

#!/usr/bin/env python3
import os
import signal
import subprocess

# The FIFO file.
os.mkfifo('tmp.fifo')

# A subprocess to simply discard any input from the FIFO.
FNULL = open(os.devnull, 'w')
proc = subprocess.Popen(['/bin/cat', 'tmp.fifo'], stdout=FNULL, stderr=FNULL)
print('pid = %d' % proc.pid)

# Open the FIFO, and MUST BE BINARY MODE.
fifo = open('tmp.fifo', 'wb')

i = 0
# Endlessly write to the FIFO.
while True:

# Try to write to the FIFO, restart the subprocess on demand, until succeeded.
while True:
try:
# Optimistically write to the FIFO.
fifo.write(f'hello{i}'.encode())
fifo.flush()
except IOError as e:
# The subprocess died. Close the FIFO and reap the subprocess.
print('IOError is occured.')
fifo.close()
os.kill(proc.pid, signal.SIGKILL)
proc.wait()

# Start the subprocess again.
proc = subprocess.Popen(['/bin/cat', 'tmp.fifo'], stdout=FNULL, stderr=FNULL)
print('pid = %d' % proc.pid)
fifo = open('tmp.fifo', 'wb')
else:
# The write goes on well.
break
os.kill(proc.pid, signal.SIGKILL)
i += 1

and save it as test.py.

Then run strace -o strace.out python3 test.py in the shell. Check the strace.out and we can find something like

openat(AT_FDCWD, "tmp.fifo", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 4
fstat(4, {st_mode=S_IFIFO|0644, st_size=0, ...}) = 0
ioctl(4, TCGETS, 0x7ffcba5cd290) = -1 ENOTTY (Inappropriate ioctl for device)
lseek(4, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)
write(4, "hello0", 6) = 6
kill(35626, SIGKILL) = 0
write(4, "hello1", 6) = 6
kill(35626, SIGKILL) = 0
write(4, "hello2", 6) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=35625, si_uid=1000} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=35626, si_uid=1000, si_status=SIGKILL, si_utime=0, si_stime=0} ---
write(1, "IOError is occured.\n", 20) = 20
write(4, "hello2", 6) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=35625, si_uid=1000} ---
close(4) = 0
write(2, "Traceback (most recent call last"..., 35) = 35
write(2, " File \"test.py\", line 26, in <m"..., 39) = 39

Note that Python tried to write hello2 twice, during fifo.flush and fifo.close respectively. The output explains why two SIGPIPE exceptions are generated well.

In order to solve the problem, we can use open('tmp.fifo', 'wb', buffering=0) to disable the write buffer. Then only one SIGPIPE exception will be generated.

BrokenPipeError in Python but not in Perl

What's going on here is that in both cases you have a process writing to a pipe whose read end was closed (by head exiting after a certain number of bytes).

This causes a SIGPIPE signal to be sent to the writing process. By default this kills the process. The process can ignore the signal if it wants to, which just makes the write call fail with an EPIPE error.

Starting with version 3.3, Python raises a BrokenPipeError exception in this case, so it looks like Python 1) ignores SIGPIPE by default and 2) translates EPIPE to a BrokenPipeError exception.

Perl does not ignore or handle signals by default. That means it gets killed by SIGPIPE in your example, but because it is not the last command in a pipeline (that would be head here), the shell just ignores it. You can make it more visible by not using a pipeline:

perl pipe.pl > >(head -n3000 >/dev/null)

This piece of bash trickery makes perl write to a pipe, but not as part of a shell pipeline. I can't test it now, but at minimum this will set $? (the command exit status) to 141 in the shell (128 + signal number, which for SIGPIPE is 13), and it may also report a Broken pipe.

You can deal with it manually in the Perl code, though:

  • Variant 1: Throw an error from the signal handler

    $SIG{PIPE} = sub { die "BrokenPipeError" };
  • Variant 2: Ignore the signal, handle write errors

    $SIG{PIPE} = 'IGNORE';
    ...
    print $i, "\n" or die "Can't print: $!";

    Note that in this case you have to think about buffering, however. If you don't enable autoflush (as in STDOUT->autoflush(1)) and output is going to a pipe or file, Perl will collect the text in an internal buffer first (and the print call will succeed). Only when the buffer gets full (or when the filehandle is closed, whichever happens first) is the text actually written out and the buffer emptied. This is why close can also report write errors.



Related Topics



Leave a reply



Submit