Search code examples
pythonpython-3.xsubprocessstdin

Why stdin.readline doesn't read until the end


I'm new to python and i try to send information to one subprocess from other python script but the subprocess dont read anything until the main stop sending.

I try to send new lines and lines that ends on '\n'. I understand that my child process is being blocked until the stream finishes, but if I send \ n or directly stdin.write ('\ n'), it should read correctly, but it does not

Main process:

import subprocess
import time

child = subprocess.Popen("python3 example.py", shell=True, stdin=subprocess.PIPE, universal_newlines=True)
s = "this is the message"
print("MAIN:The child pid is: " + str(child.pid))
for i in range(0, 5):
    print("MAIN:iteration send:" + str(i))
    msg = s + "-" + str(i) + "\n"
    child.stdin.writelines(msg)
    time.sleep(1)
child.kill()

Subprocess:

import time
from sys import stdin

while True:
    try:
        print("CHILD:before read")
        s = stdin.readline()
        print("CHILD:after read")
        print("CHILD:message received is:" + s)
    except EOFError as err:
        print("M_READ_ERROR")
    time.sleep(1)

And my output is this

MAIN:The child pid is: 18041
MAIN:iteration send:0
CHILD:before read
MAIN:iteration send:1
MAIN:iteration send:2
MAIN:iteration send:3
MAIN:iteration send:4
CHILD:after read
CHILD:message received id:this is the message-0

But I expected something like:

MAIN:The child pid is: 18041
MAIN:iteration send:0
CHILD:before read
CHILD:after read
CHILD:message received id:this is the message-0
MAIN:iteration send:1
CHILD:before read
CHILD:after read
CHILD:message received id:this is the message-1
MAIN:iteration send:2
CHILD:before read
CHILD:after read
CHILD:message received id:this is the message-2
MAIN:iteration send:3
CHILD:before read
CHILD:after read
CHILD:message received id:this is the message-3
MAIN:iteration send:4
CHILD:before read
CHILD:after read
CHILD:message received id:this is the message-4

Solution

  • Your pipe is using the default buffer size for your system (io.DEFAULT_BUFFER_SIZE). The reader is blocked because the buffer has not yet filled, so there's no indicator on the stream that a read is available.

    To fix this, you need to control buffering. There are two ways.

    First, you could do an explicit flush after each write.

    child.stdin.writelines(msg)
    child.stdin.flush()
    

    This effectively implements line buffering, but in your own code.

    Second, you could select buffering mode in the Popen() call by passing the bufsize kwarg. Positive bufsizes greater than 1 set a buffer size of that value, which means that your reader will get ready signals at intervals as that buffer is filled. But there are special cases too:

    • 0 means unbuffered, so that each write flushes immediately;
    • 1 means line-buffered, which is a special case where in user-space the io library scans writes for newlines and flushes after them.

    You can pass bufsize=1 to force flush after newlines. In python3, this depends on universal_newlines=True, but you have this already.