pexpect/pexpect — pull request #56 by jquast (2014-05-25 05:35:26+00:00)
Title: Issue #20: Strange EOF/TIMEOUT behavior

Problem
-------

When running a subprocess that writes a lot of data to stdout, then
exits, it is possible to read all data from stdout, then, when calling
waitpid(2), to be told the subprocess has not yet exited.

pexpect would then call read_nonblocking and block for a full 30
seconds, because there remains no more data on stdout.

Solution
--------

Add new parameter, poll_exit to read_nonblocking so that select(2) is
called at shorter intervals than previously done, but still up until
timeout specified, doing a poll for waitpid(2) at regular intervals.
This ensures that no longer than (default, 0.15) seconds elapse, instead
of the default of 30.

Testing
-------

All existing unit tests, of course. However, as a race condition, it is
not possible to reliably reproduce, it may require anywhere from 1 to 5
minutes of looping to cause it to reproduce, so it was not put into base
tests.

A simple program was authored::

TIMEOUT=1
def test_2():
while True:
stime = time.time()
child = pexpect.spawn('ls -lR /dev',
timeout=TIMEOUT)
child.expect(pexpect.EOF)
elapsed = time.time() - stime
print('got eof in {0:0.2f} in pid {1}'
.format(elapsed, child.pid))
assert math.floor(elapsed) < TIMEOUT, elapsed

Without this change, this program will raise an exception after several
dozen runs, hitting the race condition.

With this change, I have been executing this program for several thousand
iterations without failed assertion.

PLEASE CONFER
-------------

@takluyver, with this change, I think we may also delete the
self.__irix_hack and its surrounding hack -- and even the first
self.isalive() check just before this change, for "some systems
such as Solaris" without any penalty in performance.

What do you think?