123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- """
- Selectors for the Posix event loop.
- """
- from __future__ import unicode_literals, absolute_import
- import sys
- import abc
- import errno
- import select
- import six
- __all__ = (
- 'AutoSelector',
- 'PollSelector',
- 'SelectSelector',
- 'Selector',
- 'fd_to_int',
- )
- def fd_to_int(fd):
- assert isinstance(fd, int) or hasattr(fd, 'fileno')
- if isinstance(fd, int):
- return fd
- else:
- return fd.fileno()
- class Selector(six.with_metaclass(abc.ABCMeta, object)):
- @abc.abstractmethod
- def register(self, fd):
- assert isinstance(fd, int)
- @abc.abstractmethod
- def unregister(self, fd):
- assert isinstance(fd, int)
- @abc.abstractmethod
- def select(self, timeout):
- pass
- @abc.abstractmethod
- def close(self):
- pass
- class AutoSelector(Selector):
- def __init__(self):
- self._fds = []
- self._select_selector = SelectSelector()
- self._selectors = [self._select_selector]
- # When 'select.poll' exists, create a PollSelector.
- if hasattr(select, 'poll'):
- self._poll_selector = PollSelector()
- self._selectors.append(self._poll_selector)
- else:
- self._poll_selector = None
- # Use of the 'select' module, that was introduced in Python3.4. We don't
- # use it before 3.5 however, because this is the point where this module
- # retries interrupted system calls.
- if sys.version_info >= (3, 5):
- self._py3_selector = Python3Selector()
- self._selectors.append(self._py3_selector)
- else:
- self._py3_selector = None
- def register(self, fd):
- assert isinstance(fd, int)
- self._fds.append(fd)
- for sel in self._selectors:
- sel.register(fd)
- def unregister(self, fd):
- assert isinstance(fd, int)
- self._fds.remove(fd)
- for sel in self._selectors:
- sel.unregister(fd)
- def select(self, timeout):
- # Try Python 3 selector first.
- if self._py3_selector:
- try:
- return self._py3_selector.select(timeout)
- except PermissionError:
- # We had a situation (in pypager) where epoll raised a
- # PermissionError when a local file descriptor was registered,
- # however poll and select worked fine. So, in that case, just
- # try using select below.
- pass
- try:
- # Prefer 'select.select', if we don't have much file descriptors.
- # This is more universal.
- return self._select_selector.select(timeout)
- except ValueError:
- # When we have more than 1024 open file descriptors, we'll always
- # get a "ValueError: filedescriptor out of range in select()" for
- # 'select'. In this case, try, using 'poll' instead.
- if self._poll_selector is not None:
- return self._poll_selector.select(timeout)
- else:
- raise
- def close(self):
- for sel in self._selectors:
- sel.close()
- class Python3Selector(Selector):
- """
- Use of the Python3 'selectors' module.
- NOTE: Only use on Python 3.5 or newer!
- """
- def __init__(self):
- assert sys.version_info >= (3, 5)
- import selectors # Inline import: Python3 only!
- self._sel = selectors.DefaultSelector()
- def register(self, fd):
- assert isinstance(fd, int)
- import selectors # Inline import: Python3 only!
- self._sel.register(fd, selectors.EVENT_READ, None)
- def unregister(self, fd):
- assert isinstance(fd, int)
- self._sel.unregister(fd)
- def select(self, timeout):
- events = self._sel.select(timeout=timeout)
- return [key.fileobj for key, mask in events]
- def close(self):
- self._sel.close()
- class PollSelector(Selector):
- def __init__(self):
- self._poll = select.poll()
- def register(self, fd):
- assert isinstance(fd, int)
- self._poll.register(fd, select.POLLIN)
- def unregister(self, fd):
- assert isinstance(fd, int)
- def select(self, timeout):
- tuples = self._poll.poll(timeout) # Returns (fd, event) tuples.
- return [t[0] for t in tuples]
- def close(self):
- pass # XXX
- class SelectSelector(Selector):
- """
- Wrapper around select.select.
- When the SIGWINCH signal is handled, other system calls, like select
- are aborted in Python. This wrapper will retry the system call.
- """
- def __init__(self):
- self._fds = []
- def register(self, fd):
- self._fds.append(fd)
- def unregister(self, fd):
- self._fds.remove(fd)
- def select(self, timeout):
- while True:
- try:
- return select.select(self._fds, [], [], timeout)[0]
- except select.error as e:
- # Retry select call when EINTR
- if e.args and e.args[0] == errno.EINTR:
- continue
- else:
- raise
- def close(self):
- pass
- def select_fds(read_fds, timeout, selector=AutoSelector):
- """
- Wait for a list of file descriptors (`read_fds`) to become ready for
- reading. This chooses the most appropriate select-tool for use in
- prompt-toolkit.
- """
- # Map to ensure that we return the objects that were passed in originally.
- # Whether they are a fd integer or an object that has a fileno().
- # (The 'poll' implementation for instance, returns always integers.)
- fd_map = dict((fd_to_int(fd), fd) for fd in read_fds)
- # Wait, using selector.
- sel = selector()
- try:
- for fd in read_fds:
- sel.register(fd)
- result = sel.select(timeout)
- if result is not None:
- return [fd_map[fd_to_int(fd)] for fd in result]
- finally:
- sel.close()
|