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