Make compatible with Python >=2.6.6, bug #330937.
authorZac Medico <zmedico@gentoo.org>
Mon, 21 May 2012 22:01:18 +0000 (15:01 -0700)
committerZac Medico <zmedico@gentoo.org>
Mon, 21 May 2012 22:33:28 +0000 (15:33 -0700)
pym/_emerge/__init__.py
pym/portage/__init__.py

index ec62ac831e092d199f9ac2adeab818ef3198c75a..950bc7a66bc8c2c4aaf79d56f590cc2488596bff 100644 (file)
@@ -3,7 +3,6 @@
 # Distributed under the terms of the GNU General Public License v2
 # $Id: emerge 5976 2007-02-17 09:14:53Z genone $
 
-import array
 from collections import deque
 import fcntl
 import formatter
@@ -1767,6 +1766,38 @@ class AbstractPollTask(AsynchronousTask):
                                self._unregister()
                                self.wait()
 
+       def _read_buf(self, fd, event):
+               """
+               | POLLIN | RETURN
+               | BIT    | VALUE
+               | ---------------------------------------------------
+               | 1      | Read self._bufsize into a string of bytes,
+               |        | handling EAGAIN and EIO. An empty string
+               |        | of bytes indicates EOF.
+               | ---------------------------------------------------
+               | 0      | None
+               """
+               # NOTE: array.fromfile() is no longer used here because it has
+               # bugs in all known versions of Python (including Python 2.7
+               # and Python 3.2).
+               buf = None
+               if event & PollConstants.POLLIN:
+                       try:
+                               buf = os.read(fd, self._bufsize)
+                       except OSError, e:
+                               # EIO happens with pty on Linux after the
+                               # slave end of the pty has been closed.
+                               if e.errno == errno.EIO:
+                                       # EOF: return empty string of bytes
+                                       buf = ''
+                               elif e.errno == errno.EAGAIN:
+                                       # EAGAIN: return None
+                                       buf = None
+                               else:
+                                       raise
+
+               return buf
+
 class PipeReader(AbstractPollTask):
 
        """
@@ -1819,23 +1850,16 @@ class PipeReader(AbstractPollTask):
 
        def _output_handler(self, fd, event):
 
-               if event & PollConstants.POLLIN:
-
-                       for f in self.input_files.itervalues():
-                               if fd == f.fileno():
-                                       break
-
-                       buf = array.array('B')
-                       try:
-                               buf.fromfile(f, self._bufsize)
-                       except EOFError:
-                               pass
-
-                       if buf:
-                               self._read_data.append(buf.tostring())
+               while True:
+                       data = self._read_buf(fd, event)
+                       if data is None:
+                               break
+                       if data:
+                               self._read_data.append(data)
                        else:
                                self._unregister()
                                self.wait()
+                               break
 
                self._unregister_if_appropriate(event)
                return self._registered
@@ -2231,24 +2255,59 @@ class SpawnProcess(SubProcess):
 
        def _output_handler(self, fd, event):
 
-               if event & PollConstants.POLLIN:
+               files = self._files
+               while True:
+                       buf = self._read_buf(fd, event)
 
-                       files = self._files
-                       buf = array.array('B')
-                       try:
-                               buf.fromfile(files.process, self._bufsize)
-                       except EOFError:
-                               pass
+                       if buf is None:
+                               # not a POLLIN event, EAGAIN, etc...
+                               break
 
-                       if buf:
-                               if not self.background:
-                                       buf.tofile(files.stdout)
-                                       files.stdout.flush()
-                               buf.tofile(files.log)
-                               files.log.flush()
-                       else:
+                       if not buf:
+                               # EOF
                                self._unregister()
                                self.wait()
+                               break
+
+                       else:
+                               if not self.background:
+                                       write_successful = False
+                                       failures = 0
+                                       while True:
+                                               try:
+                                                       if not write_successful:
+                                                               files.stdout.write(buf)
+                                                               files.stdout.flush()
+                                                               write_successful = True
+                                                       break
+                                               except OSError, e:
+                                                       if e.errno != errno.EAGAIN:
+                                                               raise
+                                                       del e
+                                                       failures += 1
+                                                       if failures > 50:
+                                                               # Avoid a potentially infinite loop. In
+                                                               # most cases, the failure count is zero
+                                                               # and it's unlikely to exceed 1.
+                                                               raise
+
+                                                       # This means that a subprocess has put an inherited
+                                                       # stdio file descriptor (typically stdin) into
+                                                       # O_NONBLOCK mode. This is not acceptable (see bug
+                                                       # #264435), so revert it. We need to use a loop
+                                                       # here since there's a race condition due to
+                                                       # parallel processes being able to change the
+                                                       # flags on the inherited file descriptor.
+                                                       # TODO: When possible, avoid having child processes
+                                                       # inherit stdio file descriptors from portage
+                                                       # (maybe it can't be avoided with
+                                                       # PROPERTIES=interactive).
+                                                       fcntl.fcntl(files.stdout.fileno(), fcntl.F_SETFL,
+                                                               fcntl.fcntl(files.stdout.fileno(),
+                                                               fcntl.F_GETFL) ^ os.O_NONBLOCK)
+
+                               files.log.write(buf)
+                               files.log.flush()
 
                self._unregister_if_appropriate(event)
                return self._registered
@@ -2260,19 +2319,18 @@ class SpawnProcess(SubProcess):
                monitor the process from inside a poll() loop.
                """
 
