Python and FIFOs
Asked Answered
D

2

6

I was trying to understand FIFOs using Python under linux and I found a strange behavior i don't understand.

The following is fifoserver.py

import sys
import time

def readline(f):
    s = f.readline()
    while s == "":
        time.sleep(0.0001)
        s = f.readline()
    return s

while True:
    f = open(sys.argv[1], "r")
    x = float(readline(f))
    g = open(sys.argv[2], "w")
    g.write(str(x**2) + "\n")
    g.close()
    f.close()
    sys.stdout.write("Processed " + repr(x) + "\n")

and this is fifoclient.py

import sys
import time

def readline(f):
    s = f.readline()
    while s == "":
        time.sleep(0.0001)
        s = f.readline()
    return s

def req(x):
    f = open("input", "w")
    f.write(str(x) + "\n")
    f.flush()
    g = open("output", "r")
    result = float(readline(g))
    g.close()
    f.close()
    return result

for i in range(100000):
    sys.stdout.write("%i, %s\n" % (i, i*i == req(i)))

I also created two FIFOs using mkfifo input and mkfifo output.

What I don't understand is why when I run the server (with python fifoserver.py input output) and the client (with python fifoclient.py) from two consoles after some requests the client crashes with a "broken pipe" error on f.flush(). Note that before crashing I've seen from a few hundreds to several thousands correctly processed requests running fine.

What is the problem in my code?

Desertion answered 22/2, 2011 at 21:30 Comment(6)
I think you've got a copy-and-paste typo here. The client and server code is the same.Allinclusive
@Jeff Bauer: Sorry... I think I'll never get used to this stupid clipboard handling in X.Desertion
Your server is leaking file handles: each iteration of the loop, you're opening a new file handle to sys.argv[2] and never closing it. Don't assume the garbage collector will take care of that for you -- clean it up explicitly with a call to close(), or better yet, use a with statement.Bostow
And your client is leaking file handles too, for the same reason.Bostow
@Adam: The files would get closed automatically, since the assignment f = open(...) uses the name f for a new file object, thereby dropping the last reference to the old one. The old file object gets garbage collected immediately, which includes that it is closed.Nadabus
@Adam Rosenfield (and two other comment upvoters): No. I was using a python version that didn't need to explicitly close, but even adding it doesn't fix the problem. Now the code closes the handles and also is compatible with both python2.x and python3.x. Still the problem remains on both versions.Desertion
P
5

As other comments have alluded to, you have a race condition.

I suspect that in the failing case, the server gets suspended after one of these lines:

g.write(str(x**2) + "\n")
g.close()

The client is then able to read the result, print it to the screen, and loop back. It then reopens f - which succeeds, because it's still open on the server side - and writes the message. Meanwhile, the server has managed to close f. Next, the flush on the client side executes a write() syscall on the pipe, which triggers the SIGPIPE because it's now closed on the other side.

If I'm correct, you should be able to fix it by moving the server's f.close() to be above the g.write(...).

Poleyn answered 23/2, 2011 at 5:24 Comment(2)
This was the problem for broken pipe! And it's also what I didn't understand about FIFOs... I thought that reopening a pipe after a open/write/close sequence from a client would have stalled if the server only did open/read and didn't closed yet (so basically the server was in the same "session" of the first open). This behavior makes IMO much harder to use two FIFOs for bidirectional communication.Desertion
@6502: Yes. In practice, UNIX-domain sockets are usually much better suited for such communication between local processes.Poleyn
F
0

I am not a unix expert, but my guess is that you eventually end up with the file closed in both processes, and the open-for-write happens next. As there is nothing to accept the data, the pipe breaks.

I don't understand why you are opening and closing the pipe all the time.

Try starting the process that reads the pipe first, have it open the pipe and it will sit waiting for data.

Then start the pipe-writer, and have it pump out all the data you want to send. It will stall if it gets ahead. When the writer closes the pipe, the reader gets zero bytes instead of blocking, and should close. IIRC, Python detects this and returns EOF.

Freeload answered 22/2, 2011 at 22:31 Comment(3)
With a FIFO opening for write doesn't breaks if there is no one reading... it just waits. Actually I can run first either the server or the client and everything works for several requests (I've seen from a few hundreds to 60000+). Then, for reasons I don't understand, I get a broken pipe on the flush command in the client.Desertion
Don't close the file. While thc server is writing to the stdout, the file f is closed, so the client's flush will fail.Freeload
The server closes the input only after writing the output, so the client must have been already passed the flush call and opened the output FIFO for reading. Am I missing something?Desertion

© 2022 - 2024 — McMap. All rights reserved.