SNIP IT: Popen3 in a non-heinous wrapper

Noah noah at noah.org
Thu May 2 14:55:56 EDT 2002


This is interesting. I'm working on something similar now. Your open
issue with the child app being required to flush early and often is
likely to remain an issue. I had the same problem when I first tried to
do this with pipes. I learned from the helpful people on this list and
from my own reading that applications  that use the Standard IO library
will likely get hung on a pipe.
	man -s 3 stdio

The Standard IO library has three states for a FILE *. These are:
_IOFBF for block buffered; _IOLBF for line buffered;  and _IONBF for
unbuffered. The STDIO lib will use block buffering when talking to a
block file descriptor such as a pipe. This is usually not helpful for
interactive programs.  The program may have put data in its output that
remains unflushed because the output buffer is not full; then the
program will go and deadlock while waiting for input -- because you
never send it any because you are still waiting for its output 
(still stuck in the STDIO's output buffer).

The "answer" is to use a pseudo-tty. A TTY device will force *line*
buffering (as opposed to block buffering). Line buffering means that
you will get  each line when the child program sends a line feed.  This
corresponds to the way most interactive programs operate --  send a
line of output then wait for a line of input.

I put "answer" in quotes because it's ugly solution and because there
is no POSIX standard for pseudo-TTY devices (even though they have a
TTY standard...). What would make more sense to me would be to have
some way to set a mode on a file descriptor so that it will tell the
STDIO to be line-buffered. I have investigated, and I don't think there
is a way to set the buffered state of a child process. The STDIO
Library does not maintain any external state in the kernel or whatnot,
so I don't think there is any way for you to alter it. I'm not quite
sure how this line-buffered/block-buffered state change happens
internally in the STDIO library. I think the STDIO lib looks at the
file descriptor and decides to change  behavior based on whether it's a
TTY or a block file (see isatty()).

I hope that this qualifies as helpful.

Yours,
Noah

-----Original Message-----
From: python-list-admin at python.org
[mailto:python-list-admin at python.org]On Behalf Of Phlip
Sent: Thursday, May 02, 2002 9:34 AM
To: python-list at python.org
Subject: SNIP IT: Popen3 in a non-heinous wrapper


Not Hyp:

Despite the copious and well-consolidated documentation on the
subject, I have assembled a single, coherent wrapper for Popen3 that
hides the cluster of functions one must call to beat use out of it.
Find it below my sig.

Remember that the app it calls must call "flush" early and often; this
is an open issue.

Reviews & upgrades welcome.

--
  Phlip
               http://www.c2.com/cgi/wiki?PhlIp
  --  Argue for your <limits.h> and
      sure enough, they'r yours        --


import popen2
from fcntl import fcntl, F_SETFL
from select import select


def Piper(command, outputSink, errorSink):
	proc = popen2.Popen3(command, capturestderr = 1)
	O_NONBLOCK = 04000
	fcntl(proc.tochild.fileno(), F_SETFL, O_NONBLOCK)
	fcntl(proc.fromchild.fileno(), F_SETFL, O_NONBLOCK)
	fcntl(proc.childerr.fileno(), F_SETFL, O_NONBLOCK)

	while 1:
		w = []
		selectables = [proc.childerr, proc.fromchild]
		r, w, e = select(selectables, w, selectables)

		for i in r + e:
			if i == proc.childerr:
				err = proc.childerr.read()
				if err:  errorSink(err)

			if i == proc.fromchild:
				all = proc.fromchild.read()
				if all:  outputSink(all)

		got = proc.poll()
		if got != -1:
			break

	return got
			

if __name__ == '__main__':

	def outputSink(output):
		print output

	def errorSink(error):
		 print '***********************', error

	Piper(
		'ls . ; ls non_existent_folder ; ls .',
		outputSink,
		errorSink,
		)






More information about the Python-list mailing list