using FIFOs for input and output in python

Maori picture Maori · Jun 9, 2016 · Viewed 7.4k times · Source

To communicate with a shell, which is started once and runs in a separate process, I used Popen from subprocess.

import os
from subprocess import Popen, PIPE

def server():
    FIFO_PATH = '/tmp/my_fifo'
    FIFO_PATH2 = '/tmp/in_fifo'
    if os.path.exists(FIFO_PATH):
        os.unlink(FIFO_PATH)
    if os.path.exists(FIFO_PATH2):
        os.unlink(FIFO_PATH2)

    if not os.path.exists(FIFO_PATH2):
        os.mkfifo(FIFO_PATH2)
        in_fifo = open(FIFO_PATH2, 'rw+')
        print "in_fifo:", in_fifo

    if not os.path.exists(FIFO_PATH):
        os.mkfifo(FIFO_PATH)
        my_fifo = open(FIFO_PATH, 'rw+')
        print "my_fifo:", my_fifo

    p = Popen(['python', '-u', 'shell.py'], shell=False, stdin=in_fifo, stdout=my_fifo)

def read():
    FIFO_PATH = '/tmp/my_fifo'
    i=0
    while i < 10:
        ++i
        print i, open(FIFO_PATH, 'r').readline()

def write(input):
    FIFO_PATH2 = '/tmp/in_fifo'

    pipe = open(FIFO_PATH2, 'w+')
    pipe.write(input+'\n')

def test():
    server()
    write('test')
    read()

and the shell.py

Input = ' '
print 'shell called'
while Input!= 'z':
    Input=raw_input()
    print 'input ', Input

    if Input != '':
        if Input == 'test':
            print 'Yeehhaaaaa it works'

so calling test() give the following result

in_fifo: <open file '/tmp/in_fifo', mode 'rw+' at 0x7f0a4e17ed20>
my_fifo: <open file '/tmp/my_fifo', mode 'rw+' at 0x7f0a4e17edb0>
0 shell called

0 input  test

Questions

Why is only the first line printed? How to print all lines?

Also I'm not sure about the proper use of FIFOs. Maybe there are better ways to get this done. I'm open for any suggestions.

Using p to call p.stdin.write() and p.stdout.readline() is no solution for me because I have to call the functions from javascript without having the instance p.

Answer

David Cullen picture David Cullen · Jun 9, 2016

From the man page for mkfifo:

Opening a FIFO for reading normally blocks until some other process opens the same FIFO for writing, and vice versa. See fifo(7) for nonblocking handling of FIFO special files.

So the second time you open the FIFO for reading, the call blocks. This can be seen in the traceback after pressing Ctrl+C:

^CTraceback (most recent call last):
0
Traceback (most recent call last):
  File "shell_fifo.py", line 51, in <module>
  File "shell.py", line 4, in <module>
    test()
  File "shell_fifo.py", line 48, in test
        read()
  File "shell_fifo.py", line 29, in read
    print i, open(FIFO_PATH, 'r').readline() # read() is blocked here
KeyboardInterrupt
Input=raw_input()
KeyboardInterrupt

Change your read function so that it only opens the FIFO once:

def read():
    FIFO_PATH = '/tmp/my_fifo'
    i = 0
    with open(FIFO_PATH, 'r') as read_fifo:
        while i < 10:
            i += 1
            print i, read_fifo.readline().rstrip()

You should see output like this:

in_fifo: <open file '/tmp/in_fifo', mode 'rw+' at 0x7f1ba655b5d0>
my_fifo: <open file '/tmp/my_fifo', mode 'rw+' at 0x7f1ba655b540>
1 shell called
2 input  test
3 Yeehhaaaaa it works