Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A non-blocking read on a subprocess.PIPE in Python

I'm using the subprocess module to start a subprocess and connect to its output stream (standard output). I want to be able to execute non-blocking reads on its standard output. Is there a way to make .readline non-blocking or to check if there is data on the stream before I invoke .readline? I'd like this to be portable or at least work under Windows and Linux.

Here is how I do it for now (it's blocking on the .readline if no data is available):

p = subprocess.Popen('myprogram.exe', stdout = subprocess.PIPE) output_str = p.stdout.readline() 
like image 248
Mathieu Pagé Avatar asked Dec 17 '08 17:12

Mathieu Pagé


People also ask

What is subprocess pipe in Python?

Source code: Lib/subprocess.py. The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace several older modules and functions: os.

How do you pass a pipe in subprocess?

To use a pipe with the subprocess module, you have to pass shell=True . In your particular case, however, the simple solution is to call subprocess. check_output(('ps', '-A')) and then str. find on the output.

Does subprocess Popen block?

Popen is nonblocking. call and check_call are blocking. You can make the Popen instance block by calling its wait or communicate method.

What does Popen return?

RETURN VALUE Upon successful completion, popen() shall return a pointer to an open stream that can be used to read or write to the pipe. Otherwise, it shall return a null pointer and may set errno to indicate the error.


2 Answers

fcntl, select, asyncproc won't help in this case.

A reliable way to read a stream without blocking regardless of operating system is to use Queue.get_nowait():

import sys from subprocess import PIPE, Popen from threading  import Thread  try:     from queue import Queue, Empty except ImportError:     from Queue import Queue, Empty  # python 2.x  ON_POSIX = 'posix' in sys.builtin_module_names  def enqueue_output(out, queue):     for line in iter(out.readline, b''):         queue.put(line)     out.close()  p = Popen(['myprogram.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()  # ... do other things here  # read line without blocking try:  line = q.get_nowait() # or q.get(timeout=.1) except Empty:     print('no output yet') else: # got line     # ... do something with line 
like image 79
jfs Avatar answered Sep 27 '22 16:09

jfs


I have often had a similar problem; Python programs I write frequently need to have the ability to execute some primary functionality while simultaneously accepting user input from the command line (stdin). Simply putting the user input handling functionality in another thread doesn't solve the problem because readline() blocks and has no timeout. If the primary functionality is complete and there is no longer any need to wait for further user input I typically want my program to exit, but it can't because readline() is still blocking in the other thread waiting for a line. A solution I have found to this problem is to make stdin a non-blocking file using the fcntl module:

import fcntl import os import sys  # make stdin a non-blocking file fd = sys.stdin.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)  # user input handling thread while mainThreadIsRunning:       try: input = sys.stdin.readline()       except: continue       handleInput(input) 

In my opinion this is a bit cleaner than using the select or signal modules to solve this problem but then again it only works on UNIX...

like image 30
Jesse Avatar answered Sep 27 '22 16:09

Jesse