diff -r 1287d4c9cd39 Doc/library/select.rst --- a/Doc/library/select.rst Fri Aug 02 10:22:07 2013 +0200 +++ b/Doc/library/select.rst Sat Aug 03 00:43:52 2013 +0200 @@ -5,12 +5,18 @@ :synopsis: Wait for I/O completion on multiple streams. -This module provides access to the :c:func:`select` and :c:func:`poll` functions -available in most operating systems, :c:func:`devpoll` available on +This module defines a :class:`BaseSelector` abstract base class, along with +several concrete implementations (:class:`DefaultSelector`, +:class:`EpollSelector`...), that can be used to wait for I/O readiness +notification on multiple streams. + +It also provides access to the low-level :c:func:`select` and :c:func:`poll` +functions available in most operating systems, :c:func:`devpoll` available on Solaris and derivatives, :c:func:`epoll` available on Linux 2.5+ and :c:func:`kqueue` available on most BSD. -Note that on Windows, it only works for sockets; on other operating systems, -it also works for other file types (in particular, on Unix, it works on pipes). + +Note that on Windows, it only works for sockets; on other operating systems, it +also works for other file types (in particular, on Unix, it works on pipes). It cannot be used on regular files to determine whether a file has grown since it was last read. @@ -24,6 +30,236 @@ .. versionchanged:: 3.3 Following :pep:`3151`, this class was made an alias of :exc:`OSError`. + +.. class:: BaseSelector + :noindex: + + Selector abstract base class. All other selector classes implement it. It + provides methods for streams registration, polling... + +.. class:: DefaultSelector + :noindex: + + The default selector class, using the most efficient implementation + available on the current platform. This is what you should use. + +.. class:: SelectSelector + :noindex: + + :c:func:`select`-based selector. + +.. class:: PollSelector + :noindex: + + :c:func:`poll`-based selector. + +.. class:: EpollSelector + :noindex: + + :c:func:`epoll`-based selector. + +.. class:: KqueueSelector + :noindex: + + :c:func:`kqueue`-based selector. + +Subclass relationships:: + + BaseSelector + SelectSelector + PollSelector + EpollSelector + KqueueSelector + + +.. class:: SelectorKey + :noindex: + + A :class:`~collections.namedtuple` associating a file object to its backing + file descriptor, selected event mask and attached data. It is used + internally by the selectors, and is also returned by some methods for + inspection. + + .. versionadded:: 3.4 + +In :class:`BaseSelector` and :class:`SelectorKey` methods and attributes, +*events* is a bitwise mask indicating which I/O events should be waited for on +a given file object. It can be a combination of the constants below: + + +-----------------------+-----------------------------------------------+ + | Constant | Meaning | + +=======================+===============================================+ + | :const:`EVENT_READ` | Available for read | + +-----------------------+-----------------------------------------------+ + | :const:`EVENT_WRITE` | Available for write | + +-----------------------+-----------------------------------------------+ + + +.. _select-BaseSelector: + +:class:`BaseSelector` Objects +----------------------------- + +A :class:`BaseSelector` is used to wait for I/O event readiness on multiple +file streams. It supports file stream registration, unregistration, and a +method to wait for I/O events on those streams, with an optional timeout. It's +an abstract base class, so should not be instanciated. Use +:class:`DefaultSelector` instead, or one of :class:`SelectSelector`, +:class:`KqueueSelector` etc. if you want to specifically use an implementation, +and your platform supports it. + + +.. class:: BaseSelector() + + Constructor. + + +All class methods: + +.. method:: BaseSelector.register(fileobj, events, data=None) + + Register a file object for selection, monitoring it for events. + + *fileobj* must be a file descriptor or any object with a :meth:`fileno` + method + *events* is a bitwise mask of events to monitor + *data* is an opaque object that will be returned along with the ready file + object by :meth:`BaseSelector.select` + + This returns new :class:`SelectorKey` instance, or raises a + :exc:`ValueError` in case of invalid event mask or if the file object - or + its underlying file descriptor - is already registered. + +.. method:: BaseSelector.unregister(fileobj) + + Unregister a file object from selection, removing it from monitoring. + + *fileobj* must be a file descriptor or any object with a :meth:`fileno` + method previously registered. + + This returns the associated :class:`SelectorKey` instance, and raises a + :exc:`ValueError` if the file object is not registered. + +.. method:: BaseSelector.modify(fileobj, events, data=None) + + Change a registered file object monitored events or attached data. + + This is equivalent to :meth:`BaseSelector.unregister(fileobj)` followed by + :meth:`BaseSelector.register(fileobj, events, data)`, except that it can be + implemented more efficiently. + + This returns a new :class:`SelectorKey` instance, or raises a + :exc:`ValueError` in case of invalid event mask or if the file object is not + registered. + +.. method:: BaseSelector.select(timeout=None) + + Wait until some registered file objects become ready, or the timeout + expires. + + if ``timeout > 0``, this specifies the maximum wait time, in seconds + if ``timeout <= 0``, the call won't block, and will report the currently + ready file objects + if *timeout* is ``None``, the call will block until a monitored file object + becomes ready + + This returns a list of ``(fileobj, events, data)`` tuple, one for each ready + file object. + + *fileobj* is the original file object registered + *events* is a bitmask of events ready on this file object + *data* is the data passed to :class:`BaseSelector.register` + +.. method:: BaseSelector.close() + + Close the selector. + + This must be called to make sure that any underlying resource is freed. + +.. method:: BaseSelector.get_keys() + + Return the set of all :class:`SelectorKey` registered. + +.. method:: BaseSelector.get_info(fileobj) + + Return information about a registered file object. + + This returns the ``(events, data)`` tuple associated to this file object, + and raises :exc:`KeyError` if the file object is not registered. + + *events* is a bitmask of events ready on this file object + *data* is the data passed to :class:`BaseSelector.register` + +.. method:: BaseSelector.registered_count() + + This returns the number of currently registered file objects. + +.. method:: BaseSelector.fileno() + + This returns the file descriptor associated to the selector, or ``None`` if + not applicable. + + +.. _select-SelectorKey: + +:class:`SelectorKey` Objects +---------------------------- + +A :class:`SelectorKey` is a :class:`~collections.namedtuple` used to associate +a file object to its backing file decriptor, selected event mask and attached +data. It's returned by some methods to make it easier for the caller to keep +track of registered file objects. + + +.. attribute:: SelectorKey.fileobj + + File object registered. + +.. attribute:: SelectorKey.fd + + Backing file descriptor. + +.. attribute:: SelectorKey.events + + Events that must be waited for this file object. + +.. attribute:: SelectorKey.data + + Optional opaque data associated to this file descriptor: for example, this + could be used to store per-client session. + + +Examples of selector usage:: + + >>> import os + >>> import select + >>> + >>> s = select.DefaultSelector() + >>> r, w = os.pipe() + >>> + >>> for i in range(2): + ... s.register(os.dup(r), select.EVENT_READ) + ... s.register(os.dup(w), select.EVENT_WRITE) + ... + SelectorKey(fileobj=6, fd=6, events=1, data=None) + SelectorKey(fileobj=7, fd=7, events=2, data=None) + SelectorKey(fileobj=8, fd=8, events=1, data=None) + SelectorKey(fileobj=9, fd=9, events=2, data=None) + >>> print(s.select()) + [(7, 2, None), (9, 2, None)] + >>> + >>> for fd, event, data in s.select(): + ... if event & select.EVENT_WRITE: + ... os.write(fd, b'w') + ... + 1 + 1 + >>> print(s.select()) + [(7, 2, None), (9, 2, None), (8, 1, None), (6, 1, None)] + >>> s.close() + + +Interface to low-level operating systems primitives: .. function:: devpoll() diff -r 1287d4c9cd39 Lib/select.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/select.py Sat Aug 03 00:43:52 2013 +0200 @@ -0,0 +1,441 @@ +"""Select module. + +This module supports asynchronous I/O on multiple file descriptors. +""" + + +from abc import ABCMeta, abstractmethod +from collections import namedtuple +import functools +from _select import * +import sys +try: + from time import monotonic as time +except ImportError: + from time import time as time + + +# generic events, that must be mapped to implementation-specific ones +EVENT_READ = (1 << 0) +EVENT_WRITE = (1 << 1) + + +def _fileobj_to_fd(fileobj): + """Return a file descriptor from a file object. + + Parameters: + fileobj -- file descriptor, or any object with a `fileno()` method + + Returns: + corresponding file descriptor + """ + if isinstance(fileobj, int): + fd = fileobj + else: + try: + fd = int(fileobj.fileno()) + except (ValueError, TypeError): + raise ValueError("Invalid file object: {!r}".format(fileobj)) + return fd + + +def _select_interrupt_wrapper(func): + """InterruptedError-safe wrapper for select(), taking the (optional) + timeout into account.""" + @functools.wraps(func) + def wrapper(self, timeout=None): + if timeout is not None and timeout > 0: + deadline = time() + timeout + while True: + try: + return func(self, timeout) + except InterruptedError: + if timeout is not None: + if timeout > 0: + timeout = deadline - time() + if timeout <= 0: + # timeout expired + return [] + + return wrapper + + +SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data']) +"""Object used to associate a file object to its backing file descriptor, +selected event mask and attached data.""" + + +class BaseSelector(metaclass=ABCMeta): + """Base selector class. + + A selector supports registering file objects to be monitored for specific + I/O events. + + A file object is a file descriptor or any object with a `fileno()` method. + An arbitrary object can be attached to the file object, which can be used + for example to store context information, a callback, etc. + + A selector can use various implementations (select(), poll(), epoll()...) + depending on the platform. The default `Selector` class uses the most + performant implementation on the current platform. + """ + + def __init__(self): + # this maps file descriptors to keys + self._fd_to_key = {} + # this maps file objects to keys - for fast (un)registering + self._fileobj_to_key = {} + + def register(self, fileobj, events, data=None): + """Register a file object. + + Parameters: + fileobj -- file object + events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE) + data -- attached data + + Returns: + SelectorKey instance + """ + if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)): + raise ValueError("Invalid events: {}".format(events)) + + key = SelectorKey(fileobj, _fileobj_to_fd(fileobj), events, data) + + if key.fd in self._fd_to_key: + raise ValueError("{!r} (FD {}) is already " + "registered".format(fileobj, key.fd)) + + self._fd_to_key[key.fd] = key + self._fileobj_to_key[fileobj] = key + return key + + def unregister(self, fileobj): + """Unregister a file object. + + Parameters: + fileobj -- file object + + Returns: + SelectorKey instance + """ + try: + key = self._fileobj_to_key.pop(fileobj) + del self._fd_to_key[key.fd] + except KeyError: + raise ValueError("{!r} is not registered".format(fileobj)) + return key + + def modify(self, fileobj, events, data=None): + """Change a registered file object monitored events or attached data. + + Parameters: + fileobj -- file object + events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE) + data -- attached data + """ + # TODO: Subclasses can probably optimize this even further. + try: + key = self._fileobj_to_key[fileobj] + except KeyError: + raise ValueError("{!r} is not registered".format(fileobj)) + if events != key.events or data != key.data: + # TODO: If only the data changed, use a shortcut that only + # updates the data. + self.unregister(fileobj) + return self.register(fileobj, events, data) + else: + return key + + @abstractmethod + def select(self, timeout=None): + """Perform the actual selection, until some monitored file objects are + ready or a timeout expires. + + Parameters: + timeout -- if timeout > 0, this specifies the maximum wait time, in + seconds + if timeout <= 0, the select() call won't block, and will + report the currently ready file objects + if timeout is None, select() will block until a monitored + file object becomes ready + + Returns: + list of (fileobj, events, attached data) for ready file objects + `events` is a bitwise mask of EVENT_READ|EVENT_WRITE + """ + raise NotImplementedError() + + def close(self): + """Close the selector. + + This must be called to make sure that any underlying resource is freed. + """ + self._fd_to_key.clear() + self._fileobj_to_key.clear() + + def get_keys(self): + """Return the set of SelectorKey for all the file objects + registered.""" + return self._fd_to_key.values() + + def get_info(self, fileobj): + """Return information about a registered file object. + + Returns: + (events, data) associated to this file object + + Raises KeyError if the file object is not registered. + """ + try: + key = self._fileobj_to_key[fileobj] + except KeyError: + raise KeyError("{} is not registered".format(fileobj)) + return key.events, key.data + + def registered_count(self): + """Return the number of registered file objects. + + Returns: + number of currently registered file objects + """ + return len(self.get_keys()) + + def fileno(self): + """Return the file descriptor associated to the selector, or None if + not applicable.""" + return None + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def _key_from_fd(self, fd): + """Return the key associated to a given file descriptor. + + Parameters: + fd -- file descriptor + + Returns: + corresponding key, or None if not found + """ + try: + return self._fd_to_key[fd] + except KeyError: + return None + + +class SelectSelector(BaseSelector): + """Select-based selector.""" + + def __init__(self): + super().__init__() + self._readers = set() + self._writers = set() + + def register(self, fileobj, events, data=None): + key = super().register(fileobj, events, data) + if events & EVENT_READ: + self._readers.add(key.fd) + if events & EVENT_WRITE: + self._writers.add(key.fd) + return key + + def unregister(self, fileobj): + key = super().unregister(fileobj) + self._readers.discard(key.fd) + self._writers.discard(key.fd) + return key + + @_select_interrupt_wrapper + def select(self, timeout=None): + timeout = None if timeout is None else max(timeout, 0) + r, w, _ = self._select(self._readers, self._writers, [], timeout) + r = set(r) + w = set(w) + ready = [] + for fd in r | w: + events = 0 + if fd in r: + events |= EVENT_READ + if fd in w: + events |= EVENT_WRITE + + key = self._key_from_fd(fd) + if key: + ready.append((key.fileobj, events & key.events, key.data)) + return ready + + if sys.platform == 'win32': + def _select(self, r, w, _, timeout=None): + r, w, x = select(r, w, w, timeout) + return r, w + x, [] + else: + from _select import select as _select + + +if 'poll' in globals(): + + # TODO: Implement poll() for Windows with workaround for + # brokenness in WSAPoll() (Richard Oudkerk, see + # http://bugs.python.org/issue16507). + + class PollSelector(BaseSelector): + """Poll-based selector.""" + + def __init__(self): + super().__init__() + self._poll = poll() + + def register(self, fileobj, events, data=None): + key = super().register(fileobj, events, data) + poll_events = 0 + if events & EVENT_READ: + poll_events |= POLLIN + if events & EVENT_WRITE: + poll_events |= POLLOUT + self._poll.register(key.fd, poll_events) + return key + + def unregister(self, fileobj): + key = super().unregister(fileobj) + self._poll.unregister(key.fd) + return key + + @_select_interrupt_wrapper + def select(self, timeout=None): + timeout = None if timeout is None else max(int(1000 * timeout), 0) + ready = [] + fd_event_list = self._poll.poll(timeout) + for fd, event in fd_event_list: + events = 0 + if event & ~POLLIN: + events |= EVENT_WRITE + if event & ~POLLOUT: + events |= EVENT_READ + + key = self._key_from_fd(fd) + if key: + ready.append((key.fileobj, events & key.events, key.data)) + return ready + + +if 'epoll' in globals(): + + class EpollSelector(BaseSelector): + """Epoll-based selector.""" + + def __init__(self): + super().__init__() + self._epoll = epoll() + + def fileno(self): + return self._epoll.fileno() + + def register(self, fileobj, events, data=None): + key = super().register(fileobj, events, data) + epoll_events = 0 + if events & EVENT_READ: + epoll_events |= EPOLLIN + if events & EVENT_WRITE: + epoll_events |= EPOLLOUT + self._epoll.register(key.fd, epoll_events) + return key + + def unregister(self, fileobj): + key = super().unregister(fileobj) + self._epoll.unregister(key.fd) + return key + + @_select_interrupt_wrapper + def select(self, timeout=None): + timeout = -1 if timeout is None else max(timeout, 0) + max_ev = len(self.get_keys()) + ready = [] + fd_event_list = self._epoll.poll(timeout, max_ev) + for fd, event in fd_event_list: + events = 0 + if event & ~EPOLLIN: + events |= EVENT_WRITE + if event & ~EPOLLOUT: + events |= EVENT_READ + + key = self._key_from_fd(fd) + if key: + ready.append((key.fileobj, events & key.events, key.data)) + return ready + + def close(self): + super().close() + self._epoll.close() + + +if 'kqueue' in globals(): + + class KqueueSelector(BaseSelector): + """Kqueue-based selector.""" + + def __init__(self): + super().__init__() + self._kqueue = kqueue() + + def fileno(self): + return self._kqueue.fileno() + + def unregister(self, fileobj): + key = super().unregister(fileobj) + if key.events & EVENT_READ: + kev = kevent(key.fd, KQ_FILTER_READ, KQ_EV_DELETE) + self._kqueue.control([kev], 0, 0) + if key.events & EVENT_WRITE: + kev = kevent(key.fd, KQ_FILTER_WRITE, KQ_EV_DELETE) + self._kqueue.control([kev], 0, 0) + return key + + def register(self, fileobj, events, data=None): + key = super().register(fileobj, events, data) + if events & EVENT_READ: + kev = kevent(key.fd, KQ_FILTER_READ, KQ_EV_ADD) + self._kqueue.control([kev], 0, 0) + if events & EVENT_WRITE: + kev = kevent(key.fd, KQ_FILTER_WRITE, KQ_EV_ADD) + self._kqueue.control([kev], 0, 0) + return key + + @_select_interrupt_wrapper + def select(self, timeout=None): + timeout = None if timeout is None else max(timeout, 0) + max_ev = len(self.get_keys()) + ready = [] + kev_list = self._kqueue.control(None, max_ev, timeout) + for kev in kev_list: + fd = kev.ident + flag = kev.filter + events = 0 + if flag == KQ_FILTER_READ: + events |= EVENT_READ + if flag == KQ_FILTER_WRITE: + events |= EVENT_WRITE + + key = self._key_from_fd(fd) + if key: + ready.append((key.fileobj, events & key.events, key.data)) + return ready + + def close(self): + super().close() + self._kqueue.close() + + +# Choose the best implementation: roughly, epoll|kqueue > poll > select. +# select() also can't accept a FD > FD_SETSIZE (usually around 1024) +if 'KqueueSelector' in globals(): + DefaultSelector = KqueueSelector +elif 'EpollSelector' in globals(): + DefaultSelector = EpollSelector +elif 'PollSelector' in globals(): + DefaultSelector = PollSelector +else: + DefaultSelector = SelectSelector diff -r 1287d4c9cd39 Lib/test/test_select.py --- a/Lib/test/test_select.py Fri Aug 02 10:22:07 2013 +0200 +++ b/Lib/test/test_select.py Sat Aug 03 00:43:52 2013 +0200 @@ -1,9 +1,28 @@ import errno import os +import random import select +import signal import sys import unittest from test import support +try: + from time import monotonic as time +except ImportError: + from time import time as time +try: + import resource +except ImportError: + resource = None + + +def find_ready_matching(ready, flag): + match = [] + for fd, mode, data in ready: + if mode & flag: + match.append(fd) + return match + @unittest.skipIf((sys.platform[:3]=='win'), "can't easily test on this system") @@ -75,9 +94,383 @@ a[:] = [F()] * 10 self.assertEqual(select.select([], a, []), ([], a[:5], [])) + +class BaseSelectorTestCase(unittest.TestCase): + + def test_register(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + self.addCleanup(os.close, wr) + rdo = os.fdopen(rd, "rb", closefd=False) + + key = s.register(rdo, select.EVENT_READ, "data") + self.assertIsInstance(key, select.SelectorKey) + self.assertEqual(key.fileobj, rdo) + self.assertEqual(key.fd, rd) + self.assertEqual(key.data, "data") + + # register an unknown event + self.assertRaises(ValueError, s.register, 0, 999999) + + # register twice + self.assertRaises(ValueError, s.register, rdo, select.EVENT_READ) + + # register the same FD, but with a different object + self.assertRaises(ValueError, s.register, rd, select.EVENT_READ) + + def test_unregister(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + self.addCleanup(os.close, wr) + + s.register(rd, select.EVENT_READ) + s.unregister(rd) + self.assertFalse(s.get_keys()) + + # unregister an unknown file obj + self.assertRaises(ValueError, s.unregister, 999999) + + # unregister twice + self.assertRaises(ValueError, s.unregister, rd) + + def test_modify(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + self.addCleanup(os.close, wr) + + key = s.register(rd, select.EVENT_READ) + + # modify events + key2 = s.modify(rd, select.EVENT_WRITE) + self.assertNotEqual(key.events, key2.events) + self.assertEqual((select.EVENT_WRITE, None), s.get_info(rd)) + + s.unregister(rd) + + # modify data + d1 = object() + d2 = object() + + key = s.register(rd, select.EVENT_READ, d1) + key2 = s.modify(rd, select.EVENT_READ, d2) + self.assertEqual(key.events, key2.events) + self.assertNotEqual(key.data, key2.data) + self.assertEqual((select.EVENT_READ, d2), s.get_info(rd)) + + # modify unknown file obj + self.assertRaises(ValueError, s.modify, 999999, select.EVENT_READ) + + def test_close(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + self.addCleanup(os.close, wr) + + s.register(rd, select.EVENT_READ) + s.register(wr, select.EVENT_WRITE) + + s.close() + self.assertFalse(s.get_keys()) + + def test_get_keys(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + self.addCleanup(os.close, wr) + rdo = os.fdopen(rd, "rb", closefd=False) + wro = os.fdopen(wr, "wb", closefd=False) + + s.register(rdo, select.EVENT_READ, "rdo") + s.register(wro, select.EVENT_WRITE, "wro") + + self.assertEqual(set(s.get_keys()), + set([select.SelectorKey(rdo, rd, select.EVENT_READ, "rdo"), + select.SelectorKey(wro, wr, select.EVENT_WRITE, "wro")])) + + s.close() + self.assertFalse(s.get_keys()) + + def test_get_info(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + self.addCleanup(os.close, wr) + + s.register(rd, select.EVENT_READ, "data") + self.assertEqual((select.EVENT_READ, "data"), s.get_info(rd)) + + # unknown file obj + self.assertRaises(KeyError, s.get_info, 999999) + + def test_registered_count(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + self.addCleanup(os.close, wr) + + self.assertEqual(0, s.registered_count()) + + s.register(rd, select.EVENT_READ) + s.register(wr, select.EVENT_WRITE) + self.assertEqual(2, s.registered_count()) + + s.unregister(rd) + s.unregister(wr) + self.assertEqual(0, s.registered_count()) + + def test_context_manager(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + self.addCleanup(os.close, wr) + + with s as sel: + sel.register(rd, select.EVENT_READ) + sel.register(wr, select.EVENT_WRITE) + + self.assertFalse(s.get_keys()) + + @unittest.skipUnless(hasattr(select.DefaultSelector, 'fileno'), + 'test needs DefaultSelector.fileno()') + def test_fileno(self): + self.assertIsInstance(select.DefaultSelector().fileno(), int) + + def test_selector(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + NUM_PIPES = 12 + MSG = b" This is a test." + MSG_LEN = len(MSG) + readers = [] + writers = [] + r2w = {} + w2r = {} + + for i in range(NUM_PIPES): + rd, wr = os.pipe() + s.register(rd, select.EVENT_READ) + s.register(wr, select.EVENT_WRITE) + readers.append(rd) + writers.append(wr) + r2w[rd] = wr + w2r[wr] = rd + + bufs = [] + + while writers: + ready = s.select() + ready_writers = find_ready_matching(ready, select.EVENT_WRITE) + if not ready_writers: + self.fail("no pipes ready for writing") + wr = random.choice(ready_writers) + os.write(wr, MSG) + + ready = s.select() + ready_readers = find_ready_matching(ready, select.EVENT_READ) + if not ready_readers: + self.fail("no pipes ready for reading") + self.assertEqual([w2r[wr]], ready_readers) + rd = ready_readers[0] + buf = os.read(rd, MSG_LEN) + self.assertEqual(len(buf), MSG_LEN) + bufs.append(buf) + os.close(r2w[rd]) ; os.close(rd) + s.unregister(r2w[rd]) + s.unregister(rd) + writers.remove(r2w[rd]) + + self.assertEqual(bufs, [MSG] * NUM_PIPES) + + @unittest.skipUnless((os.name == 'posix'), "test requires a POSIX system") + def test_multi_timeout(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + cmd = 'for i in 0 1 2 3 4 5 6 7 8 9; do echo testing...; sleep 1; done' + p = os.popen(cmd, 'r') + s.register(p.fileno(), select.EVENT_READ, p) + + for tout in (0, 1, 2, 4, 8, 16) + (None,)*10: + if support.verbose: + print('timeout =', tout) + + ready = s.select(tout) + if not ready: + continue + eof_seen = False + for fileobj, evt, data in ready: + if fileobj == p.fileno() and evt & select.EVENT_READ: + while True: + line = p.readline() + if support.verbose: + print(repr(line)) + if not line: + if support.verbose: + print('EOF') + eof_seen = True + break + if not eof_seen: + self.fail('Unexpected return values from select(): %r' % ready) + p.close() + + def test_below_fd_setsize(self): + # No implementation should have a problem with less than FD_SETSIZE + # file descriptors. To be conservative, let's say 64. + NUM_FDS = 64 + + s = self.SELECTOR() + self.addCleanup(s.close) + + r, w = os.pipe() + self.addCleanup(os.close, r) + self.addCleanup(os.close, w) + + for i in range(NUM_FDS): + fd = os.dup(w) + self.addCleanup(os.close, fd) + + s.register(fd, select.EVENT_WRITE) + self.assertEquals(NUM_FDS, len(s.select())) + + def test_timeout(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + self.addCleanup(os.close, wr) + + s.register(wr, select.EVENT_WRITE) + self.assertEqual(1, len(s.select(0))) + self.assertEqual(1, len(s.select(-1))) + + s.unregister(wr) + s.register(rd, select.EVENT_READ) + self.assertEqual(0, len(s.select(0))) + self.assertEqual(0, len(s.select(-1))) + + @unittest.skipUnless(hasattr(signal, "alarm"), + "signal.alarm() required for this test") + def test_interrupted(self): + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = os.pipe() + self.addCleanup(os.close, rd) + self.addCleanup(os.close, wr) + + orig_alrm_handler = signal.signal(signal.SIGALRM, lambda *args: None) + self.addCleanup(signal.alarm, 0) + self.addCleanup(signal.signal, signal.SIGALRM, orig_alrm_handler) + + signal.alarm(1) + + s.register(rd, select.EVENT_READ) + t = time() + self.assertEqual([], s.select(2)) + self.assertTrue(1.7 < time() - t < 3) + + +class ScalableSelectorMixIn: + + @unittest.skipUnless(resource, "Test needs resource module") + def test_above_fd_setsize(self): + # A scalable implementation should have no problem with more than + # FD_SETSIZE file descriptors. Since we don't know the value, we just + # try to set the soft RLIMIT_NOFILE to the hard RLIMIT_NOFILE ceiling. + try: + soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) + resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard)) + self.addCleanup(resource.setrlimit, resource.RLIMIT_NOFILE, + (soft, hard)) + NUM_FDS = hard + except OSError: + NUM_FDS = soft + + # guard for already allocated FDs (stdin, stdout...) + NUM_FDS -= 32 + + s = self.SELECTOR() + self.addCleanup(s.close) + + r, w = os.pipe() + self.addCleanup(os.close, r) + self.addCleanup(os.close, w) + + for i in range(NUM_FDS): + try: + fd = os.dup(w) + except OSError as e: + if e.errno == errno.EMFILE: + # too many FD, skip + self.skipTest("FD limit reached") + raise + + self.addCleanup(os.close, fd) + s.register(fd, select.EVENT_WRITE) + + self.assertEquals(NUM_FDS, len(s.select())) + + +class SelectorTestCase(BaseSelectorTestCase): + + SELECTOR = select.DefaultSelector + + +class SelectSelectorTestCase(BaseSelectorTestCase): + + SELECTOR = select.SelectSelector + + +@unittest.skipUnless(hasattr(select, 'PollSelector'), + "Test needs select.poll()") +class PollSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn): + + SELECTOR = getattr(select, 'PollSelector', None) + + +@unittest.skipUnless(hasattr(select, 'EpollSelector'), + "Test needs select.epoll()") +class EpollSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn): + + SELECTOR = getattr(select, 'EpollSelector', None) + + +@unittest.skipUnless(hasattr(select, 'KqueueSelector'), + "Test needs select.kqueue()") +class KqueueSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn): + + SELECTOR = getattr(select, 'KqueueSelector', None) + + def test_main(): - support.run_unittest(SelectTestCase) + tests = [SelectTestCase] + tests.extend([SelectorTestCase, SelectSelectorTestCase, + PollSelectorTestCase, EpollSelectorTestCase, + KqueueSelectorTestCase]) + support.run_unittest(*tests) support.reap_children() + if __name__ == "__main__": test_main() diff -r 1287d4c9cd39 Modules/selectmodule.c --- a/Modules/selectmodule.c Fri Aug 02 10:22:07 2013 +0200 +++ b/Modules/selectmodule.c Sat Aug 03 00:43:52 2013 +0200 @@ -2154,7 +2154,7 @@ static struct PyModuleDef selectmodule = { PyModuleDef_HEAD_INIT, - "select", + "_select", module_doc, -1, select_methods, @@ -2168,7 +2168,7 @@ PyMODINIT_FUNC -PyInit_select(void) +PyInit__select(void) { PyObject *m; m = PyModule_Create(&selectmodule); diff -r 1287d4c9cd39 PCbuild/readme.txt --- a/PCbuild/readme.txt Fri Aug 02 10:22:07 2013 +0200 +++ b/PCbuild/readme.txt Sat Aug 03 00:43:52 2013 +0200 @@ -92,7 +92,7 @@ pyexpat Python wrapper for accelerated XML parsing, which incorporates stable code from the Expat project: http://sourceforge.net/projects/expat/ -select +_select selectmodule.c unicodedata large tables of Unicode data diff -r 1287d4c9cd39 setup.py --- a/setup.py Fri Aug 02 10:22:07 2013 +0200 +++ b/setup.py Sat Aug 03 00:43:52 2013 +0200 @@ -619,7 +619,7 @@ missing.append('spwd') # select(2); not on ancient System V - exts.append( Extension('select', ['selectmodule.c']) ) + exts.append( Extension('_select', ['selectmodule.c']) ) # Fred Drake's interface to the Python parser exts.append( Extension('parser', ['parsermodule.c']) )