You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
5.8 KiB
218 lines
5.8 KiB
"""
|
|
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: # noqa (PermissionError doesn't exist in Py2)
|
|
# 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()
|