Python subprocess readlines() hangs

后端 未结 4 1832
独厮守ぢ
独厮守ぢ 2020-11-22 01:43

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)

相关标签:
4条回答
  • 2020-11-22 01:51

    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:

    http://docs.python.org/library/subprocess.html

    Here is a very similar problem for further study:

    Using subprocess with select and pty hangs when capturing output

    0 讨论(0)
  • 2020-11-22 02:03

    Try this:

    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.

    0 讨论(0)
  • 2020-11-22 02:05

    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 pexpect:

    import sys
    import pexpect
    
    pexpect.run("ruby ruby_sleep.rb", logfile=sys.stdout)
    

    Or 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()
    

    Or using 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

    Or using 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)[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!")
    
    0 讨论(0)
  • 2020-11-22 02:12

    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 ls, though.

    0 讨论(0)
提交回复
热议问题