From: <pj...@us...> - 2008-11-23 22:22:07
|
Revision: 5621 http://jython.svn.sourceforge.net/jython/?rev=5621&view=rev Author: pjenvey Date: 2008-11-23 22:22:05 +0000 (Sun, 23 Nov 2008) Log Message: ----------- don't buffer the various popen2 helper streams in hopes to make test_cmd_line less flakey Modified Paths: -------------- trunk/jython/Lib/popen2.py Modified: trunk/jython/Lib/popen2.py =================================================================== --- trunk/jython/Lib/popen2.py 2008-11-23 21:56:25 UTC (rev 5620) +++ trunk/jython/Lib/popen2.py 2008-11-23 22:22:05 UTC (rev 5621) @@ -44,7 +44,7 @@ the close method. """ def __init__(self, stream, process, name): - self._file = FileUtil.wrap(stream) + self._file = FileUtil.wrap(stream, 0) self._process = process def __getattr__(self, name): @@ -93,10 +93,10 @@ bufsize ) - self.tochild = FileUtil.wrap(self._tochild) - self.fromchild = FileUtil.wrap(self._fromchild) + self.tochild = FileUtil.wrap(self._tochild, 0) + self.fromchild = FileUtil.wrap(self._fromchild, 0) if self._childerr: - self.childerr = FileUtil.wrap(self._childerr) + self.childerr = FileUtil.wrap(self._childerr, 0) def _startChildWaiter(self): """Start a subthread that waits for the child process to exit.""" @@ -198,7 +198,7 @@ "%s-stderr" % self.process, self._close ) - return FileUtil.wrap(joinedStream) + return FileUtil.wrap(joinedStream, 0) def _close( self ): """Must be closed twice (once for each of the two joined pipes)""" This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <pj...@us...> - 2009-04-03 06:02:46
|
Revision: 6153 http://jython.svn.sourceforge.net/jython/?rev=6153&view=rev Author: pjenvey Date: 2009-04-03 06:02:36 +0000 (Fri, 03 Apr 2009) Log Message: ----------- from: http://svn.python.org/projects/python/branches/release25-maint/Lib/popen2.py@50759 Modified Paths: -------------- trunk/jython/Lib/popen2.py Modified: trunk/jython/Lib/popen2.py =================================================================== --- trunk/jython/Lib/popen2.py 2009-04-03 05:54:58 UTC (rev 6152) +++ trunk/jython/Lib/popen2.py 2009-04-03 06:02:36 UTC (rev 6153) @@ -1,269 +1,213 @@ -""" -popen2.py +"""Spawn a command with pipes to its stdin, stdout, and optionally stderr. -Implement popen2 module functionality for Jython. - -Note that the popen* methods in this module follow the return value -ordering of the Python popen2.popen* methods: - - fromChild, toChild = popen2.popen2(...) - fromChild, toChild, errorFromChild = popen2.popen3(...) - fromChildInclError, toChild = popen2.popen4(...) - -The os.popen* methods are more natural as follows: - - toChild, fromChild = os.popen2(...) - toChild, fromChild, errorFromChild = os.popen3(...) - toChild, fromChildInclError = os.popen4(...) - -The Popen3 and Popen4 classes allow users to poll() or wait() for -child processes to terminate. +The normal os.popen(cmd, mode) call spawns a shell command and provides a +file interface to just the input or output of the process depending on +whether mode is 'r' or 'w'. This module provides the functions popen2(cmd) +and popen3(cmd) which return two or three pipes to the spawned command. """ -import jarray -from java.lang import System -from java.util import Vector -from java.io import BufferedOutputStream -from java.io import BufferedInputStream -from java.io import PipedOutputStream -from java.io import PipedInputStream -from javashell import shellexecute -from org.python.core.util import FileUtil +import os +import sys +__all__ = ["popen2", "popen3", "popen4"] -__all__ = ["popen", "popen2", "popen3", "popen4", "Popen3", "Popen4"] +try: + MAXFD = os.sysconf('SC_OPEN_MAX') +except (AttributeError, ValueError): + MAXFD = 256 _active = [] def _cleanup(): - """For CPython compatibility""" - pass + for inst in _active[:]: + if inst.poll(_deadstate=sys.maxint) >= 0: + try: + _active.remove(inst) + except ValueError: + # This can happen if two threads create a new Popen instance. + # It's harmless that it was already removed, so ignore. + pass -class _ProcessFile: - """Python file object that returns the process exit status from - the close method. - """ - def __init__(self, stream, process, name): - self._file = FileUtil.wrap(stream, 0) - self._process = process - - def __getattr__(self, name): - return getattr(self._file, name) - - def __repr__(self): - return `self._file` - - def close(self): - self._file.close() - return self._process.waitFor() or None - class Popen3: """Class representing a child process. Normally instances are created by the factory functions popen2() and popen3().""" - sts = -1 # Child not completed yet - childWaiter = None - count = 0 - - def __init__(self, cmd, capturestderr=0, bufsize=-1): + sts = -1 # Child not completed yet + + def __init__(self, cmd, capturestderr=False, bufsize=-1): """The parameter 'cmd' is the shell command to execute in a - sub-process. Can be either a sequence of executable - and arguments, or a shell command. - The 'capturestderr' flag, if true, specifies that - the object should capture standard error output of the child process. - The default is false. If the 'bufsize' parameter is specified, it - specifies the size of the I/O buffers to/from the child process. - """ - self.process = shellexecute( cmd ) - self._tochild = self.process.getOutputStream() - self._fromchild = self.process.getInputStream() + sub-process. On UNIX, 'cmd' may be a sequence, in which case arguments + will be passed directly to the program without shell intervention (as + with os.spawnv()). If 'cmd' is a string it will be passed to the shell + (as with os.system()). The 'capturestderr' flag, if true, specifies + that the object should capture standard error output of the child + process. The default is false. If the 'bufsize' parameter is + specified, it specifies the size of the I/O buffers to/from the child + process.""" + _cleanup() + self.cmd = cmd + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() if capturestderr: - self._childerr = self.process.getErrorStream() + errout, errin = os.pipe() + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + if capturestderr: + os.dup2(errin, 2) + self._run_child(cmd) + os.close(p2cread) + self.tochild = os.fdopen(p2cwrite, 'w', bufsize) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) + if capturestderr: + os.close(errin) + self.childerr = os.fdopen(errout, 'r', bufsize) else: - self._childerr = None - import threading - self.childWaiterLock = threading.Lock() + self.childerr = None - if bufsize > 0: - self._tochild = BufferedOutputStream( self._tochild, bufsize ) - self._fromchild = BufferedInputStream( self._fromchild, bufsize ) - if self._childerr: - self._childerr = BufferedInputStream( - self._childerr, - bufsize - ) - - self.tochild = FileUtil.wrap(self._tochild, 0) - self.fromchild = FileUtil.wrap(self._fromchild, 0) - if self._childerr: - self.childerr = FileUtil.wrap(self._childerr, 0) + def __del__(self): + # In case the child hasn't been waited on, check if it's done. + self.poll(_deadstate=sys.maxint) + if self.sts < 0: + if _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) - def _startChildWaiter(self): - """Start a subthread that waits for the child process to exit.""" - self.childWaiterLock.acquire() + def _run_child(self, cmd): + if isinstance(cmd, basestring): + cmd = ['/bin/sh', '-c', cmd] + for i in xrange(3, MAXFD): + try: + os.close(i) + except OSError: + pass try: - if not self.childWaiter: - import threading - self.childWaiter = threading.Thread( - target=self.wait, - name="ChildWaiter %s" % self.process, - args=() - ) - self.childWaiter.setDaemon( 1 ) - self.childWaiter.start() + os.execvp(cmd[0], cmd) finally: - self.childWaiterLock.release() + os._exit(1) - def poll(self): + def poll(self, _deadstate=None): """Return the exit status of the child process if it has finished, or -1 if it hasn't finished yet.""" - if self.sts < 0 and not self.childWaiter: - self._startChildWaiter() - self.childWaiter.join( .1 ) + if self.sts < 0: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + # pid will be 0 if self.pid hasn't terminated + if pid == self.pid: + self.sts = sts + except os.error: + if _deadstate is not None: + self.sts = _deadstate return self.sts def wait(self): """Wait for and return the exit status of the child process.""" - self.sts = self.process.waitFor() - # some processes won't terminate until tochild stream is - # closed, but that's really the responsibility of the caller + if self.sts < 0: + pid, sts = os.waitpid(self.pid, 0) + # This used to be a test, but it is believed to be + # always true, so I changed it to an assertion - mvl + assert pid == self.pid + self.sts = sts return self.sts -def _makeReaderThread( stream, outfunc, bufsize, name=None, postFunc=None ): - """Create a thread that reads the stream, calling outfunc for each block, - and calling postFunc when the end of stream is reached. - """ - Popen3.count += 1 - name = name or str( Popen3.count ) - threadName = "StreamReader %s" % name - import threading - reader = threading.Thread( - target=_readStream, - name=threadName, - args=( stream, outfunc, bufsize, postFunc ) - ) - reader.setDaemon( 1 ) - reader.start() - return reader - -def _readStream( instream, outfunc, bufsize, postFunc=None ): - """Read instream, calling outfunc( buf, 0, count ) with each block. - Copy streams by passing destStream.write as the outfunc. - postFunc is called when the end of instream is reached. - """ - bufsize = bufsize < 1 and 4096 or bufsize - buf = jarray.zeros( bufsize, 'b' ) - total = 0 - while 1: - count = instream.read( buf ) - if -1 == count: - instream.close() - if postFunc: postFunc() - break - else: - total += count - outfunc( buf, 0, count ) - return total - class Popen4(Popen3): - """Popen object that joins the stdout and stderr streams into a single - output stream.""" childerr = None def __init__(self, cmd, bufsize=-1): - Popen3.__init__( self, cmd, 1, bufsize ) - self.closed = Vector() # use a vector for synchronization close() - self.fromchild = self._join( - self._fromchild, - self._childerr, - bufsize - ) + _cleanup() + self.cmd = cmd + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + os.dup2(c2pwrite, 2) + self._run_child(cmd) + os.close(p2cread) + self.tochild = os.fdopen(p2cwrite, 'w', bufsize) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) - def _join( self, stdout, stderr, bufsize ): - """create a stream that joins two output streams""" - self._pipeOut = PipedOutputStream() - joinedStream = PipedInputStream( self._pipeOut ) - self._outReader = _makeReaderThread( - stdout, - self._pipeOut.write, - bufsize, - "%s-stdout" % self.process, - self._close - ) - self._errReader = _makeReaderThread( - stderr, - self._pipeOut.write, - bufsize, - "%s-stderr" % self.process, - self._close - ) - return FileUtil.wrap(joinedStream, 0) - def _close( self ): - """Must be closed twice (once for each of the two joined pipes)""" - self.closed.add( None ) - if self.closed.size() > 1: - self._pipeOut.close() +if sys.platform[:3] == "win" or sys.platform == "os2emx": + # Some things don't make sense on non-Unix platforms. + del Popen3, Popen4 -def popen(path, mode='r', bufsize=-1): - p = Popen3( path, 0, bufsize ) - if mode == 'r': - return _ProcessFile(p.fromchild, p.process, path) - elif mode == 'w': - return _ProcessFile(p.tochild, p.process, path) - else: - raise OSError(0, "Invalid mode", mode) + def popen2(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout, child_stdin) are returned.""" + w, r = os.popen2(cmd, mode, bufsize) + return r, w -def popen2(path, mode="t", bufsize=-1): - p = Popen3(path, 0, bufsize) - return p.fromchild, p.tochild + def popen3(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout, child_stdin, child_stderr) are returned.""" + w, r, e = os.popen3(cmd, mode, bufsize) + return r, w, e -def popen3(path, mode="t", bufsize=-1): - p = Popen3(path, 1, bufsize) - return p.fromchild, p.tochild, p.childerr + def popen4(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout_stderr, child_stdin) are returned.""" + w, r = os.popen4(cmd, mode, bufsize) + return r, w +else: + def popen2(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout, child_stdin) are returned.""" + inst = Popen3(cmd, False, bufsize) + return inst.fromchild, inst.tochild -def popen4(path, mode="t", bufsize=-1): - p = Popen4(path, bufsize) - return p.fromchild, p.tochild + def popen3(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout, child_stdin, child_stderr) are returned.""" + inst = Popen3(cmd, True, bufsize) + return inst.fromchild, inst.tochild, inst.childerr -def system( cmd ): - """Imitate the standard library 'system' call. - Execute 'cmd' in a shell, and send output to stdout & stderr. + def popen4(cmd, bufsize=-1, mode='t'): + """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may + be a sequence, in which case arguments will be passed directly to the + program without shell intervention (as with os.spawnv()). If 'cmd' is a + string it will be passed to the shell (as with os.system()). If + 'bufsize' is specified, it sets the buffer size for the I/O pipes. The + file objects (child_stdout_stderr, child_stdin) are returned.""" + inst = Popen4(cmd, bufsize) + return inst.fromchild, inst.tochild - This is in popen2 only because its Jython implementation is similar to - that of the popen functions. - """ - bufsize = 4096 - # this uses some Popen3 internals, and thus belongs in popen3 - # os.system should also be this function - p = Popen3( cmd, 1, bufsize) - p.tochild.close() - - # read stderr in separate thread - errReader = _makeReaderThread( - p._childerr, - System.err.write, - bufsize, - "stderr" - ) + __all__.extend(["Popen3", "Popen4"]) - # read stdin in main thread - _readStream( - p._fromchild, - System.out.write, - bufsize - ) - - status = p.wait() - return status - def _test(): - # _test comes from python22/lib/popen2.py + # When the test runs, there shouldn't be any open pipes + _cleanup() + assert not _active, "Active pipes when test starts " + repr([c.cmd for c in _active]) cmd = "cat" teststr = "ab cd\n" - import os - if os.name in [ "nt", "java" ]: + if os.name == "nt": cmd = "more" # "more" doesn't act the same way across Windows flavors, # sometimes adding an extra newline at the start or the @@ -275,7 +219,7 @@ w.close() got = r.read() if got.strip() != expected: - raise ValueError("wrote %s read %s" % (teststr, got)) + raise ValueError("wrote %r read %r" % (teststr, got)) print "testing popen3..." try: r, w, e = popen3([cmd]) @@ -284,26 +228,17 @@ w.write(teststr) w.close() got = r.read() - err = e.read() if got.strip() != expected: - raise ValueError("wrote %s read %s, error %s" % (teststr, got, err )) - if err: - raise ValueError("unexected %s on stderr" % err ) -# this portion of the test is inapplicable to the Jython implementation -# for inst in _active[:]: -# inst.wait() -# if _active: -# raise ValueError("_active not empty") + raise ValueError("wrote %r read %r" % (teststr, got)) + got = e.read() + if got: + raise ValueError("unexpected %r on stderr" % (got,)) + for inst in _active[:]: + inst.wait() + _cleanup() + if _active: + raise ValueError("_active not empty") print "All OK" - p = Popen3( cmd ) - q = "This is\na test of\nwriting\n" - p.tochild.write( q ) - p.tochild.close() - r = p.fromchild.read() - x = p.poll() - assert x == 0 - assert r.strip() == q.strip() - if __name__ == '__main__': _test() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <pj...@us...> - 2009-05-23 01:23:40
|
Revision: 6364 http://jython.svn.sourceforge.net/jython/?rev=6364&view=rev Author: pjenvey Date: 2009-05-23 01:23:24 +0000 (Sat, 23 May 2009) Log Message: ----------- handle failed __init__s Modified Paths: -------------- trunk/jython/Lib/popen2.py Modified: trunk/jython/Lib/popen2.py =================================================================== --- trunk/jython/Lib/popen2.py 2009-05-22 02:41:04 UTC (rev 6363) +++ trunk/jython/Lib/popen2.py 2009-05-23 01:23:24 UTC (rev 6364) @@ -47,7 +47,10 @@ self.childerr = self._popen.stderr def __del__(self): - self._popen.__del__() + # XXX: Should let _popen __del__ on its own, but it's a new + # style class: http://bugs.jython.org/issue1057 + if hasattr(self, '_popen'): + self._popen.__del__() def poll(self, _deadstate=None): """Return the exit status of the child process if it has finished, This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <pj...@us...> - 2009-09-09 03:06:38
|
Revision: 6767 http://jython.svn.sourceforge.net/jython/?rev=6767&view=rev Author: pjenvey Date: 2009-09-09 03:06:20 +0000 (Wed, 09 Sep 2009) Log Message: ----------- fix os.popen*/popen2 to only invoke a shell if cmd is a string refs http://bugs.python.org/issue5329 Modified Paths: -------------- trunk/jython/Lib/popen2.py Modified: trunk/jython/Lib/popen2.py =================================================================== --- trunk/jython/Lib/popen2.py 2009-09-09 02:58:36 UTC (rev 6766) +++ trunk/jython/Lib/popen2.py 2009-09-09 03:06:20 UTC (rev 6767) @@ -34,7 +34,8 @@ process.""" stderr = subprocess.PIPE if capturestderr else None PIPE = subprocess.PIPE - self._popen = subprocess.Popen(cmd, bufsize=bufsize, shell=True, + self._popen = subprocess.Popen(cmd, bufsize=bufsize, + shell=isinstance(cmd, basestring), stdin=PIPE, stdout=PIPE, stderr=stderr) self._setup(cmd) @@ -73,7 +74,8 @@ def __init__(self, cmd, bufsize=-1): PIPE = subprocess.PIPE - self._popen = subprocess.Popen(cmd, bufsize=bufsize, shell=True, + self._popen = subprocess.Popen(cmd, bufsize=bufsize, + shell=isinstance(cmd, basestring), stdin=PIPE, stdout=PIPE, stderr=subprocess.STDOUT) self._setup(cmd) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |