blob: c533f13d69578e9dbf7ad1e9888c6e2880fc71be [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):
Charles-François Natalib3330a0a2013-12-01 11:04:17 +010067 """Selector abstract base class.
Charles-François Natali243d8d82013-09-04 19:02:49 +020068
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
Charles-François Natalib3330a0a2013-12-01 11:04:17 +010081 @abstractmethod
Charles-François Natali243d8d82013-09-04 19:02:49 +020082 def register(self, fileobj, events, data=None):
83 """Register a file object.
84
85 Parameters:
86 fileobj -- file object or file descriptor
87 events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)
88 data -- attached data
89
90 Returns:
91 SelectorKey instance
92 """
Charles-François Natalib3330a0a2013-12-01 11:04:17 +010093 raise NotImplementedError
Charles-François Natali243d8d82013-09-04 19:02:49 +020094
Charles-François Natalib3330a0a2013-12-01 11:04:17 +010095 @abstractmethod
Charles-François Natali243d8d82013-09-04 19:02:49 +020096 def unregister(self, fileobj):
97 """Unregister a file object.
98
99 Parameters:
100 fileobj -- file object or file descriptor
101
102 Returns:
103 SelectorKey instance
104 """
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100105 raise NotImplementedError
Charles-François Natali243d8d82013-09-04 19:02:49 +0200106
107 def modify(self, fileobj, events, data=None):
108 """Change a registered file object monitored events or attached data.
109
110 Parameters:
111 fileobj -- file object or file descriptor
112 events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)
113 data -- attached data
114
115 Returns:
116 SelectorKey instance
117 """
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100118 self.unregister(fileobj)
119 return self.register(fileobj, events, data)
Charles-François Natali243d8d82013-09-04 19:02:49 +0200120
121 @abstractmethod
122 def select(self, timeout=None):
123 """Perform the actual selection, until some monitored file objects are
124 ready or a timeout expires.
125
126 Parameters:
127 timeout -- if timeout > 0, this specifies the maximum wait time, in
128 seconds
129 if timeout <= 0, the select() call won't block, and will
130 report the currently ready file objects
131 if timeout is None, select() will block until a monitored
132 file object becomes ready
133
134 Returns:
135 list of (key, events) for ready file objects
136 `events` is a bitwise mask of EVENT_READ|EVENT_WRITE
137 """
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100138 raise NotImplementedError
Charles-François Natali243d8d82013-09-04 19:02:49 +0200139
140 def close(self):
141 """Close the selector.
142
143 This must be called to make sure that any underlying resource is freed.
144 """
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100145 pass
Charles-François Natali243d8d82013-09-04 19:02:49 +0200146
147 def get_key(self, fileobj):
148 """Return the key associated to a registered file object.
149
150 Returns:
151 SelectorKey for this file object
152 """
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100153 mapping = self.get_map()
Charles-François Natali243d8d82013-09-04 19:02:49 +0200154 try:
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100155 return mapping[fileobj]
Charles-François Natali243d8d82013-09-04 19:02:49 +0200156 except KeyError:
157 raise KeyError("{!r} is not registered".format(fileobj)) from None
158
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100159 @abstractmethod
Charles-François Natali4574b492013-10-30 20:31:04 +0100160 def get_map(self):
161 """Return a mapping of file objects to selector keys."""
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100162 raise NotImplementedError
Charles-François Natali4574b492013-10-30 20:31:04 +0100163
Charles-François Natali243d8d82013-09-04 19:02:49 +0200164 def __enter__(self):
165 return self
166
167 def __exit__(self, *args):
168 self.close()
169
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100170
171class _BaseSelectorImpl(BaseSelector):
172 """Base selector implementation."""
173
174 def __init__(self):
175 # this maps file descriptors to keys
176 self._fd_to_key = {}
177 # read-only mapping returned by get_map()
178 self._map = _SelectorMapping(self)
179
180 def register(self, fileobj, events, data=None):
181 if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
182 raise ValueError("Invalid events: {!r}".format(events))
183
184 key = SelectorKey(fileobj, _fileobj_to_fd(fileobj), events, data)
185
186 if key.fd in self._fd_to_key:
187 raise KeyError("{!r} (FD {}) is already "
188 "registered".format(fileobj, key.fd))
189
190 self._fd_to_key[key.fd] = key
191 return key
192
193 def unregister(self, fileobj):
194 try:
195 key = self._fd_to_key.pop(_fileobj_to_fd(fileobj))
196 except KeyError:
197 raise KeyError("{!r} is not registered".format(fileobj)) from None
198 return key
199
200 def modify(self, fileobj, events, data=None):
201 # TODO: Subclasses can probably optimize this even further.
202 try:
203 key = self._fd_to_key[_fileobj_to_fd(fileobj)]
204 except KeyError:
205 raise KeyError("{!r} is not registered".format(fileobj)) from None
206 if events != key.events:
207 self.unregister(fileobj)
208 key = self.register(fileobj, events, data)
209 elif data != key.data:
210 # Use a shortcut to update the data.
211 key = key._replace(data=data)
212 self._fd_to_key[key.fd] = key
213 return key
214
215 def close(self):
216 self._fd_to_key.clear()
217
218 def get_map(self):
219 return self._map
220
Charles-François Natali243d8d82013-09-04 19:02:49 +0200221 def _key_from_fd(self, fd):
222 """Return the key associated to a given file descriptor.
223
224 Parameters:
225 fd -- file descriptor
226
227 Returns:
228 corresponding key, or None if not found
229 """
230 try:
231 return self._fd_to_key[fd]
232 except KeyError:
233 return None
234
235
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100236class SelectSelector(_BaseSelectorImpl):
Charles-François Natali243d8d82013-09-04 19:02:49 +0200237 """Select-based selector."""
238
239 def __init__(self):
240 super().__init__()
241 self._readers = set()
242 self._writers = set()
243
244 def register(self, fileobj, events, data=None):
245 key = super().register(fileobj, events, data)
246 if events & EVENT_READ:
247 self._readers.add(key.fd)
248 if events & EVENT_WRITE:
249 self._writers.add(key.fd)
250 return key
251
252 def unregister(self, fileobj):
253 key = super().unregister(fileobj)
254 self._readers.discard(key.fd)
255 self._writers.discard(key.fd)
256 return key
257
258 if sys.platform == 'win32':
259 def _select(self, r, w, _, timeout=None):
260 r, w, x = select.select(r, w, w, timeout)
261 return r, w + x, []
262 else:
263 _select = select.select
264
265 def select(self, timeout=None):
266 timeout = None if timeout is None else max(timeout, 0)
267 ready = []
268 try:
269 r, w, _ = self._select(self._readers, self._writers, [], timeout)
270 except InterruptedError:
271 return ready
272 r = set(r)
273 w = set(w)
274 for fd in r | w:
275 events = 0
276 if fd in r:
277 events |= EVENT_READ
278 if fd in w:
279 events |= EVENT_WRITE
280
281 key = self._key_from_fd(fd)
282 if key:
283 ready.append((key, events & key.events))
284 return ready
285
286
287if hasattr(select, 'poll'):
288
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100289 class PollSelector(_BaseSelectorImpl):
Charles-François Natali243d8d82013-09-04 19:02:49 +0200290 """Poll-based selector."""
291
292 def __init__(self):
293 super().__init__()
294 self._poll = select.poll()
295
296 def register(self, fileobj, events, data=None):
297 key = super().register(fileobj, events, data)
298 poll_events = 0
299 if events & EVENT_READ:
300 poll_events |= select.POLLIN
301 if events & EVENT_WRITE:
302 poll_events |= select.POLLOUT
303 self._poll.register(key.fd, poll_events)
304 return key
305
306 def unregister(self, fileobj):
307 key = super().unregister(fileobj)
308 self._poll.unregister(key.fd)
309 return key
310
311 def select(self, timeout=None):
312 timeout = None if timeout is None else max(int(1000 * timeout), 0)
313 ready = []
314 try:
315 fd_event_list = self._poll.poll(timeout)
316 except InterruptedError:
317 return ready
318 for fd, event in fd_event_list:
319 events = 0
320 if event & ~select.POLLIN:
321 events |= EVENT_WRITE
322 if event & ~select.POLLOUT:
323 events |= EVENT_READ
324
325 key = self._key_from_fd(fd)
326 if key:
327 ready.append((key, events & key.events))
328 return ready
329
330
331if hasattr(select, 'epoll'):
332
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100333 class EpollSelector(_BaseSelectorImpl):
Charles-François Natali243d8d82013-09-04 19:02:49 +0200334 """Epoll-based selector."""
335
336 def __init__(self):
337 super().__init__()
338 self._epoll = select.epoll()
339
340 def fileno(self):
341 return self._epoll.fileno()
342
343 def register(self, fileobj, events, data=None):
344 key = super().register(fileobj, events, data)
345 epoll_events = 0
346 if events & EVENT_READ:
347 epoll_events |= select.EPOLLIN
348 if events & EVENT_WRITE:
349 epoll_events |= select.EPOLLOUT
350 self._epoll.register(key.fd, epoll_events)
351 return key
352
353 def unregister(self, fileobj):
354 key = super().unregister(fileobj)
355 self._epoll.unregister(key.fd)
356 return key
357
358 def select(self, timeout=None):
359 timeout = -1 if timeout is None else max(timeout, 0)
360 max_ev = len(self._fd_to_key)
361 ready = []
362 try:
363 fd_event_list = self._epoll.poll(timeout, max_ev)
364 except InterruptedError:
365 return ready
366 for fd, event in fd_event_list:
367 events = 0
368 if event & ~select.EPOLLIN:
369 events |= EVENT_WRITE
370 if event & ~select.EPOLLOUT:
371 events |= EVENT_READ
372
373 key = self._key_from_fd(fd)
374 if key:
375 ready.append((key, events & key.events))
376 return ready
377
378 def close(self):
Charles-François Natali243d8d82013-09-04 19:02:49 +0200379 self._epoll.close()
Guido van Rossum61a2ced2013-10-31 11:01:40 -0700380 super().close()
Charles-François Natali243d8d82013-09-04 19:02:49 +0200381
382
383if hasattr(select, 'kqueue'):
384
Charles-François Natalib3330a0a2013-12-01 11:04:17 +0100385 class KqueueSelector(_BaseSelectorImpl):
Charles-François Natali243d8d82013-09-04 19:02:49 +0200386 """Kqueue-based selector."""
387
388 def __init__(self):
389 super().__init__()
390 self._kqueue = select.kqueue()
391
392 def fileno(self):
393 return self._kqueue.fileno()
394
395 def register(self, fileobj, events, data=None):
396 key = super().register(fileobj, events, data)
397 if events & EVENT_READ:
398 kev = select.kevent(key.fd, select.KQ_FILTER_READ,
399 select.KQ_EV_ADD)
400 self._kqueue.control([kev], 0, 0)
401 if events & EVENT_WRITE:
402 kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
403 select.KQ_EV_ADD)
404 self._kqueue.control([kev], 0, 0)
405 return key
406
407 def unregister(self, fileobj):
408 key = super().unregister(fileobj)
409 if key.events & EVENT_READ:
410 kev = select.kevent(key.fd, select.KQ_FILTER_READ,
411 select.KQ_EV_DELETE)
412 self._kqueue.control([kev], 0, 0)
413 if key.events & EVENT_WRITE:
414 kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
415 select.KQ_EV_DELETE)
416 self._kqueue.control([kev], 0, 0)
417 return key
418
419 def select(self, timeout=None):
420 timeout = None if timeout is None else max(timeout, 0)
421 max_ev = len(self._fd_to_key)
422 ready = []
423 try:
424 kev_list = self._kqueue.control(None, max_ev, timeout)
425 except InterruptedError:
426 return ready
427 for kev in kev_list:
428 fd = kev.ident
429 flag = kev.filter
430 events = 0
431 if flag == select.KQ_FILTER_READ:
432 events |= EVENT_READ
433 if flag == select.KQ_FILTER_WRITE:
434 events |= EVENT_WRITE
435
436 key = self._key_from_fd(fd)
437 if key:
438 ready.append((key, events & key.events))
439 return ready
440
441 def close(self):
Charles-François Natali243d8d82013-09-04 19:02:49 +0200442 self._kqueue.close()
Guido van Rossum61a2ced2013-10-31 11:01:40 -0700443 super().close()
Charles-François Natali243d8d82013-09-04 19:02:49 +0200444
445
446# Choose the best implementation: roughly, epoll|kqueue > poll > select.
447# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
448if 'KqueueSelector' in globals():
449 DefaultSelector = KqueueSelector
450elif 'EpollSelector' in globals():
451 DefaultSelector = EpollSelector
452elif 'PollSelector' in globals():
453 DefaultSelector = PollSelector
454else:
455 DefaultSelector = SelectSelector