3

I've read many of the questions related to this and learned a lot but I still haven't been able to solve my problem. I'm building a wxPython app that runs a c++ executable and displays the stdout from that executable in real time. I've run into several strange results trying to make this work. Here's my current setup/problem:

//test.cc (compiled as test.out with gcc 4.5.2)
#include <stdio.h>
int main()
{
  FILE* fh = fopen("output.txt", "w");
  for (int i = 0; i < 10000; i++)
  {
      printf("Outputting: %d\n", i);
      fprintf(fh, "Outputting: %d\n", i);
  }
  fclose(fh);
  return 0;
}

#wxPythonScript.py (running on 2.7 interpreter)
def run(self):
  self.externalBinary = subprocess.Popen(['./test.out'], shell=False, stdout=subprocess.PIPE)
  while not self.wantAbort:
      line = self.externalBinary.stdout.readline()
      wx.PostEvent(self.notifyWindow, Result_Event(line, Result_Event.EVT_STDOUT_ID))
    print('Subprocess still running')
  print('Subprocess aborted smoothly')

If I run the above code the subprocess takes a very long time to complete, even though all it has to do is write out the data and exit. However if I run the following it completes very quickly:

#wxPythonScript.py (running on 2.7 interpreter)
def run(self):
  outFile = open('output.txt', 'r+')
  self.externalBinary = subprocess.Popen(['./test.out'], shell=False, stdout=outFile)
  while not self.wantAbort:
      #line = self.externalBinary.stdout.readline()
      #wx.PostEvent(self.notifyWindow, Result_Event(line, Result_Event.EVT_STDOUT_ID))
    print('Subprocess still running')
  print('Subprocess aborted smoothly')

So basically whenever I redirect stdout from the subprocess to a PIPE it slows down/hangs, but if I write it to a file or don't redirect it at all then it's fine. Why is that?