-               if event & PollConstants.POLLIN:
+               while True:
+                       buf = self._read_buf(fd, event)
 
-                       buf = array.array('B')
-                       try:
-                               buf.fromfile(self._files.process, self._bufsize)
-                       except EOFError:
-                               pass
+                       if buf is None:
+                               # not a POLLIN event, EAGAIN, etc...
+                               break
 
-                       if buf:
-                               pass
-                       else:
+                       if not buf:
+                               # EOF
                                self._unregister()
                                self.wait()
+                               break
 
                self._unregister_if_appropriate(event)
                return self._registered
index 97ee03ec516a37b5b587b4cb3e8fbab07335f6c1..d86d2e3c7aa0bcd2849dd45f1375c74d816d57c6 100644 (file)
@@ -3131,34 +3131,38 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, fakero
        if logfile:
                log_file = open(logfile, 'a')
                stdout_file = os.fdopen(os.dup(fd_pipes_orig[1]), 'w')
-               master_file = os.fdopen(master_fd, 'r')
-               iwtd = [master_file]
+               iwtd = [master_fd]
                owtd = []
                ewtd = []
-               import array, select
+               import select
                buffsize = 65536
                eof = False
                while not eof:
                        events = select.select(iwtd, owtd, ewtd)
                        for f in events[0]:
-                               # Use non-blocking mode to prevent read
-                               # calls from blocking indefinitely.
-                               buf = array.array('B')
-                               try:
-                                       buf.fromfile(f, buffsize)
-                               except EOFError:
-                                       pass
-                               if not buf:
-                                       eof = True
-                                       break
-                               if f is master_file:
-                                       buf.tofile(stdout_file)
+                               while True:
+                                       try:
+                                               buf = os.read(master_fd, buffsize)
+                                       except OSError, e:
+                                               # EIO happens with pty on Linux after the
+                                               # slave end of the pty has been closed.
+                                               if e.errno == errno.EIO:
+                                                       # EOF
+                                                       eof = True
+                                                       break
+                                               elif e.errno == errno.EAGAIN:
+                                                       break
+                                               else:
+                                                       raise
+
+                                       stdout_file.write(buf)
                                        stdout_file.flush()
-                                       buf.tofile(log_file)
+                                       log_file.write(buf)
                                        log_file.flush()
+
                log_file.close()
                stdout_file.close()
-               master_file.close()
+               os.close(master_fd)
        pid = mypids[-1]
        retval = os.waitpid(pid, 0)[1]
        portage.process.spawned_pids.remove(pid)