33

I have a named pipe in linux and i want to read it from python. The problem is that the python process 'consumes' one core (100%) continuously. My code is the following:

FIFO = '/var/run/mypipe'
os.mkfifo(FIFO)
with open(FIFO) as fifo:
    while True:
        line = fifo.read()

I want to ask if the 'sleep' will help the situation or the process going to loss some input data from pipe. I can't control the input so i don't know the frequency of data input. I read about select and poll but i didn't find any example for my problem. Finally, i want to ask if the 100% usage will have any impact on the data input(loss or something?).

edit: I don't want to break the loop. I want the process runs continuously and 'hears' for data from the pipe.

user1005633
  • 625
  • 3
  • 9
  • 23

2 Answers2

53

In typical UNIX fashion, read(2) returns 0 bytes to indicate end-of-file which can mean:

  • There are no more bytes in a file
  • The other end of a socket has shutdown the connection
  • The writer has closed a pipe

In your case, fifo.read() is returning an empty string, because the writer has closed its file descriptor.

You should detect that case and break out of your loop:

reader.py:

import os
import errno

FIFO = 'mypipe'

try:
    os.mkfifo(FIFO)
except OSError as oe: 
    if oe.errno != errno.EEXIST:
        raise

print("Opening FIFO...")
with open(FIFO) as fifo:
    print("FIFO opened")
    while True:
        data = fifo.read()
        if len(data) == 0:
            print("Writer closed")
            break
        print('Read: "{0}"'.format(data))

Example session

Terminal 1:

$ python reader.py 
Opening FIFO...
<blocks>

Terminal 2:

$ echo -n 'hello' > mypipe 

Terminal 1:

FIFO opened
Read: "hello"
Writer closed
$ 

Update 1 - Continuously re-open

You indicate that you want to keep listening for writes on the pipe, presumably even after a writer has closed.

To do this efficiently, you can (and should) take advantage of the fact that

Normally, opening the FIFO blocks until the other end is opened also.

Here, I add another loop around open and the read loop. This way, once the pipe is closed, the code will attempt to re-open it, which will block until another writer opens the pipe:

import os
import errno

FIFO = 'mypipe'

try:
    os.mkfifo(FIFO)
except OSError as oe:
    if oe.errno != errno.EEXIST:
        raise

while True:
    print("Opening FIFO...")
    with open(FIFO) as fifo:
        print("FIFO opened")
        while True:
            data = fifo.read()
            if len(data) == 0:
                print("Writer closed")
                break
            print('Read: "{0}"'.format(data))

Terminal 1:

$ python reader.py 
Opening FIFO...
<blocks>

Terminal 2:

$ echo -n 'hello' > mypipe 

Terminal 1:

FIFO opened
Read: "hello"
Writer closed
Opening FIFO...
<blocks>

Terminal 2:

$ echo -n 'hello' > mypipe 

Terminal 1:

FIFO opened
Read: "hello"
Writer closed
Opening FIFO...
<blocks>

... and so on.


You can learn more by reading the man page for pipes:

Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
  • i don't want to break the loop. I want to read continuously from it. The script waits data from another process. If it take a string with a specific string it do some api calls... – user1005633 Aug 22 '16 at 22:56
  • 1
    *"I want to read continuously from it."* No you don't. That's what's causing your 100% CPU utilization. As you can see in my updated example, `open` blocks until there is a writer. What you want to do is close the pipe, and then allow `open` to block as you re-open it. See my update 1. – Jonathon Reinhart Aug 22 '16 at 23:31
  • I highly suggest you fully read the man pages I've linked to, and completely understand the opening, reading, and writing semantics of pipes/fifos. – Jonathon Reinhart Aug 22 '16 at 23:41
  • Using `for line in fifo` instead of `fifo.read()` also seems to help. – huggie Mar 08 '19 at 14:01
  • 1
    @huggie That works if your pipe is newline-terminated strings. – Jonathon Reinhart Mar 08 '19 at 16:46
13

(Years later) If I'm understanding the OP's use case using for ... in ... does exactly what is desired:

import os

FIFO = 'myfifo'
os.mkfifo(FIFO)
with open(FIFO) as fifo:
    for line in fifo:
        print(line)

This program patiently waits for input from the fifo until is is provided, then prints it on the screen. No CPU is used in the meantime.

This is also the more idiomatic way in Python so I would recommend it rather than using read() directly.

If the client side writing to the fifo closes, the for loop ends and the program quits. If you wanted it to reopen the fifo to wait for the next client to open it you can put the for section into a while loop:

import os

FIFO = 'myfifo'
os.mkfifo(FIFO)
while True:
    with open(FIFO) as fifo:
        for line in fifo:
            print(line)

This will reopen the fifo and wait as usual.

Tristan
  • 1,730
  • 3
  • 20
  • 25
  • 1
    (more years later) As stated in the comments on the accepted answer, `for line in fifo` only works if `fifo` contains newline-separated strings – clemisch Jun 17 '19 at 15:52
  • Yes; so annoying! I started rethinking using a fifo for interprocess communication for this reason. There are some much more robust approaches for this, like multiprocessing.Queue https://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes – Tristan Sep 05 '19 at 18:06