subprocess py file compiles
[software/python-on-guile.git] / modules / language / python / module / selectors.py
1 module(selectors)
2
3 """Selectors module.
4
5 This module allows high-level and efficient I/O multiplexing, built upon the
6 `select` module primitives.
7 """
8
9
10 from abc import ABCMeta, abstractmethod
11 from collections import namedtuple, Mapping
12 import math
13 import select as selectraw
14 import sys
15
16 # generic events, that must be mapped to implementation-specific ones
17 EVENT_READ = (1 << 0)
18 EVENT_WRITE = (1 << 1)
19
20 def _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 Raises:
30 ValueError if the object is invalid
31 """
32 if isinstance(fileobj, int):
33 fd = fileobj
34 else:
35 try:
36 fd = int(fileobj.fileno())
37 except (AttributeError, TypeError, ValueError):
38 raise ValueError("Invalid file object: "
39 "{!r}".format(fileobj)) from None
40 if fd < 0:
41 raise ValueError("Invalid file descriptor: {}".format(fd))
42 return fd
43
44
45 SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data'])
46
47
48 class _SelectorMapping(Mapping):
49 """Mapping of file objects to selector keys."""
50
51 def __init__(self, selector):
52 self._selector = selector
53
54 def __len__(self):
55 return len(self._selector._fd_to_key)
56
57 def __getitem__(self, fileobj):
58 try:
59 fd = self._selector._fileobj_lookup(fileobj)
60 return self._selector._fd_to_key[fd]
61 except KeyError:
62 raise KeyError("{!r} is not registered".format(fileobj)) from None
63
64 def __iter__(self):
65 return iter(self._selector._fd_to_key)
66
67 class BaseSelector(metaclass=ABCMeta):
68 """Selector abstract base class.
69
70 A selector supports registering file objects to be monitored for specific
71 I/O events.
72
73 A file object is a file descriptor or any object with a `fileno()` method.
74 An arbitrary object can be attached to the file object, which can be used
75 for example to store context information, a callback, etc.
76
77 A selector can use various implementations (select(), poll(), epoll()...)
78 depending on the platform. The default `Selector` class uses the most
79 efficient implementation on the current platform.
80 """
81
82 @abstractmethod
83 def register(self, fileobj, events, data=None):
84 """Register a file object.
85
86 Parameters:
87 fileobj -- file object or file descriptor
88 events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)
89 data -- attached data
90
91 Returns:
92 SelectorKey instance
93
94 Raises:
95 ValueError if events is invalid
96 KeyError if fileobj is already registered
97 OSError if fileobj is closed or otherwise is unacceptable to
98 the underlying system call (if a system call is made)
99
100 Note:
101 OSError may or may not be raised
102 """
103 raise NotImplementedError
104
105 @abstractmethod
106 def unregister(self, fileobj):
107 """Unregister a file object.
108
109 Parameters:
110 fileobj -- file object or file descriptor
111
112 Returns:
113 SelectorKey instance
114
115 Raises:
116 KeyError if fileobj is not registered
117
118 Note:
119 If fileobj is registered but has since been closed this does
120 *not* raise OSError (even if the wrapped syscall does)
121 """
122 raise NotImplementedError
123
124 def modify(self, fileobj, events, data=None):
125 """Change a registered file object monitored events or attached data.
126
127 Parameters:
128 fileobj -- file object or file descriptor
129 events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)
130 data -- attached data
131
132 Returns:
133 SelectorKey instance
134
135 Raises:
136 Anything that unregister() or register() raises
137 """
138 self.unregister(fileobj)
139 return self.register(fileobj, events, data)
140
141 @abstractmethod
142 def select(self, timeout=None):
143 """Perform the actual selection, until some monitored file objects are
144 ready or a timeout expires.
145
146 Parameters:
147 timeout -- if timeout > 0, this specifies the maximum wait time, in
148 seconds
149 if timeout <= 0, the select() call won't block, and will
150 report the currently ready file objects
151 if timeout is None, select() will block until a monitored
152 file object becomes ready
153
154 Returns:
155 list of (key, events) for ready file objects
156 `events` is a bitwise mask of EVENT_READ|EVENT_WRITE
157 """
158 raise NotImplementedError
159
160 def close(self):
161 """Close the selector.
162
163 This must be called to make sure that any underlying resource is freed.
164 """
165 pass
166
167 def get_key(self, fileobj):
168 """Return the key associated to a registered file object.
169
170 Returns:
171 SelectorKey for this file object
172 """
173 mapping = self.get_map()
174 if mapping is None:
175 raise RuntimeError('Selector is closed')
176 try:
177 return mapping[fileobj]
178 except KeyError:
179 raise KeyError("{!r} is not registered".format(fileobj)) from None
180
181 @abstractmethod
182 def get_map(self):
183 """Return a mapping of file objects to selector keys."""
184 raise NotImplementedError
185
186 def __enter__(self):
187 return self
188
189 def __exit__(self, *args):
190 self.close()
191
192 class _BaseSelectorImpl(BaseSelector):
193 """Base selector implementation."""
194
195 def __init__(self):
196 # this maps file descriptors to keys
197 self._fd_to_key = {}
198 # read-only mapping returned by get_map()
199 self._map = _SelectorMapping(self)
200
201 def _fileobj_lookup(self, fileobj):
202 """Return a file descriptor from a file object.
203
204 This wraps _fileobj_to_fd() to do an exhaustive search in case
205 the object is invalid but we still have it in our map. This
206 is used by unregister() so we can unregister an object that
207 was previously registered even if it is closed. It is also
208 used by _SelectorMapping.
209 """
210 try:
211 return _fileobj_to_fd(fileobj)
212 except ValueError:
213 # Do an exhaustive search.
214 for key in self._fd_to_key.values():
215 if key.fileobj is fileobj:
216 return key.fd
217 # Raise ValueError after all.
218 raise
219
220 def register(self, fileobj, events, data=None):
221 if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
222 raise ValueError("Invalid events: {!r}".format(events))
223
224 key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data)
225
226 if key.fd in self._fd_to_key:
227 raise KeyError("{!r} (FD {}) is already registered"
228 .format(fileobj, key.fd))
229
230 self._fd_to_key[key.fd] = key
231 return key
232
233 def unregister(self, fileobj):
234 try:
235 key = self._fd_to_key.pop(self._fileobj_lookup(fileobj))
236 except KeyError:
237 raise KeyError("{!r} is not registered".format(fileobj)) from None
238 return key
239
240 def modify(self, fileobj, events, data=None):
241 # TODO: Subclasses can probably optimize this even further.
242 try:
243 key = self._fd_to_key[self._fileobj_lookup(fileobj)]
244 except KeyError:
245 raise KeyError("{!r} is not registered".format(fileobj)) from None
246 if events != key.events:
247 self.unregister(fileobj)
248 key = self.register(fileobj, events, data)
249 elif data != key.data:
250 # Use a shortcut to update the data.
251 key = key._replace(data=data)
252 self._fd_to_key[key.fd] = key
253 return key
254
255 def close(self):
256 self._fd_to_key.clear()
257 self._map = None
258
259 def get_map(self):
260 return self._map
261
262 def _key_from_fd(self, fd):
263 """Return the key associated to a given file descriptor.
264
265 Parameters:
266 fd -- file descriptor
267
268 Returns:
269 corresponding key, or None if not found
270 """
271 try:
272 return self._fd_to_key[fd]
273 except KeyError:
274 return None
275
276
277 class SelectSelector(_BaseSelectorImpl):
278 """Select-based selector."""
279
280 def __init__(self):
281 super().__init__()
282 self._readers = set()
283 self._writers = set()
284
285 def register(self, fileobj, events, data=None):
286 key = super().register(fileobj, events, data)
287 if events & EVENT_READ:
288 self._readers.add(key.fd)
289 if events & EVENT_WRITE:
290 self._writers.add(key.fd)
291 return key
292
293 def unregister(self, fileobj):
294 key = super().unregister(fileobj)
295 self._readers.discard(key.fd)
296 self._writers.discard(key.fd)
297 return key
298
299 if sys.platform == 'win32':
300 def _select(self, r, w, _, timeout=None):
301 r, w, x = selectraw.select(r, w, w, timeout)
302 return r, w + x, []
303 else:
304 _select = selectraw.select
305
306 def select(self, timeout=None):
307 timeout = None if timeout is None else max(timeout, 0)
308 ready = []
309 try:
310 r, w, _ = self._select(self._readers, self._writers, [], timeout)
311 except InterruptedError:
312 return ready
313 r = set(r)
314 w = set(w)
315 for fd in r | w:
316 events = 0
317 if fd in r:
318 events |= EVENT_READ
319 if fd in w:
320 events |= EVENT_WRITE
321
322 key = self._key_from_fd(fd)
323 if key:
324 ready.append((key, events & key.events))
325 return ready
326
327 class PollSelector(_BaseSelectorImpl):
328 """Poll-based selector."""
329
330 def __init__(self):
331 super().__init__()
332 self._poll = selectraw.poll()
333
334 def register(self, fileobj, events, data=None):
335 key = super().register(fileobj, events, data)
336 poll_events = 0
337 if events & EVENT_READ:
338 poll_events |= selectraw.POLLIN
339 if events & EVENT_WRITE:
340 poll_events |= selectraw.POLLOUT
341 self._poll.register(key.fd, poll_events)
342 return key
343
344 def unregister(self, fileobj):
345 key = super().unregister(fileobj)
346 self._poll.unregister(key.fd)
347 return key
348
349 def select(self, timeout=None):
350 if timeout is None:
351 timeout = None
352 elif timeout <= 0:
353 timeout = 0
354 else:
355 # poll() has a resolution of 1 millisecond, round away from
356 # zero to wait *at least* timeout seconds.
357 timeout = math.ceil(timeout * 1e3)
358 ready = []
359 try:
360 fd_event_list = self._poll.poll(timeout)
361 except InterruptedError:
362 return ready
363 for fd, event in fd_event_list:
364 events = 0
365 if event & ~selectraw.POLLIN:
366 events |= EVENT_WRITE
367 if event & ~selectraw.POLLOUT:
368 events |= EVENT_READ
369
370 key = self._key_from_fd(fd)
371 if key:
372 ready.append((key, events & key.events))
373 return ready
374
375
376 class EpollSelector(_BaseSelectorImpl):
377 """Epoll-based selector."""
378
379 def __init__(self):
380 super().__init__()
381 self._epoll = selectraw.epoll()
382
383 def fileno(self):
384 return self._epoll.fileno()
385
386 def register(self, fileobj, events, data=None):
387 key = super().register(fileobj, events, data)
388 epoll_events = 0
389 if events & EVENT_READ:
390 epoll_events |= selectraw.EPOLLIN
391 if events & EVENT_WRITE:
392 epoll_events |= selectraw.EPOLLOUT
393 try:
394 self._epoll.register(key.fd, epoll_events)
395 except BaseException:
396 super().unregister(fileobj)
397 raise
398 return key
399
400 def unregister(self, fileobj):
401 key = super().unregister(fileobj)
402 try:
403 self._epoll.unregister(key.fd)
404 except OSError:
405 # This can happen if the FD was closed since it
406 # was registered.
407 pass
408 return key
409
410 def select(self, timeout=None):
411 if timeout is None:
412 timeout = -1
413 elif timeout <= 0:
414 timeout = 0
415 else:
416 # epoll_wait() has a resolution of 1 millisecond, round away
417 # from zero to wait *at least* timeout seconds.
418 timeout = math.ceil(timeout * 1e3) * 1e-3
419
420 # epoll_wait() expects `maxevents` to be greater than zero;
421 # we want to make sure that `select()` can be called when no
422 # FD is registered.
423 max_ev = max(len(self._fd_to_key), 1)
424
425 ready = []
426 try:
427 fd_event_list = self._epoll.poll(timeout, max_ev)
428 except InterruptedError:
429 return ready
430 for fd, event in fd_event_list:
431 events = 0
432 if event & ~selectraw.EPOLLIN:
433 events |= EVENT_WRITE
434 if event & ~selectraw.EPOLLOUT:
435 events |= EVENT_READ
436
437 key = self._key_from_fd(fd)
438 if key:
439 ready.append((key, events & key.events))
440 return ready
441
442 def close(self):
443 self._epoll.close()
444 super().close()
445
446 DefaultSelector = EpollSelector
447
448 __all__ = [ 'BaseSelector' ,
449 'DefaultSelector' ,
450 'EpollSelector' ,
451 'PollSelector' ,
452 'SelectSelector' ,
453 'EventRead' ,
454 'EventWrite' ]
455