aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/select.py
blob: f678f84c557d2689d8706646a9b73ad6dc1b8d24 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
"""
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()