Patchwork [1,of,2] selector2: vendor selector2 library

login
register
mail settings
Submitter Jun Wu
Date July 15, 2017, 3:27 a.m.
Message ID <5664763de82b48ca6882.1500089246@x1c>
Download mbox | patch
Permalink /patch/22384/
State Accepted
Headers show

Comments

Jun Wu - July 15, 2017, 3:27 a.m.
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1500088786 25200
#      Fri Jul 14 20:19:46 2017 -0700
# Node ID 5664763de82b48ca6882bbb624d01d467b4920d0
# Parent  0407a51b9d8c3ade36b6495f44897c7a70e20975
# Available At https://bitbucket.org/quark-zju/hg-draft
#              hg pull https://bitbucket.org/quark-zju/hg-draft -r 5664763de82b
selector2: vendor selector2 library

This library was a backport of the Python 3 "selectors" library. It is
useful to provide a better selector interface for Python2, to address some
issues of the plain old select.select, mentioned in the next patch.

The code [1] was ported using the MIT license, with some minor modifications
to make our test happy:

  1. "# no-check-code" was added since it's foreign code.
  2. "from __future__ import absolute_import" was added.
  3. "from collections import namedtuple, Mapping" changed to avoid direct
     symbol import.

[1]: https://github.com/SethMichaelLarson/selectors2/blob/d27dbd2fdc48331fb76ed431f44b6e6956de7f82/selectors2.py

Patch

