blob: 3971502ef28aab7aabd8d240d5893984d692aa5c [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
Guido van Rossumd0786a12013-11-07 08:39:28 -0800141 if events != key.events:
Charles-François Natali243d8d82013-09-04 19:02:49 +0200142 self.unregister(fileobj)
143 return self.register(fileobj, events, data)
Guido van Rossumd0786a12013-11-07 08:39:28 -0800144 elif data != key.data:
145 # Use a shortcut to update the data.
146 key = key._replace(data=data)
147 self._fd_to_key[key.fd] = key
148 return key
Charles-François Natali243d8d82013-09-04 19:02:49 +0200149 else:
150 return key
151
152 @abstractmethod
153 def select(self, timeout=None):
154 """Perform the actual selection, until some monitored file objects are
155 ready or a timeout expires.
156
157 Parameters:
158 timeout -- if timeout > 0, this specifies the maximum wait time, in
159 seconds
160 if timeout <= 0, the select() call won't block, and will
161 report the currently ready file objects
162 if timeout is None, select() will block until a monitored
163 file object becomes ready
164
165 Returns:
166 list of (key, events) for ready file objects
167 `events` is a bitwise mask of EVENT_READ|EVENT_WRITE
168 """
169 raise NotImplementedError()
170
171 def close(self):
172 """Close the selector.
173
174 This must be called to make sure that any underlying resource is freed.
175 """
176 self._fd_to_key.clear()
177
178 def get_key(self, fileobj):
179 """Return the key associated to a registered file object.
180
181 Returns:
182 SelectorKey for this file object
183 """
184 try:
185 return self._fd_to_key[_fileobj_to_fd(fileobj)]
186 except KeyError:
187 raise KeyError("{!r} is not registered".format(fileobj)) from None
188
Charles-François Natali4574b492013-10-30 20:31:04 +0100189 def get_map(self):
190 """Return a mapping of file objects to selector keys."""
191 return self._map
192
Charles-François Natali243d8d82013-09-04 19:02:49 +0200193 def __enter__(self):
194 return self
195
196 def __exit__(self, *args):
197 self.close()
198
199 def _key_from_fd(self, fd):
200 """Return the key associated to a given file descriptor.
201
202 Parameters:
203 fd -- file descriptor
204
205 Returns:
206 corresponding key, or None if not found
207 """
208 try:
209 return self._fd_to_key[fd]
210 except KeyError:
211 return None
212
213
214class SelectSelector(BaseSelector):
215 """Select-based selector."""
216
217 def __init__(self):
218 super().__init__()
219 self._readers = set()
220 self._writers = set()
221
222 def register(self, fileobj, events, data=None):
223 key = super().register(fileobj, events, data)
224 if events & EVENT_READ:
225 self._readers.add(key.fd)
226 if events & EVENT_WRITE:
227 self._writers.add(key.fd)
228 return key
229
230 def unregister(self, fileobj):
231 key = super().unregister(fileobj)
232 self._readers.discard(key.fd)
233 self._writers.discard(key.fd)
234 return key
235
236 if sys.platform == 'win32':
237 def _select(self, r, w, _, timeout=None):
238 r, w, x = select.select(r, w, w, timeout)
239 return r, w + x, []
240 else:
241 _select = select.select
242
243 def select(self, timeout=None):
244 timeout = None if timeout is None else max(timeout, 0)
245 ready = []
246 try:
247 r, w, _ = self._select(self._readers, self._writers, [], timeout)
248 except InterruptedError:
249 return ready
250 r = set(r)
251 w = set(w)
252 for fd in r | w:
253 events = 0
254 if fd in r:
255 events |= EVENT_READ
256 if fd in w:
257 events |= EVENT_WRITE
258
259 key = self._key_from_fd(fd)
260 if key:
261 ready.append((key, events & key.events))
262 return ready
263
264
265if hasattr(select, 'poll'):
266
267 class PollSelector(BaseSelector):
268 """Poll-based selector."""
269
270 def __init__(self):
271 super().__init__()
272 self._poll = select.poll()
273
274 def register(self, fileobj, events, data=None):
275 key = super().register(fileobj, events, data)
276 poll_events = 0
277 if events & EVENT_READ:
278 poll_events |= select.POLLIN
279 if events & EVENT_WRITE:
280 poll_events |= select.POLLOUT
281 self._poll.register(key.fd, poll_events)
282 return key
283
284 def unregister(self, fileobj):
285 key = super().unregister(fileobj)
286 self._poll.unregister(key.fd)
287 return key
288
289 def select(self, timeout=None):
290 timeout = None if timeout is None else max(int(1000 * timeout), 0)
291 ready = []
292 try:
293 fd_event_list = self._poll.poll(timeout)
294 except InterruptedError:
295 return ready
296 for fd, event in fd_event_list:
297 events = 0
298 if event & ~select.POLLIN:
299 events |= EVENT_WRITE
300 if event & ~select.POLLOUT:
301 events |= EVENT_READ
302
303 key = self._key_from_fd(fd)
304 if key:
305 ready.append((key, events & key.events))
306 return ready
307
308
309if hasattr(select, 'epoll'):
310
311 class EpollSelector(BaseSelector):
312 """Epoll-based selector."""
313
314 def __init__(self):
315 super().__init__()
316 self._epoll = select.epoll()
317
318 def fileno(self):
319 return self._epoll.fileno()
320
321 def register(self, fileobj, events, data=None):
322 key = super().register(fileobj, events, data)
323 epoll_events = 0
324 if events & EVENT_READ:
325 epoll_events |= select.EPOLLIN
326 if events & EVENT_WRITE:
327 epoll_events |= select.EPOLLOUT
328 self._epoll.register(key.fd, epoll_events)
329 return key
330
331 def unregister(self, fileobj):
332 key = super().unregister(fileobj)
333 self._epoll.unregister(key.fd)
334 return key
335
336 def select(self, timeout=None):
337 timeout = -1 if timeout is None else max(timeout, 0)
338 max_ev = len(self._fd_to_key)
339 ready = []
340 try:
341 fd_event_list = self._epoll.poll(timeout, max_ev)
342 except InterruptedError:
343 return ready
344 for fd, event in fd_event_list:
345 events = 0
346 if event & ~select.EPOLLIN:
347 events |= EVENT_WRITE
348 if event & ~select.EPOLLOUT:
349 events |= EVENT_READ
350
351 key = self._key_from_fd(fd)
352 if key:
353 ready.append((key, events & key.events))
354 return ready
355
356 def close(self):
Charles-François Natali243d8d82013-09-04 19:02:49 +0200357 self._epoll.close()
Guido van Rossum61a2ced2013-10-31 11:01:40 -0700358 super().close()
Charles-François Natali243d8d82013-09-04 19:02:49 +0200359
360
361if hasattr(select, 'kqueue'):
362
363 class KqueueSelector(BaseSelector):
364 """Kqueue-based selector."""
365
366 def __init__(self):
367 super().__init__()
368 self._kqueue = select.kqueue()
369
370 def fileno(self):
371 return self._kqueue.fileno()
372
373 def register(self, fileobj, events, data=None):
374 key = super().register(fileobj, events, data)
375 if events & EVENT_READ:
376 kev = select.kevent(key.fd, select.KQ_FILTER_READ,
377 select.KQ_EV_ADD)
378 self._kqueue.control([kev], 0, 0)
379 if events & EVENT_WRITE:
380 kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
381 select.KQ_EV_ADD)
382 self._kqueue.control([kev], 0, 0)
383 return key
384
385 def unregister(self, fileobj):
386 key = super().unregister(fileobj)
387 if key.events & EVENT_READ:
388 kev = select.kevent(key.fd, select.KQ_FILTER_READ,
389 select.KQ_EV_DELETE)
390 self._kqueue.control([kev], 0, 0)
391 if key.events & EVENT_WRITE:
392 kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
393 select.KQ_EV_DELETE)
394 self._kqueue.control([kev], 0, 0)
395 return key
396
397 def select(self, timeout=None):
398 timeout = None if timeout is None else max(timeout, 0)
399 max_ev = len(self._fd_to_key)
400 ready = []
401 try:
402 kev_list = self._kqueue.control(None, max_ev, timeout)
403 except InterruptedError:
404 return ready
405 for kev in kev_list:
406 fd = kev.ident
407 flag = kev.filter
408 events = 0
409 if flag == select.KQ_FILTER_READ:
410 events |= EVENT_READ
411 if flag == select.KQ_FILTER_WRITE:
412 events |= EVENT_WRITE
413
414 key = self._key_from_fd(fd)
415 if key:
416 ready.append((key, events & key.events))
417 return ready
418
419 def close(self):
Charles-François Natali243d8d82013-09-04 19:02:49 +0200420 self._kqueue.close()
Guido van Rossum61a2ced2013-10-31 11:01:40 -0700421 super().close()
Charles-François Natali243d8d82013-09-04 19:02:49 +0200422
423
424# Choose the best implementation: roughly, epoll|kqueue > poll > select.
425# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
426if 'KqueueSelector' in globals():
427 DefaultSelector = KqueueSelector
428elif 'EpollSelector' in globals():
429 DefaultSelector = EpollSelector
430elif 'PollSelector' in globals():
431 DefaultSelector = PollSelector
432else:
433 DefaultSelector = SelectSelector