#!/usr/bin/env python # BASED ON: # # subprocess - Subprocesses with accessible I/O streams # # For more information about this module, see PEP 324. # # This module should remain compatible with Python 2.2, see PEP 291. # # Copyright (c) 2003-2005 by Peter Astrand # # Licensed to PSF under a Contributor Agreement. # See http://www.python.org/2.4/license for licensing details. import sys import os import traceback import gc import errno import fcntl import pickle import time def _execute_child(initialReadSize, verbose): """Execute program (POSIX version)""" pid = None # For transferring possible exec failure from child to parent errpipe_read, errpipe_write = os.pipe() try: try: gc_was_enabled = gc.isenabled() # Disable gc to avoid bug where gc -> file_dealloc -> # write to stderr -> hang. http://bugs.python.org/issue1336 gc.disable() try: pid = os.fork() except: if gc_was_enabled: gc.enable() raise if pid == 0: # Child os.write(errpipe_write, "a" * 512) # This exitcode won't be reported to applications, so it # really doesn't matter what we return. os._exit(255) # Parent if gc_was_enabled: gc.enable() except: print >> sys.stderr, "stderr: child pid: %r; %r" % ( pid, traceback.format_exc()) raise finally: # be sure the FD is closed no matter what os.close(errpipe_write) # Wait for exec to fail or succeed; possibly raising exception # Exception limited to 1M fcntl.fcntl(errpipe_read, fcntl.F_SETFL, fcntl.fcntl(errpipe_read, fcntl.F_GETFL, 0) | os.O_NONBLOCK) if verbose: print >> sys.stderr, "errpipe_read (%r) FCNTL FLAGS: %r" % ( errpipe_read, fcntl.fcntl(errpipe_read, fcntl.F_GETFL, 0)) data = "" rSize = initialReadSize while True: # Read the next chunk of data while True: try: if verbose: print >> sys.stderr, "os.read(%r, %r); child pid: %r" % ( errpipe_read, rSize, pid) newData = os.read(errpipe_read, rSize) except OSError, e: if e.errno in [errno.EINTR, errno.EWOULDBLOCK, errno.EAGAIN]: if verbose: print >> sys.stderr, traceback.format_exc() continue raise else: break if not newData: break rSize -= len(newData) data += newData #break except: #print >> sys.stderr, "stderr: child pid: ", pid, traceback.format_exc() raise finally: # be sure the FD is closed no matter what os.close(errpipe_read) # Wait for child to finish if pid is not None: waitpidResult = os.waitpid(pid, 0) if verbose: print >> sys.stderr, "os.waitpid: child pid= %r returned=%r" % ( pid, waitpidResult) if data != "": if verbose: print >> sys.stderr, "got data; len=%r" % (len(data),) else: raise Exception("No data from child") def test(decreasing): sizeBase = 1024 failedMultipliers = [] multiplierRangeGen = xrange(1, 1025) if decreasing: multiplierRangeGen = reversed(multiplierRangeGen) for multiplier in multiplierRangeGen: multiplierFailed = False for i in range(10): print >> sys.stderr, 'run {0} multiplier {1}'.format(i, multiplier) try: _execute_child(initialReadSize=(sizeBase*multiplier), verbose=False) except OSError as ose: if ose.errno == errno.EINVAL: if not multiplierFailed: multiplierFailed = True failedMultipliers.append(multiplier) print >> sys.stderr, "EINVAL: ", traceback.format_exc() print >> sys.stderr, "Failed initialReadSize multipliers of %r: %r" % ( sizeBase, failedMultipliers,) # We see the EINVAL errors from os.read on Mac OS when iteratating over # initialReadSize in *decreasing* order all the way down to 128KB; Note that # there were no errors encountered below 128KB! test(decreasing=True) # However, we don't encounter any EINVAL errors from os.read when iterating over # the same initialReadSize range in *increasing* order! #test(decreasing=False)