| # Copyright 1999-2020 Gentoo Authors |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import array |
| import errno |
| import os |
| |
| from portage.util.futures import asyncio |
| from _emerge.AsynchronousTask import AsynchronousTask |
| |
| |
| class AbstractPollTask(AsynchronousTask): |
| |
| __slots__ = ("_registered",) |
| |
| _bufsize = 4096 |
| |
| def _read_array(self, f): |
| """ |
| NOTE: array.fromfile() is used here only for testing purposes, |
| because it has bugs in all known versions of Python (including |
| Python 2.7 and Python 3.2). See PipeReaderArrayTestCase. |
| |
| A benchmark that copies bytes from /dev/zero to /dev/null shows |
| that arrays give a 15% performance improvement for Python 2.7.14. |
| However, arrays significantly *decrease* performance for Python 3. |
| """ |
| buf = array.array("B") |
| try: |
| buf.fromfile(f, self._bufsize) |
| except EOFError: |
| pass |
| except TypeError: |
| # Python 3.2: |
| # TypeError: read() didn't return bytes |
| pass |
| except IOError as 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 |
| pass |
| elif e.errno == errno.EAGAIN: |
| # EAGAIN: return None |
| buf = None |
| else: |
| raise |
| |
| if buf is not None: |
| try: |
| # Python >=3.2 |
| buf = buf.tobytes() |
| except AttributeError: |
| buf = buf.tostring() |
| |
| return buf |
| |
| def _read_buf(self, fd): |
| """ |
| Read self._bufsize into a string of bytes, handling EAGAIN and |
| EIO. This will only call os.read() once, so the caller should |
| call this method in a loop until either None or an empty string |
| of bytes is returned. An empty string of bytes indicates EOF. |
| None indicates EAGAIN. |
| |
| NOTE: os.read() will be called regardless of the event flags, |
| since otherwise data may be lost (see bug #531724). |
| |
| @param fd: file descriptor (non-blocking mode required) |
| @type fd: int |
| @rtype: bytes or None |
| @return: A string of bytes, or 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 |
| try: |
| buf = os.read(fd, self._bufsize) |
| except OSError as 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 = b"" |
| elif e.errno == errno.EAGAIN: |
| # EAGAIN: return None |
| buf = None |
| else: |
| raise |
| |
| return buf |
| |
| def _async_wait(self): |
| self._unregister() |
| super(AbstractPollTask, self)._async_wait() |
| |
| def _unregister(self): |
| self._registered = False |
| |
| def _wait_loop(self, timeout=None): |
| loop = self.scheduler |
| tasks = [self.async_wait()] |
| if timeout is not None: |
| tasks.append( |
| asyncio.ensure_future(asyncio.sleep(timeout, loop=loop), loop=loop) |
| ) |
| try: |
| loop.run_until_complete( |
| asyncio.ensure_future( |
| asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED, loop=loop), |
| loop=loop, |
| ) |
| ) |
| finally: |
| for task in tasks: |
| task.cancel() |