diff --git a/mercurial/selectors2.py b/mercurial/selectors2.py
new file mode 100644
--- /dev/null
+++ b/mercurial/selectors2.py
@@ -0,0 +1,744 @@ 
+""" Back-ported, durable, and portable selectors """
+
+# MIT License
+#
+# Copyright (c) 2017 Seth Michael Larson
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# no-check-code
+
+from __future__ import absolute_import
+
+import collections
+import errno
+import math
+import platform
+import select
+import socket
+import sys
+import time
+
+namedtuple = collections.namedtuple
+Mapping = collections.Mapping
+
+try:
+    monotonic = time.monotonic
+except AttributeError:
+    monotonic = time.time
+
+__author__ = 'Seth Michael Larson'
+__email__ = 'sethmichaellarson@protonmail.com'
+__version__ = '2.0.0'
+__license__ = 'MIT'
+__url__ = 'https://www.github.com/SethMichaelLarson/selectors2'
+
+__all__ = ['EVENT_READ',
+           'EVENT_WRITE',
+           'SelectorKey',
+           'DefaultSelector',
+           'BaseSelector']
+
+EVENT_READ = (1 << 0)
+EVENT_WRITE = (1 << 1)
+_DEFAULT_SELECTOR = None
+_SYSCALL_SENTINEL = object()  # Sentinel in case a system call returns None.
+_ERROR_TYPES = (OSError, IOError, socket.error)
+
+
+SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data'])
+
+
+class _SelectorMapping(Mapping):
+    """ Mapping of file objects to selector keys """
+
+    def __init__(self, selector):
+        self._selector = selector
+
+    def __len__(self):
+        return len(self._selector._fd_to_key)
+
+    def __getitem__(self, fileobj):
+        try:
+            fd = self._selector._fileobj_lookup(fileobj)
+            return self._selector._fd_to_key[fd]
+        except KeyError:
+            raise KeyError("{0!r} is not registered.".format(fileobj))
+
+    def __iter__(self):
+        return iter(self._selector._fd_to_key)
+
+
+def _fileobj_to_fd(fileobj):
+    """ Return a file descriptor from a file object. If
+    given an integer will simply return that integer back. """
+    if isinstance(fileobj, int):
+        fd = fileobj
+    else:
+        try:
+            fd = int(fileobj.fileno())
+        except (AttributeError, TypeError, ValueError):
+            raise ValueError("Invalid file object: {0!r}".format(fileobj))
+    if fd < 0:
+        raise ValueError("Invalid file descriptor: {0}".format(fd))
+    return fd
+
+
+class BaseSelector(object):
+    """ Abstract 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 info,
+    a callback, etc.
+
+    A selector can use various implementations (select(), poll(), epoll(),
+    and kqueue()) depending on the platform. The 'DefaultSelector' class uses
+    the most efficient implementation for the current platform.
+    """
+    def __init__(self):
+        # Maps file descriptors to keys.
+        self._fd_to_key = {}
+
+        # Read-only mapping returned by get_map()
+        self._map = _SelectorMapping(self)
+
+    def _fileobj_lookup(self, fileobj):
+        """ Return a file descriptor from a file object.
+        This wraps _fileobj_to_fd() to do an exhaustive
+        search in case the object is invalid but we still
+        have it in our map. Used by unregister() so we can
+        unregister an object that was previously registered
+        even if it is closed. It is also used by _SelectorMapping
+        """
+        try:
+            return _fileobj_to_fd(fileobj)
+        except ValueError:
+
+            # Search through all our mapped keys.
+            for key in self._fd_to_key.values():
+                if key.fileobj is fileobj:
+                    return key.fd
+
+            # Raise ValueError after all.
+            raise
+
+    def register(self, fileobj, events, data=None):
+        """ Register a file object for a set of events to monitor. """
+        if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
+            raise ValueError("Invalid events: {0!r}".format(events))
+
+        key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data)
+
+        if key.fd in self._fd_to_key:
+            raise KeyError("{0!r} (FD {1}) is already registered"
+                           .format(fileobj, key.fd))
+
+        self._fd_to_key[key.fd] = key
+        return key
+
+    def unregister(self, fileobj):
+        """ Unregister a file object from being monitored. """
+        try:
+            key = self._fd_to_key.pop(self._fileobj_lookup(fileobj))
+        except KeyError:
+            raise KeyError("{0!r} is not registered".format(fileobj))
+
+        # Getting the fileno of a closed socket on Windows errors with EBADF.
+        except socket.error as err:
+            if err.errno != errno.EBADF:
+                raise
+            else:
+                for key in self._fd_to_key.values():
+                    if key.fileobj is fileobj:
+                        self._fd_to_key.pop(key.fd)
+                        break
+                else:
+                    raise KeyError("{0!r} is not registered".format(fileobj))
+        return key
+
+    def modify(self, fileobj, events, data=None):
+        """ Change a registered file object monitored events and data. """
+        # NOTE: Some subclasses optimize this operation even further.
+        try:
+            key = self._fd_to_key[self._fileobj_lookup(fileobj)]
+        except KeyError:
+            raise KeyError("{0!r} is not registered".format(fileobj))
+
+        if events != key.events:
+            self.unregister(fileobj)
+            key = self.register(fileobj, events, data)
+
+        elif data != key.data:
+            # Use a shortcut to update the data.
+            key = key._replace(data=data)
+            self._fd_to_key[key.fd] = key
+
+        return key
+
+    def select(self, timeout=None):
+        """ Perform the actual selection until some monitored file objects
+        are ready or the timeout expires. """
+        raise NotImplementedError()
+
+    def close(self):
+        """ Close the selector. This must be called to ensure that all
+        underlying resources are freed. """
+        self._fd_to_key.clear()
+        self._map = None
+
+    def get_key(self, fileobj):
+        """ Return the key associated with a registered file object. """
+        mapping = self.get_map()
+        if mapping is None:
+            raise RuntimeError("Selector is closed")
+        try:
+            return mapping[fileobj]
+        except KeyError:
+            raise KeyError("{0!r} is not registered".format(fileobj))
+
+    def get_map(self):
+        """ Return a mapping of file objects to selector keys """
+        return self._map
+
+    def _key_from_fd(self, fd):
+        """ Return the key associated to a given file descriptor
+         Return None if it is not found. """
+        try:
+            return self._fd_to_key[fd]
+        except KeyError:
+            return None
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *_):
+        self.close()
+
+
+# Almost all platforms have select.select()
+if hasattr(select, "select"):
+    class SelectSelector(BaseSelector):
+        """ Select-based selector. """
+        def __init__(self):
+            super(SelectSelector, self).__init__()
+            self._readers = set()
+            self._writers = set()
+
+        def register(self, fileobj, events, data=None):
+            key = super(SelectSelector, self).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(SelectSelector, self).unregister(fileobj)
+            self._readers.discard(key.fd)
+            self._writers.discard(key.fd)
+            return key
+
+        def select(self, timeout=None):
+            # Selecting on empty lists on Windows errors out.
+            if not len(self._readers) and not len(self._writers):
+                return []
+
+            timeout = None if timeout is None else max(timeout, 0.0)
+            ready = []
+            r, w, _ = _syscall_wrapper(self._wrap_select, True, self._readers,
+                                       self._writers, timeout)
+            r = set(r)
+            w = set(w)
+            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, events & key.events))
+            return ready
+
+        def _wrap_select(self, r, w, timeout=None):
+            """ Wrapper for select.select because timeout is a positional arg """
+            return select.select(r, w, [], timeout)
+
+    __all__.append('SelectSelector')
+
+    # Jython has a different implementation of .fileno() for socket objects.
+    if platform.system() == 'Java':
+        class _JythonSelectorMapping(object):
+            """ This is an implementation of _SelectorMapping that is built
+            for use specifically with Jython, which does not provide a hashable
+            value from socket.socket.fileno(). """
+
+            def __init__(self, selector):
+                assert isinstance(selector, JythonSelectSelector)
+                self._selector = selector
+
+            def __len__(self):
+                return len(self._selector._sockets)
+
+            def __getitem__(self, fileobj):
+                for sock, key in self._selector._sockets:
+                    if sock is fileobj:
+                        return key
+                else:
+                    raise KeyError("{0!r} is not registered.".format(fileobj))
+
+        class JythonSelectSelector(SelectSelector):
+            """ This is an implementation of SelectSelector that is for Jython
+            which works around that Jython's socket.socket.fileno() does not
+            return an integer fd value. All SelectorKey.fd will be equal to -1
+            and should not be used. This instead uses object id to compare fileobj
+            and will only use select.select as it's the only selector that allows
+            directly passing in socket objects rather than registering fds.
+            See: http://bugs.jython.org/issue1678
+                 https://wiki.python.org/jython/NewSocketModule#socket.fileno.28.29_does_not_return_an_integer
+            """
+
+            def __init__(self):
+                super(JythonSelectSelector, self).__init__()
+
+                self._sockets = []  # Uses a list of tuples instead of dictionary.
+                self._map = _JythonSelectorMapping(self)
+                self._readers = []
+                self._writers = []
+
+                # Jython has a select.cpython_compatible_select function in older versions.
+                self._select_func = getattr(select, 'cpython_compatible_select', select.select)
+
+            def register(self, fileobj, events, data=None):
+                for sock, _ in self._sockets:
+                    if sock is fileobj:
+                        raise KeyError("{0!r} is already registered"
+                                       .format(fileobj, sock))
+
+                key = SelectorKey(fileobj, -1, events, data)
+                self._sockets.append((fileobj, key))
+
+                if events & EVENT_READ:
+                    self._readers.append(fileobj)
+                if events & EVENT_WRITE:
+                    self._writers.append(fileobj)
+                return key
+
+            def unregister(self, fileobj):
+                for i, (sock, key) in enumerate(self._sockets):
+                    if sock is fileobj:
+                        break
+                else:
+                    raise KeyError("{0!r} is not registered.".format(fileobj))
+
+                if key.events & EVENT_READ:
+                    self._readers.remove(fileobj)
+                if key.events & EVENT_WRITE:
+                    self._writers.remove(fileobj)
+
+                del self._sockets[i]
+                return key
+
+            def _wrap_select(self, r, w, timeout=None):
+                """ Wrapper for select.select because timeout is a positional arg """
+                return self._select_func(r, w, [], timeout)
+
+        __all__.append('JythonSelectSelector')
+        SelectSelector = JythonSelectSelector  # Override so the wrong selector isn't used.
+
+
+if hasattr(select, "poll"):
+    class PollSelector(BaseSelector):
+        """ Poll-based selector """
+        def __init__(self):
+            super(PollSelector, self).__init__()
+            self._poll = select.poll()
+
+        def register(self, fileobj, events, data=None):
+            key = super(PollSelector, self).register(fileobj, events, data)
+            event_mask = 0
+            if events & EVENT_READ:
+                event_mask |= select.POLLIN
+            if events & EVENT_WRITE:
+                event_mask |= select.POLLOUT
+            self._poll.register(key.fd, event_mask)
+            return key
+
+        def unregister(self, fileobj):
+            key = super(PollSelector, self).unregister(fileobj)
+            self._poll.unregister(key.fd)
+            return key
+
+        def _wrap_poll(self, timeout=None):
+            """ Wrapper function for select.poll.poll() so that
+            _syscall_wrapper can work with only seconds. """
+            if timeout is not None:
+                if timeout <= 0:
+                    timeout = 0
+                else:
+                    # select.poll.poll() has a resolution of 1 millisecond,
+                    # round away from zero to wait *at least* timeout seconds.
+                    timeout = math.ceil(timeout * 1000)
+
+            result = self._poll.poll(timeout)
+            return result
+
+        def select(self, timeout=None):
+            ready = []
+            fd_events = _syscall_wrapper(self._wrap_poll, True, timeout=timeout)
+            for fd, event_mask in fd_events:
+                events = 0
+                if event_mask & ~select.POLLIN:
+                    events |= EVENT_WRITE
+                if event_mask & ~select.POLLOUT:
+                    events |= EVENT_READ
+
+                key = self._key_from_fd(fd)
+                if key:
+                    ready.append((key, events & key.events))
+
+            return ready
+
+    __all__.append('PollSelector')
+
+if hasattr(select, "epoll"):
+    class EpollSelector(BaseSelector):
+        """ Epoll-based selector """
+        def __init__(self):
+            super(EpollSelector, self).__init__()
+            self._epoll = select.epoll()
+
+        def fileno(self):
+            return self._epoll.fileno()
+
+        def register(self, fileobj, events, data=None):
+            key = super(EpollSelector, self).register(fileobj, events, data)
+            events_mask = 0
+            if events & EVENT_READ:
+                events_mask |= select.EPOLLIN
+            if events & EVENT_WRITE:
+                events_mask |= select.EPOLLOUT
+            _syscall_wrapper(self._epoll.register, False, key.fd, events_mask)
+            return key
+
+        def unregister(self, fileobj):
+            key = super(EpollSelector, self).unregister(fileobj)
+            try:
+                _syscall_wrapper(self._epoll.unregister, False, key.fd)
+            except _ERROR_TYPES:
+                # This can occur when the fd was closed since registry.
+                pass
+            return key
+
+        def select(self, timeout=None):
+            if timeout is not None:
+                if timeout <= 0:
+                    timeout = 0.0
+                else:
+                    # select.epoll.poll() has a resolution of 1 millisecond
+                    # but luckily takes seconds so we don't need a wrapper
+                    # like PollSelector. Just for better rounding.
+                    timeout = math.ceil(timeout * 1000) * 0.001
+                timeout = float(timeout)
+            else:
+                timeout = -1.0  # epoll.poll() must have a float.
+
+            # We always want at least 1 to ensure that select can be called
+            # with no file descriptors registered. Otherwise will fail.
+            max_events = max(len(self._fd_to_key), 1)
+
+            ready = []
+            fd_events = _syscall_wrapper(self._epoll.poll, True,
+                                         timeout=timeout,
+                                         maxevents=max_events)
+            for fd, event_mask in fd_events:
+                events = 0
+                if event_mask & ~select.EPOLLIN:
+                    events |= EVENT_WRITE
+                if event_mask & ~select.EPOLLOUT:
+                    events |= EVENT_READ
+
+                key = self._key_from_fd(fd)
+                if key:
+                    ready.append((key, events & key.events))
+            return ready
+
+        def close(self):
+            self._epoll.close()
+            super(EpollSelector, self).close()
+
+    __all__.append('EpollSelector')
+
+
+if hasattr(select, "devpoll"):
+    class DevpollSelector(BaseSelector):
+        """Solaris /dev/poll selector."""
+
+        def __init__(self):
+            super(DevpollSelector, self).__init__()
+            self._devpoll = select.devpoll()
+
+        def fileno(self):
+            return self._devpoll.fileno()
+
+        def register(self, fileobj, events, data=None):
+            key = super(DevpollSelector, self).register(fileobj, events, data)
+            poll_events = 0
+            if events & EVENT_READ:
+                poll_events |= select.POLLIN
+            if events & EVENT_WRITE:
+                poll_events |= select.POLLOUT
+            self._devpoll.register(key.fd, poll_events)
+            return key
+
+        def unregister(self, fileobj):
+            key = super(DevpollSelector, self).unregister(fileobj)
+            self._devpoll.unregister(key.fd)
+            return key
+
+        def _wrap_poll(self, timeout=None):
+            """ Wrapper function for select.poll.poll() so that
+            _syscall_wrapper can work with only seconds. """
+            if timeout is not None:
+                if timeout <= 0:
+                    timeout = 0
+                else:
+                    # select.devpoll.poll() has a resolution of 1 millisecond,
+                    # round away from zero to wait *at least* timeout seconds.
+                    timeout = math.ceil(timeout * 1000)
+
+            result = self._devpoll.poll(timeout)
+            return result
+
+        def select(self, timeout=None):
+            ready = []
+            fd_events = _syscall_wrapper(self._wrap_poll, True, timeout=timeout)
+            for fd, event_mask in fd_events:
+                events = 0
+                if event_mask & ~select.POLLIN:
+                    events |= EVENT_WRITE
+                if event_mask & ~select.POLLOUT:
+                    events |= EVENT_READ
+
+                key = self._key_from_fd(fd)
+                if key:
+                    ready.append((key, events & key.events))
+
+            return ready
+
+        def close(self):
+            self._devpoll.close()
+            super(DevpollSelector, self).close()
+
+    __all__.append('DevpollSelector')
+
+
+if hasattr(select, "kqueue"):
+    class KqueueSelector(BaseSelector):
+        """ Kqueue / Kevent-based selector """
+        def __init__(self):
+            super(KqueueSelector, self).__init__()
+            self._kqueue = select.kqueue()
+
+        def fileno(self):
+            return self._kqueue.fileno()
+
+        def register(self, fileobj, events, data=None):
+            key = super(KqueueSelector, self).register(fileobj, events, data)
+            if events & EVENT_READ:
+                kevent = select.kevent(key.fd,
+                                       select.KQ_FILTER_READ,
+                                       select.KQ_EV_ADD)
+
+                _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0)
+
+            if events & EVENT_WRITE:
+                kevent = select.kevent(key.fd,
+                                       select.KQ_FILTER_WRITE,
+                                       select.KQ_EV_ADD)
+
+                _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0)
+
+            return key
+
+        def unregister(self, fileobj):
+            key = super(KqueueSelector, self).unregister(fileobj)
+            if key.events & EVENT_READ:
+                kevent = select.kevent(key.fd,
+                                       select.KQ_FILTER_READ,
+                                       select.KQ_EV_DELETE)
+                try:
+                    _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0)
+                except _ERROR_TYPES:
+                    pass
+            if key.events & EVENT_WRITE:
+                kevent = select.kevent(key.fd,
+                                       select.KQ_FILTER_WRITE,
+                                       select.KQ_EV_DELETE)
+                try:
+                    _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0)
+                except _ERROR_TYPES:
+                    pass
+
+            return key
+
+        def select(self, timeout=None):
+            if timeout is not None:
+                timeout = max(timeout, 0)
+
+            max_events = len(self._fd_to_key) * 2
+            ready_fds = {}
+
+            kevent_list = _syscall_wrapper(self._kqueue.control, True,
+                                           None, max_events, timeout)
+
+            for kevent in kevent_list:
+                fd = kevent.ident
+                event_mask = kevent.filter
+                events = 0
+                if event_mask == select.KQ_FILTER_READ:
+                    events |= EVENT_READ
+                if event_mask == select.KQ_FILTER_WRITE:
+                    events |= EVENT_WRITE
+
+                key = self._key_from_fd(fd)
+                if key:
+                    if key.fd not in ready_fds:
+                        ready_fds[key.fd] = (key, events & key.events)
+                    else:
+                        old_events = ready_fds[key.fd][1]
+                        ready_fds[key.fd] = (key, (events | old_events) & key.events)
+
+            return list(ready_fds.values())
+
+        def close(self):
+            self._kqueue.close()
+            super(KqueueSelector, self).close()
+
+    __all__.append('KqueueSelector')
+
+
+def _can_allocate(struct):
+    """ Checks that select structs can be allocated by the underlying
+    operating system, not just advertised by the select module. We don't
+    check select() because we'll be hopeful that most platforms that
+    don't have it available will not advertise it. (ie: GAE) """
+    try:
+        # select.poll() objects won't fail until used.
+        if struct == 'poll':
+            p = select.poll()
+            p.poll(0)
+
+        # All others will fail on allocation.
+        else:
+            getattr(select, struct)().close()
+        return True
+    except (OSError, AttributeError):
+        return False
+
+
+# Python 3.5 uses a more direct route to wrap system calls to increase speed.
+if sys.version_info >= (3, 5):
+    def _syscall_wrapper(func, _, *args, **kwargs):
+        """ This is the short-circuit version of the below logic
+        because in Python 3.5+ all selectors restart system calls. """
+        return func(*args, **kwargs)
+else:
+    def _syscall_wrapper(func, recalc_timeout, *args, **kwargs):
+        """ Wrapper function for syscalls that could fail due to EINTR.
+        All functions should be retried if there is time left in the timeout
+        in accordance with PEP 475. """
+        timeout = kwargs.get("timeout", None)
+        if timeout is None:
+            expires = None
+            recalc_timeout = False
+        else:
+            timeout = float(timeout)
+            if timeout < 0.0:  # Timeout less than 0 treated as no timeout.
+                expires = None
+            else:
+                expires = monotonic() + timeout
+
+        args = list(args)
+        if recalc_timeout and "timeout" not in kwargs:
+            raise ValueError(
+                "Timeout must be in args or kwargs to be recalculated")
+
+        result = _SYSCALL_SENTINEL
+        while result is _SYSCALL_SENTINEL:
+            try:
+                result = func(*args, **kwargs)
+            # OSError is thrown by select.select
+            # IOError is thrown by select.epoll.poll
+            # select.error is thrown by select.poll.poll
+            # Aren't we thankful for Python 3.x rework for exceptions?
+            except (OSError, IOError, select.error) as e:
+                # select.error wasn't a subclass of OSError in the past.
+                errcode = None
+                if hasattr(e, "errno"):
+                    errcode = e.errno
+                elif hasattr(e, "args"):
+                    errcode = e.args[0]
+
+                # Also test for the Windows equivalent of EINTR.
+                is_interrupt = (errcode == errno.EINTR or (hasattr(errno, "WSAEINTR") and
+                                                           errcode == errno.WSAEINTR))
+
+                if is_interrupt:
+                    if expires is not None:
+                        current_time = monotonic()
+                        if current_time > expires:
+                            raise OSError(errno=errno.ETIMEDOUT)
+                        if recalc_timeout:
+                            if "timeout" in kwargs:
+                                kwargs["timeout"] = expires - current_time
+                    continue
+                raise
+        return result
+
+
+# Choose the best implementation, roughly:
+# kqueue == devpoll == epoll > poll > select
+# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
+def DefaultSelector():
+    """ This function serves as a first call for DefaultSelector to
+    detect if the select module is being monkey-patched incorrectly
+    by eventlet, greenlet, and preserve proper behavior. """
+    global _DEFAULT_SELECTOR
+    if _DEFAULT_SELECTOR is None:
+        if platform.system() == 'Java':  # Platform-specific: Jython
+            _DEFAULT_SELECTOR = JythonSelectSelector
+        elif _can_allocate('kqueue'):
+            _DEFAULT_SELECTOR = KqueueSelector
+        elif _can_allocate('devpoll'):
+            _DEFAULT_SELECTOR = DevpollSelector
+        elif _can_allocate('epoll'):
+            _DEFAULT_SELECTOR = EpollSelector
+        elif _can_allocate('poll'):
+            _DEFAULT_SELECTOR = PollSelector
+        elif hasattr(select, 'select'):
+            _DEFAULT_SELECTOR = SelectSelector
+        else:  # Platform-specific: AppEngine
+            raise RuntimeError('Platform does not have a selector.')
+    return _DEFAULT_SELECTOR()
diff --git a/tests/test-check-code.t b/tests/test-check-code.t
--- a/tests/test-check-code.t
+++ b/tests/test-check-code.t
@@ -14,4 +14,5 @@  New errors are not allowed. Warnings are
   Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
+  Skipping mercurial/selectors2.py it has no-che?k-code (glob)
   Skipping mercurial/statprof.py it has no-che?k-code (glob)
   Skipping tests/badserverext.py it has no-che?k-code (glob)