Solving problem is about exposing yourself to as many situations as possible like Python subprocess readlines() hangs and practice these strategies over and over. With time, it becomes second nature and a natural way you approach any problems in general. Big or small, always start with a plan, use other strategies mentioned here till you are confident and ready to code the solution.
In this post, my aim is to share an overview the topic about Python subprocess readlines() hangs, which can be followed any time. Take easy to follow this discuss.
The task I try to accomplish is to stream a ruby file and print out the output. (NOTE: I don’t want to print out everything at once)
from subprocess import Popen, PIPE, STDOUT import pty import os file_path = '/Users/luciano/Desktop/ruby_sleep.rb' command = ' '.join(["ruby", file_path]) master, slave = pty.openpty() proc = Popen(command, bufsize=0, shell=True, stdout=slave, stderr=slave, close_fds=True) stdout = os.fdopen(master, 'r', 0) while proc.poll() is None: data = stdout.readline() if data != "": print(data) else: break print("This is never reached!")
puts "hello" sleep 2 puts "goodbye!"
Streaming the file works fine. The hello/goodbye output is printed with the 2 seconds delay. Exactly as the script should work. The problem is that readline() hangs in the end and never quits. I never reach the last print.
I know there is a lot of questions like this here a stackoverflow but non of them made me solve the problem. I’m not that into the whole subprocess thing so please give me a more hands-on/concrete answer.
Fix unintended code. (nothing to do with the actual error)
I assume you use
pty due to reasons outlined in Q: Why not just use a pipe (popen())? (all other answers so far ignore your “NOTE: I don’t want to print out everything at once”).
pty is Linux only as said in the docs:
Because pseudo-terminal handling is highly platform dependent, there
is code to do it only for Linux. (The Linux code is supposed to work
on other platforms, but hasn’t been tested yet.)
It is unclear how well it works on other OSes.
You could try
import sys import pexpect pexpect.run("ruby ruby_sleep.rb", logfile=sys.stdout)
stdbuf to enable line-buffering in non-interactive mode:
from subprocess import Popen, PIPE, STDOUT proc = Popen(['stdbuf', '-oL', 'ruby', 'ruby_sleep.rb'], bufsize=1, stdout=PIPE, stderr=STDOUT, close_fds=True) for line in iter(proc.stdout.readline, b''): print line, proc.stdout.close() proc.wait()
pty from stdlib based on @Antti Haapala’s answer:
#!/usr/bin/env python import errno import os import pty from subprocess import Popen, STDOUT master_fd, slave_fd = pty.openpty() # provide tty to enable # line-buffering on ruby's side proc = Popen(['ruby', 'ruby_sleep.rb'], stdin=slave_fd, stdout=slave_fd, stderr=STDOUT, close_fds=True) os.close(slave_fd) try: while 1: try: data = os.read(master_fd, 512) except OSError as e: if e.errno != errno.EIO: raise break # EIO means EOF on some systems else: if not data: # EOF break print('got ' + repr(data)) finally: os.close(master_fd) if proc.poll() is None: proc.kill() proc.wait() print("This is reached!")
All three code examples print ‘hello’ immediately (as soon as the first EOL is seen).
leave the old more complicated code example here because it may be referenced and discussed in other posts on SO
pty based on @Antti Haapala’s answer:
import os import pty import select from subprocess import Popen, STDOUT master_fd, slave_fd = pty.openpty() # provide tty to enable # line-buffering on ruby's side proc = Popen(['ruby', 'ruby_sleep.rb'], stdout=slave_fd, stderr=STDOUT, close_fds=True) timeout = .04 # seconds while 1: ready, _, _ = select.select([master_fd], , , timeout) if ready: data = os.read(master_fd, 512) if not data: break print("got " + repr(data)) elif proc.poll() is not None: # select timeout assert not select.select([master_fd], , , 0) # detect race condition break # proc exited os.close(slave_fd) # can't do it sooner: it leads to errno.EIO error os.close(master_fd) proc.wait() print("This is reached!")
Not sure what is wrong with your code, but the following seems to work for me:
#!/usr/bin/python from subprocess import Popen, PIPE import threading p = Popen('ls', stdout=PIPE) class ReaderThread(threading.Thread): def __init__(self, stream): threading.Thread.__init__(self) self.stream = stream def run(self): while True: line = self.stream.readline() if len(line) == 0: break print line, reader = ReaderThread(p.stdout) reader.start() # Wait until subprocess is done p.wait() # Wait until we've processed all output reader.join() print "Done!"
Note that I don’t have Ruby installed and hence cannot check with your actual problem. Works fine with
Basically what you are looking at here is a race condition between your
proc.poll() and your
readline(). Since the input on the
master filehandle is never closed, if the process attempts to do a
readline() on it after the ruby process has finished outputting, there will never be anything to read, but the pipe will never close. The code will only work if the shell process closes before your code tries another readline().
Here is the timeline:
readline() print-output poll() readline() print-output (last line of real output) poll() (returns false since process is not done) readline() (waits for more output) (process is done, but output pipe still open and no poll ever happens for it).
Easy fix is to just use the subprocess module as it suggests in the docs, not in conjunction with openpty:
Here is a very similar problem for further study:
proc = Popen(command, bufsize=0, shell=True, stdout=PIPE, close_fds=True) for line in proc.stdout: print line print("This is most certainly reached!")
As others have noted,
readline() will block when reading data. It will even do so when your child process has died. I am not sure why this does not happen when executing
ls as in the other answer, but maybe the ruby interpreter detects that it is writing to a PIPE and therefore it will not close automatically.