diff --git a/Lib/select.py b/Lib/select.py
new file mode 100644
--- /dev/null
+++ b/Lib/select.py
@@ -0,0 +1,313 @@
+"""Select module.
+
+This module supports asynchronous I/O on multiple file descriptors.
+"""
+
+
+from abc import ABCMeta, abstractmethod
+import _select
+from _select import *
+import threading
+
+
+# genereric events, that must be mapped to implementation-specific ones
+SELECT_IN = (1 << 0)
+SELECT_OUT = (1 << 1)
+SELECT_ERR = (1 << 2)
+
+
+class _AbstractSelector(metaclass=ABCMeta):
+ """Abstract base selector class.
+
+ A selector supports registering and unregistering file descriptors, and then
+ polling them for I/O events. 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.
+
+ Derived classes must implement implementation-specific abstract methods.
+
+ Notes:
+ - A Selector accepts both file descriptors and objects with a `fileno()`
+ method. The mapping between file objects and the corresponding file
+ descriptor is done by the public methods.
+ - The conversion between generic events and implementation-specific ones is
+ performed by implementation-specific methods (mainly because select()
+ interface is really different from all other implementations).
+ - The public methods perform the necessary synchronization, so derived
+ classes don't have to worry about it.
+ """
+
+ def __init__(self):
+ # this maps file-descriptor to file object
+ self._fds_map = {}
+ # this lock guards the selector against concurrent access
+ self._lock = threading.Lock()
+
+ def register(self, fileobj, eventmask):
+ """Register a file object.
+
+ Parameters:
+ fileobj -- file descriptor, or any object with a `fileno()` method
+ eventmask -- bitwise mask of events to monitor
+ """
+ # find the corresponding FD
+ fd = self._file_to_fd(fileobj)
+ with self._lock:
+ # store it, and call implementation-specific register
+ self._fds_map[fd] = fileobj
+ self._register(fd, eventmask)
+
+ @abstractmethod
+ def _register(self, fd, eventmask):
+ pass
+
+ def unregister(self, fileobj):
+ """Unregister a file object.
+
+ Parameters:
+ fileobj -- file descriptor, or any object with a `fileno()` method
+ """
+ # find the corresponding FD
+ fd = self._file_to_fd(fileobj)
+ with self._lock:
+ # check it's registered, and call implementation-specific unregister
+ try:
+ del self._fds_map[fd]
+ except KeyError:
+ raise ValueError("Cannot unregister non registered file: "
+ "{!r}".format(fileobj))
+ self._unregister(fd)
+
+ @abstractmethod
+ def _unregister(self, fd):
+ pass
+
+ def modify(self, fileobj, eventmask):
+ """Change the events to monitor for a given file object.
+
+ Parameters:
+ fileobj -- file descriptor, or any object with a `fileno()` method
+ """
+ # find the corresponding FD
+ fd = self._file_to_fd(fileobj)
+ with self._lock:
+ # check it's registered, and call implementation-specific modify
+ if not fd in self._fds_map:
+ raise ValueError("Cannot modify non registered file: "
+ "{!r}".format(fileobj))
+ self._modify(fd, eventmask)
+
+ @abstractmethod
+ def _modify(self, fd, eventmask):
+ pass
+
+ 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 files
+ if timeout is None, select() will block until a monitored
+ file descriptor becomes ready
+
+ Returns:
+ set of (fileobj, event mask) for ready file object
+ """
+ if not (timeout is None or timeout >= 0):
+ raise ValueError("timeout must be None or non-negative: "
+ "{!r}".format(timeout))
+
+ with self._lock:
+ # call implementation-specific select
+ ready = self._select(timeout)
+ # and convert FD to file objects
+ return set((self._fds_map.get(fd, fd), evt) for fd, evt in ready)
+
+ @abstractmethod
+ def _select(self, timeout):
+ pass
+
+ def close(self):
+ """Close the selector.
+
+ This must be called to make sure that any underlying resource is freed.
+ """
+ with self._lock:
+ del self._fds_map
+ self._close()
+
+ @abstractmethod
+ def _close(self):
+ pass
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
+ @staticmethod
+ def _file_to_fd(fileobj):
+ """Helper method that returns the file descriptor for a given file
+ object.
+
+ Parameters:
+ fileobj -- file descriptor, or any object with a `fileno()` method
+
+ Returns:
+ corresponding FD
+ """
+ if isinstance(fileobj, int):
+ fd = fileobj
+ elif hasattr(fileobj, 'fileno'):
+ fd = fileobj.fileno()
+ else:
+ raise ValueError("Invalid file object: {!r}".format(fileobj))
+ return fd
+
+
+def _map_events(mapping, mask):
+ """Helper method to convert between generic and specific events masks."""
+ new_mask = 0
+ for source, target in mapping.items():
+ if source & mask:
+ new_mask |= target
+ return new_mask
+
+
+class SelectSelector(_AbstractSelector):
+ """Select-based selector."""
+
+ def __init__(self):
+ _AbstractSelector.__init__(self)
+ self._rset = set()
+ self._wset = set()
+ self._eset = set()
+
+ def _register(self, fd, eventmask):
+ if eventmask & SELECT_IN:
+ self._rset.add(fd)
+ if eventmask & SELECT_OUT:
+ self._wset.add(fd)
+ if eventmask & SELECT_ERR:
+ self._eset.add(fd)
+
+ def _unregister(self, fd):
+ self._rset.discard(fd)
+ self._wset.discard(fd)
+ self._eset.discard(fd)
+
+ def _modify(self, fd, eventmask):
+ self._unregister(fd)
+ self._register(fd, eventmask)
+
+ def _select(self, timeout=None):
+ rset, wset, eset = select(self._rset, self._wset, self._eset, timeout)
+
+ events = set()
+ for fd in rset:
+ events.add((fd, SELECT_IN))
+ for fd in wset:
+ events.add((fd, SELECT_OUT))
+ for fd in eset:
+ events.add((fd, SELECT_ERR))
+ return events
+
+ def _close(self):
+ pass
+
+
+if hasattr(_select, 'poll'):
+
+ class PollSelector(_AbstractSelector):
+ """Poll-based selector."""
+
+ _generic_to_specific_events = {SELECT_IN: POLLIN|POLLPRI,
+ SELECT_OUT: POLLOUT
+ }
+
+ _specific_to_generic_events = {POLLIN: SELECT_IN,
+ POLLPRI: SELECT_IN,
+ POLLOUT: SELECT_OUT,
+ POLLHUP: SELECT_IN,
+ POLLNVAL: SELECT_ERR,
+ POLLERR: SELECT_ERR,
+ }
+
+ def __init__(self):
+ _AbstractSelector.__init__(self)
+ self._poll = _select.poll()
+
+ def _register(self, fd, eventmask):
+ eventmask = _map_events(self._generic_to_specific_events, eventmask)
+ self._poll.register(fd, eventmask)
+
+ def _unregister(self, fd):
+ self._poll.unregister(fd)
+
+ def _modify(self, fd, eventmask):
+ eventmask = _map_events(self._generic_to_specific_events, eventmask)
+ self._poll.modify(fd, eventmask)
+
+ def _select(self, timeout=None):
+ events = self._poll.poll(None if timeout is None else 1000 * timeout)
+ return ((fd, _map_events(self._specific_to_generic_events, evt))
+ for fd, evt in events)
+
+ def _close(self):
+ pass
+
+
+if hasattr(_select, 'epoll'):
+
+ class EpollSelector(_AbstractSelector):
+ """Epoll-based selector."""
+
+ _generic_to_specific_events = {SELECT_IN: EPOLLIN|EPOLLPRI,
+ SELECT_OUT: EPOLLOUT
+ }
+
+ _specific_to_generic_events = {EPOLLIN: SELECT_IN,
+ EPOLLPRI: SELECT_IN,
+ EPOLLOUT: SELECT_OUT,
+ EPOLLHUP: SELECT_IN,
+ EPOLLERR: SELECT_ERR,
+ }
+
+ def __init__(self):
+ _AbstractSelector.__init__(self)
+ self._epoll = _select.epoll()
+
+ def _register(self, fd, eventmask):
+ eventmask = _map_events(self._generic_to_specific_events, eventmask)
+ self._epoll.register(fd, eventmask)
+
+ def _unregister(self, fd):
+ self._epoll.unregister(fd)
+
+ def _modify(self, fd, eventmask):
+ eventmask = _map_events(self._generic_to_specific_events, eventmask)
+ self._epoll.modify(fd, eventmask)
+
+ def _select(self, timeout):
+ events = self._epoll.poll(-1 if timeout is None else timeout)
+ return ((fd, _map_events(self._specific_to_generic_events, evt))
+ for fd, evt in events)
+
+ def _close(self):
+ self._epoll.close()
+
+
+if hasattr(_select, 'epoll'):
+ # complexity is O(