anderspitman
  • 9,230
  • 10
  • 40
  • 61
  • Please search. This gets asked frequently. – S.Lott Sep 30 '11 at 10:11
  • possible duplicate of [read subprocess stdout line by line](http://stackoverflow.com/questions/2804543/read-subprocess-stdout-line-by-line) – S.Lott Sep 30 '11 at 10:12
  • I did I promise. I've read a ton. 2 days ago I knew nothing about subprocess, threads, wx events, stdout. I will keep looking of course. Can you recommend some keywords to search for? Maybe I'm looking for the wrong thing. Mostly I've searched: non blocking stdout.readline python, subprocess stdout, etc. – anderspitman Sep 30 '11 at 10:15
  • Yep already read that one. As you can see my code is basicaly the same as that in the answer but I'm not getting the results I would expect. – anderspitman Sep 30 '11 at 10:17
  • "but I'm not getting the results I would expect". Is this question just about the performance of the buffering? Is that all? If so, please **update** the question to indicate that it works, but you don't like the performance. – S.Lott Sep 30 '11 at 11:13
  • @eryksun: Are you referring to something like this: http://stackoverflow.com/questions/375427/non-blocking-read-on-a-subprocess-pipe-in-python. My problem is that I need to constantly monitor stdout for changes. I can't wait for the subprocess to finish and then read stdout. – anderspitman Sep 30 '11 at 12:31
  • @S.Lott: I've done some more testing and narrowed down my question a bit. – anderspitman Sep 30 '11 at 12:36
  • Well another problem I've ran into is that getline would just hang and even though the subprocess is supposedly still outputting data getline would only read some of it and then just sit there. Like I said in my question I've run into several odd issues. I'm trying to ask one question at a time and narrow it down. – anderspitman Sep 30 '11 at 12:54
  • @eryksun: The "modified J.F. Sebastian's answer slightly" means that you may have a properly distinct answer to this question? Perhaps you could detail the slight modification? – S.Lott Sep 30 '11 at 15:38
  • @eryksun: I copied J.F. Sebastian's code directly and on Python 2.6 it displays 1 'no output yet' and then does nothing. If I put the try/except block in a loop then it just constantly outputs 'no output yet'. However the file is written correctly. Could you try your solution on Python 2.6 and if it works post your code as an answer? Thanks – anderspitman Sep 30 '11 at 21:08

1 Answers1

6

I only tested this on Windows, but it works in 2.6.6, 2.7.2, and 3.2.1:

from __future__ import print_function
from subprocess import PIPE, Popen
from threading  import Thread
import sys

try:
    from Queue import Queue, Empty
except ImportError:
    from queue import Queue, Empty  # python 3.x

ON_POSIX = 'posix' in sys.builtin_module_names

def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        line = line.decode(sys.stdout.encoding)
        queue.put(line)
    out.close()

def main():
    p = Popen(['c/main.exe'], stdout=PIPE, bufsize=1, close_fds=ON_POSIX)
    q = Queue()
    t = Thread(target=enqueue_output, args=(p.stdout, q))
    t.daemon = True # thread dies with the program
    t.start()

    #initially the queue is empty and stdout is open
    #stdout is closed when enqueue_output finishes
    #then continue printing until the queue is empty 

    while not p.stdout.closed or not q.empty():
        try:
            line = q.get_nowait()
        except Empty:
            continue
        else:
            print(line, end='')
    return 0

if __name__ == '__main__':
    sys.exit(main())

Output:

Outputting: 0
Outputting: 1
Outputting: 2
...
Outputting: 9997
Outputting: 9998
Outputting: 9999

Edit:

readline() will block until the program's stdout buffer flushes, which may take a long time if the data stream is intermittent. If you can edit the source, one option is to manually call fflush(stdout), or you can instead disable buffering using setvbuf at the start of the program. For example:

#include <stdio.h>

int main() {

    setvbuf(stdout, NULL, _IONBF, 0);

    FILE* fh = fopen("output.txt", "w");
    int i;

    for (i = 0; i < 10; i++) {
        printf("Outputting: %d\n", i);
        fprintf(fh, "Outputting: %d\n", i);
        sleep(1);
    }

    fclose(fh);
    return 0;
}

Also look into using unbuffer or stdbuf to modify the output stream of an existing program.

Eryk Sun
  • 33,190
  • 5
  • 92
  • 111
  • Nice I think we're getting close. If I run everything as-is it works. However the actual C program I need to invoke outputs data very slowly (about 1 line per second). If I add sleep(1); to the C loop it doesn't output anything, UNLESS I also add a fflush(stdout); I have a feeling that this will give someone smarter than me a clue as to what's wrong. Also let's assume I don't have access to the source for the C program in order to simply add fflush commands. – anderspitman Sep 30 '11 at 23:10
  • It worked for me when I added out.flush() right after line = line.decode(sys.stdout.encoding). I think one of the fundamental problems I had was that I already had a separate thread so that my GUI wouldn't lock up, but I needed ANOTHER thread within that one to handle stdout. Would there be any way to handle reading stdout in one thread? I still don't really understand what's going on, which almost bugs me more than it not working. But it does work, so thanks for your help. Also, What does close_fds do? The python docs just confused me more. – anderspitman Oct 01 '11 at 00:42
  • Nevermind I was wrong. It's still not working unless I flush directly in the C code. Any ideas? It seems like this should be incredibly simple. The output is being put in the stdout buffer. Why can't I just pull it off the buffer and everyone be happy? Do you think there is a bug in the unbuffered mode? – anderspitman Oct 01 '11 at 01:49
  • 1
    Ok my problem is simple: stdout is buffered, end of story. http://stackoverflow.com/q/874815/943814. It looks like I can either flush it from the subprocess or use something like pexpect. S. Lott you are correct this is definitely a duplicate several times over. My apologies. That said, I don't understand why normal terminals are able to read program output in real time without waiting for stdout to flush or for the program to exit. That might be worth asking if I can't find the answer. – anderspitman Oct 01 '11 at 02:22
  • throw setvbuf up as an answer and I'll take it. – anderspitman Oct 01 '11 at 05:52
  • @erykson: Man you are a freakin genious. stdbuf did the trick! You're probably getting sick of editing that answer but I think using that was the most direct answer to my question: without modifying the C source I am able to monitor the output now which was the original goal. Not the most cross-platform solution but it works for me. Thanks a ton! – anderspitman Oct 03 '11 at 03:59