blob: 3e6c2adcb58a29fc69c5f0b1cba13bda6a50d849 [file] [log] [blame]
Charles-François Natali243d8d82013-09-04 19:02:49 +02001"""Selectors module.
2
3This module allows high-level and efficient I/O multiplexing, built upon the
4`select` module primitives.
5"""
6
7
8from abc import ABCMeta, abstractmethod
Charles-François Natali4574b492013-10-30 20:31:04 +01009from collections import namedtuple, Mapping
Charles-François Natali243d8d82013-09-04 19:02:49 +020010import functools
11import select
12import sys
13
14
15# generic events, that must be mapped to implementation-specific ones
16EVENT_READ = (1 << 0)
17EVENT_WRITE = (1 << 1)
18
19
20def _fileobj_to_fd(fileobj):
21 """Return a file descriptor from a file object.
22
23 Parameters:
24 fileobj -- file object or file descriptor
25
26 Returns:
27 corresponding file descriptor
28 """
29 if isinstance(fileobj, int):
30 fd = fileobj
31 else:
32 try:
33 fd = int(fileobj.fileno())
34 except (AttributeError, TypeError, ValueError):
35 raise ValueError("Invalid file object: "
36 "{!r}".format(fileobj)) from None
37 if fd < 0:
38 raise ValueError("Invalid file descriptor: {}".format(fd))
39 return fd
40
41
42SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data'])
43"""Object used to associate a file object to its backing file descriptor,
44selected event mask and attached data."""
45
46
Charles-François Natali4574b492013-10-30 20:31:04 +010047class _SelectorMapping(Mapping):
48 """Mapping of file objects to selector keys."""
49
50 def __init__(self, selector):
51 self._selector = selector
52
53 def __len__(self):
54 return len(self._selector._fd_to_key)
55
56 def __getitem__(self, fileobj):
57 try:
58 return self._selector._fd_to_key[_fileobj_to_fd(fileobj)]
59 except KeyError:
60 raise KeyError("{!r} is not registered".format(fileobj)) from None
61
62 def __iter__(self):
63 return iter(self._selector._fd_to_key)
64
65
Charles-François Natali243d8d82013-09-04 19:02:49 +020066class BaseSelector(metaclass=ABCMeta):
67 """Base selector class.
68
69 A selector supports registering file objects to be monitored for specific
70 I/O events.
71
72 A file object is a file descriptor or any object with a `fileno()` method.
73 An arbitrary object can be attached to the file object, which can be used
74 for example to store context information, a callback, etc.
75
76 A selector can use various implementations (select(), poll(), epoll()...)
77 depending on the platform. The default `Selector` class uses the most
78 performant implementation on the current platform.
79 """
80
81 def __init__(self):
82 # this maps file descriptors to keys
83 self._fd_to_key = {}
Charles-François Natali4574b492013-10-30 20:31:04 +010084 # read-only mapping returned by get_map()
85 self._map = _SelectorMapping(self)
Charles-François Natali243d8d82013-09-04 19:02:49 +020086
87 def register(self, fileobj, events, data=None):
88 """Register a file object.
89
90 Parameters:
91 fileobj -- file object or file descriptor
92 events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)
93 data -- attached data
94
95 Returns:
96 SelectorKey instance
97 """
98 if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
99 raise ValueError("Invalid events: {!r}".format(events))
100
101 key = SelectorKey(fileobj, _fileobj_to_fd(fileobj), events, data)
102
103 if key.fd in self._fd_to_key:
104 raise KeyError("{!r} (FD {}) is already "
105 "registered".format(fileobj, key.fd))
106
107 self._fd_to_key[key.fd] = key
108 return key
109
110 def unregister(self, fileobj):
111 """Unregister a file object.
112
113 Parameters:
114 fileobj -- file object or file descriptor
115
116 Returns:
117 SelectorKey instance
118 """
119 try:
120 key = self._fd_to_key.pop(_fileobj_to_fd(fileobj))
121 except KeyError:
122 raise KeyError("{!r} is not registered".format(fileobj)) from None
123 return key
124
125 def modify(self, fileobj, events, data=None):
126 """Change a registered file object monitored events or attached data.
127
128 Parameters:
129 fileobj -- file object or file descriptor
130 events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)
131 data -- attached data
132
133 Returns:
134 SelectorKey instance
135 """
136 # TODO: Subclasses can probably optimize this even further.
137 try:
138 key = self._fd_to_key[_fileobj_to_fd(fileobj)]
139 except KeyError:
140 raise KeyError("{!r} is not registered".format(fileobj)) from None
141 if events != key.events or data != key.data:
142 # TODO: If only the data changed, use a shortcut that only
143 # updates the data.
144 self.unregister(fileobj)
145 return self.register(fileobj, events, data)
146 else:
147 return key
148
149 @abstractmethod
150 def select(self, timeout=None):
151 """Perform the actual selection, until some monitored file objects are
152 ready or a timeout expires.
153
154 Parameters:
155 timeout -- if timeout > 0, this specifies the maximum wait time, in
156 seconds
157 if timeout <= 0, the select() call won't block, and will
158 report the currently ready file objects
159 if timeout is None, select() will block until a monitored
160 file object becomes ready
161
162 Returns:
163 list of (key, events) for ready file objects
164 `events` is a bitwise mask of EVENT_READ|EVENT_WRITE
165 """
166 raise NotImplementedError()
167
168 def close(self):
169 """Close the selector.
170
171 This must be called to make sure that any underlying resource is freed.
172 """
173 self._fd_to_key.clear()
174
175 def get_key(self, fileobj):
176 """Return the key associated to a registered file object.
177
178 Returns:
179 SelectorKey for this file object
180 """
181 try:
182 return self._fd_to_key[_fileobj_to_fd(fileobj)]
183 except KeyError:
184 raise KeyError("{!r} is not registered".format(fileobj)) from None
185
Charles-François Natali4574b492013-10-30 20:31:04 +0100186 def get_map(self):
187 """Return a mapping of file objects to selector keys."""
188 return self._map
189
Charles-François Natali243d8d82013-09-04 19:02:49 +0200190 def __enter__(self):
191 return self
192
193 def __exit__(self, *args):
194 self.close()
195
196 def _key_from_fd(self, fd):
197 """Return the key associated to a given file descriptor.
198
199 Parameters:
200 fd -- file descriptor
201
202 Returns:
203 corresponding key, or None if not found
204 """
205 try:
206 return self._fd_to_key[fd]
207 except KeyError:
208 return None
209
210
211class SelectSelector(BaseSelector):
212 """Select-based selector."""
213
214 def __init__(self):
215 super().__init__()
216 self._readers = set()
217 self._writers = set()
218
219 def register(self, fileobj, events, data=None):
220 key = super().register(fileobj, events, data)
221 if events & EVENT_READ:
222 self._readers.add(key.fd)
223 if events & EVENT_WRITE:
224 self._writers.add(key.fd)
225 return key
226
227 def unregister(self, fileobj):
228 key = super().unregister(fileobj)
229 self._readers.discard(key.fd)
230 self._writers.discard(key.fd)
231 return key
232
233 if sys.platform == 'win32':
234 def _select(self, r, w, _, timeout=None):
235 r, w, x = select.select(r, w, w, timeout)
236 return r, w + x, []
237 else:
238 _select = select.select
239
240 def select(self, timeout=None):
241 timeout = None if timeout is None else max(timeout, 0)
242 ready = []
243 try:
244 r, w, _ = self._select(self._readers, self._writers, [], timeout)
245 except InterruptedError:
246 return ready
247 r = set(r)
248 w = set(w)
249 for fd in r | w:
250 events = 0
251 if fd in r:
252 events |= EVENT_READ
253 if fd in w:
254 events |= EVENT_WRITE
255
256 key = self._key_from_fd(fd)
257 if key:
258 ready.append((key, events & key.events))
259 return ready
260
261
262if hasattr(select, 'poll'):
263
264 class PollSelector(BaseSelector):
265 """Poll-based selector."""
266
267 def __init__(self):
268 super().__init__()
269 self._poll = select.poll()
270
271 def register(self, fileobj, events, data=None):
272 key = super().register(fileobj, events, data)
273 poll_events = 0
274 if events & EVENT_READ:
275 poll_events |= select.POLLIN
276 if events & EVENT_WRITE:
277 poll_events |= select.POLLOUT
278 self._poll.register(key.fd, poll_events)
279 return key
280
281 def unregister(self, fileobj):
282 key = super().unregister(fileobj)
283 self._poll.unregister(key.fd)
284 return key
285
286 def select(self, timeout=None):
287 timeout = None if timeout is None else max(int(1000 * timeout), 0)
288 ready = []
289 try:
290 fd_event_list = self._poll.poll(timeout)
291 except InterruptedError:
292 return ready
293 for fd, event in fd_event_list:
294 events = 0
295 if event & ~select.POLLIN:
296 events |= EVENT_WRITE
297 if event & ~select.POLLOUT:
298 events |= EVENT_READ
299
300 key = self._key_from_fd(fd)
301 if key:
302 ready.append((key, events & key.events))
303 return ready
304
305
306if hasattr(select, 'epoll'):
307
308 class EpollSelector(BaseSelector):
309 """Epoll-based selector."""
310
311 def __init__(self):
312 super().__init__()
313 self._epoll = select.epoll()
314
315 def fileno(self):
316 return self._epoll.fileno()
317
318 def register(self, fileobj, events, data=None):
319 key = super().register(fileobj, events, data)
320 epoll_events = 0
321 if events & EVENT_READ:
322 epoll_events |= select.EPOLLIN
323 if events & EVENT_WRITE:
324 epoll_events |= select.EPOLLOUT
325 self._epoll.register(key.fd, epoll_events)
326 return key
327
328 def unregister(self, fileobj):
329 key = super().unregister(fileobj)
330 self._epoll.unregister(key.fd)
331 return key
332
333 def select(self, timeout=None):
334 timeout = -1 if timeout is None else max(timeout, 0)
335 max_ev = len(self._fd_to_key)
336 ready = []
337 try:
338 fd_event_list = self._epoll.poll(timeout, max_ev)
339 except InterruptedError:
340 return ready
341 for fd, event in fd_event_list:
342 events = 0
343 if event & ~select.EPOLLIN:
344 events |= EVENT_WRITE
345 if event & ~select.EPOLLOUT:
346 events |= EVENT_READ
347
348 key = self._key_from_fd(fd)
349 if key:
350 ready.append((key, events & key.events))
351 return ready
352
353 def close(self):
354 super().close()
355 self._epoll.close()
356
357
358if hasattr(select, 'kqueue'):
359
360 class KqueueSelector(BaseSelector):
361 """Kqueue-based selector."""
362
363 def __init__(self):
364 super().__init__()
365 self._kqueue = select.kqueue()
366
367 def fileno(self):
368 return self._kqueue.fileno()
369
370 def register(self, fileobj, events, data=None):
371 key = super().register(fileobj, events, data)
372 if events & EVENT_READ:
373 kev = select.kevent(key.fd, select.KQ_FILTER_READ,
374 select.KQ_EV_ADD)
375 self._kqueue.control([kev], 0, 0)
376 if events & EVENT_WRITE:
377 kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
378 select.KQ_EV_ADD)
379 self._kqueue.control([kev], 0, 0)
380 return key
381
382 def unregister(self, fileobj):
383 key = super().unregister(fileobj)
384 if key.events & EVENT_READ:
385 kev = select.kevent(key.fd, select.KQ_FILTER_READ,
386 select.KQ_EV_DELETE)
387 self._kqueue.control([kev], 0, 0)
388 if key.events & EVENT_WRITE:
389 kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
390 select.KQ_EV_DELETE)
391 self._kqueue.control([kev], 0, 0)
392 return key
393
394 def select(self, timeout=None):
395 timeout = None if timeout is None else max(timeout, 0)
396 max_ev = len(self._fd_to_key)
397 ready = []
398 try:
399 kev_list = self._kqueue.control(None, max_ev, timeout)
400 except InterruptedError:
401 return ready
402 for kev in kev_list:
403 fd = kev.ident
404 flag = kev.filter
405 events = 0
406 if flag == select.KQ_FILTER_READ:
407 events |= EVENT_READ
408 if flag == select.KQ_FILTER_WRITE:
409 events |= EVENT_WRITE
410
411 key = self._key_from_fd(fd)
412 if key:
413 ready.append((key, events & key.events))
414 return ready
415
416 def close(self):
417 super().close()
418 self._kqueue.close()
419
420
421# Choose the best implementation: roughly, epoll|kqueue > poll > select.
422# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
423if 'KqueueSelector' in globals():
424 DefaultSelector = KqueueSelector
425elif 'EpollSelector' in globals():
426 DefaultSelector = EpollSelector
427elif 'PollSelector' in globals():
428 DefaultSelector = PollSelector
429else:
430 DefaultSelector = SelectSelector