Skip to content
6 changes: 6 additions & 0 deletions Doc/library/selectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ constants below:
+-----------------------+-----------------------------------------------+
| :const:`EVENT_WRITE` | Available for write |
+-----------------------+-----------------------------------------------+
| :const:`EVENT_URGENT` | Available for urgent data to read |
| | (not supported by KqueueSelector) |
+-----------------------+-----------------------------------------------+

.. versionchanged:: 3.7
Added :const:`EVENT_URGENT`.


.. class:: SelectorKey
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@ The flags :const:`re.ASCII`, :const:`re.LOCALE` and :const:`re.UNICODE`
can be set within the scope of a group.
(Contributed by Serhiy Storchaka in :issue:`31690`.)

selectors
---------

Added :const:`~selectors.EVENT_URGENT` module constant for urgent data to
read event support. (Contributed by Pim Klanke in :issue:`30844`.)

string
------

Expand Down
173 changes: 133 additions & 40 deletions Lib/selectors.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Selectors module.

This module allows high-level and efficient I/O multiplexing, built upon the
`select` module primitives.
This module allows high-level and efficient I/O multiplexing, built upon
the `select` module primitives.
"""


Expand All @@ -16,6 +16,7 @@
# generic events, that must be mapped to implementation-specific ones
EVENT_READ = (1 << 0)
EVENT_WRITE = (1 << 1)
EVENT_URGENT = (1 << 2)


def _fileobj_to_fd(fileobj):
Expand Down Expand Up @@ -50,12 +51,16 @@ def _fileobj_to_fd(fileobj):
Object used to associate a file object to its backing
file descriptor, selected event mask, and attached data.
"""
if sys.version_info >= (3, 5):
SelectorKey.fileobj.__doc__ = 'File object registered.'
SelectorKey.fd.__doc__ = 'Underlying file descriptor.'
SelectorKey.events.__doc__ = 'Events that must be waited for on this file object.'
SelectorKey.data.__doc__ = ('''Optional opaque data associated to this file object.
For example, this could be used to store a per-client session ID.''')
SelectorKey.fileobj.__doc__ = 'File object registered.'
SelectorKey.fd.__doc__ = 'Underlying file descriptor.'
SelectorKey.events.__doc__ = (
'Events that must be waited for on this file object.'
)
SelectorKey.data.__doc__ = (
"""Optional opaque data associated to this file object.

For example, this could be used to store a per-client session ID.
""")

class _SelectorMapping(Mapping):
"""Mapping of file objects to selector keys."""
Expand All @@ -80,16 +85,17 @@ def __iter__(self):
class BaseSelector(metaclass=ABCMeta):
"""Selector abstract base class.

A selector supports registering file objects to be monitored for specific
I/O events.
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 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
efficient implementation on the current platform.
A selector can use various implementations (select(), poll(),
epoll()...) depending on the platform. The default `Selector` class
uses the most efficient implementation on the current platform.
"""

@abstractmethod
Expand All @@ -98,7 +104,8 @@ def register(self, fileobj, events, data=None):

Parameters:
fileobj -- file object or file descriptor
events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)
events -- events to monitor
(bitwise mask of EVENT_READ|EVENT_WRITE|EVENT_URGENT)
data -- attached data

Returns:
Expand Down Expand Up @@ -135,11 +142,13 @@ def unregister(self, fileobj):
raise NotImplementedError

def modify(self, fileobj, events, data=None):
"""Change a registered file object monitored events or attached data.
"""Change a registered file object monitored events or attached
data.

Parameters:
fileobj -- file object or file descriptor
events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)
events -- events to monitor
(bitwise mask of EVENT_READ|EVENT_WRITE|EVENT_URGENT)
data -- attached data

Returns:
Expand All @@ -153,27 +162,28 @@ def modify(self, fileobj, events, data=None):

@abstractmethod
def select(self, timeout=None):
"""Perform the actual selection, until some monitored file objects are
ready or a timeout expires.
"""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
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 (key, events) for ready file objects
`events` is a bitwise mask of EVENT_READ|EVENT_WRITE
list of (key, events) for ready file objects. `events` is a
bitwise mask of EVENT_READ|EVENT_WRITE|EVENT_URGENT
"""
raise NotImplementedError

def close(self):
"""Close the selector.

This must be called to make sure that any underlying resource is freed.
This must be called to make sure that any underlying resource is
freed.
"""
pass

Expand Down Expand Up @@ -205,6 +215,7 @@ def __exit__(self, *args):

class _BaseSelectorImpl(BaseSelector):
"""Base selector implementation."""
_ACCEPTED_EVENTS = (EVENT_READ | EVENT_WRITE | EVENT_URGENT)

def __init__(self):
# this maps file descriptors to keys
Expand Down Expand Up @@ -232,7 +243,7 @@ def _fileobj_lookup(self, fileobj):
raise

def register(self, fileobj, events, data=None):
if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
if (not events) or (events & ~self._ACCEPTED_EVENTS):
raise ValueError("Invalid events: {!r}".format(events))

key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data)
Expand Down Expand Up @@ -294,43 +305,53 @@ def __init__(self):
super().__init__()
self._readers = set()
self._writers = set()
self._urgents = 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)
if events & EVENT_URGENT:
self._urgents.add(key.fd)
return key

def unregister(self, fileobj):
key = super().unregister(fileobj)
self._readers.discard(key.fd)
self._writers.discard(key.fd)
self._urgents.discard(key.fd)
return key

if sys.platform == 'win32':
def _select(self, r, w, _, timeout=None):
r, w, x = select.select(r, w, w, timeout)
return r, w + x, []
def _select(self, r, w, x, timeout=None):
r, _w, _x = select.select(r, w, w|x, timeout)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add a comment explaining the w|x thing as well as the _w+_x below.

w = [fd for fd in _w+_x if fd in w]
x = [fd for fd in _x if fd in x]
return r, w, x
else:
_select = select.select

def select(self, timeout=None):
timeout = None if timeout is None else max(timeout, 0)
ready = []
try:
r, w, _ = self._select(self._readers, self._writers, [], timeout)
r, w, u = self._select(self._readers, self._writers, self._urgents,
timeout)
except InterruptedError:
return ready
r = set(r)
w = set(w)
for fd in r | w:
u = set(u)
for fd in r | w | u:
events = 0
if fd in r:
events |= EVENT_READ
if fd in w:
events |= EVENT_WRITE
if fd in u:
events |= EVENT_URGENT

key = self._key_from_fd(fd)
if key:
Expand All @@ -343,6 +364,7 @@ class _PollLikeSelector(_BaseSelectorImpl):
_selector_cls = None
_EVENT_READ = None
_EVENT_WRITE = None
_EVENT_URGENT = None

def __init__(self):
super().__init__()
Expand All @@ -355,6 +377,8 @@ def register(self, fileobj, events, data=None):
poller_events |= self._EVENT_READ
if events & EVENT_WRITE:
poller_events |= self._EVENT_WRITE
if events & EVENT_URGENT:
poller_events |= self._EVENT_URGENT
try:
self._selector.register(key.fd, poller_events)
except:
Expand Down Expand Up @@ -385,6 +409,8 @@ def modify(self, fileobj, events, data=None):
selector_events |= self._EVENT_READ
if events & EVENT_WRITE:
selector_events |= self._EVENT_WRITE
if events & EVENT_URGENT:
selector_events |= self._EVENT_URGENT
try:
self._selector.modify(key.fd, selector_events)
except:
Expand Down Expand Up @@ -415,11 +441,16 @@ def select(self, timeout=None):
fd_event_list = self._selector.poll(timeout)
except InterruptedError:
return ready
flags_urgent = ~(self._EVENT_READ | self._EVENT_WRITE)
flags_write = ~(self._EVENT_READ | self._EVENT_URGENT)
flags_read = ~(self._EVENT_WRITE | self._EVENT_URGENT)
for fd, event in fd_event_list:
events = 0
if event & ~self._EVENT_READ:
if event & flags_urgent:
events |= EVENT_URGENT
if event & flags_write:
events |= EVENT_WRITE
if event & ~self._EVENT_WRITE:
if event & flags_read:
events |= EVENT_READ

key = self._key_from_fd(fd)
Expand All @@ -435,6 +466,7 @@ class PollSelector(_PollLikeSelector):
_selector_cls = select.poll
_EVENT_READ = select.POLLIN
_EVENT_WRITE = select.POLLOUT
_EVENT_URGENT = select.POLLPRI


if hasattr(select, 'epoll'):
Expand All @@ -444,6 +476,7 @@ class EpollSelector(_PollLikeSelector):
_selector_cls = select.epoll
_EVENT_READ = select.EPOLLIN
_EVENT_WRITE = select.EPOLLOUT
_EVENT_URGENT = select.EPOLLPRI

def fileno(self):
return self._selector.fileno()
Expand All @@ -468,11 +501,16 @@ def select(self, timeout=None):
fd_event_list = self._selector.poll(timeout, max_ev)
except InterruptedError:
return ready
flags_urgent = ~(self._EVENT_READ | self._EVENT_WRITE)
flags_write = ~(self._EVENT_READ | self._EVENT_URGENT)
flags_read = ~(self._EVENT_WRITE | self._EVENT_URGENT)
for fd, event in fd_event_list:
events = 0
if event & ~select.EPOLLIN:
if event & flags_urgent:
events |= EVENT_URGENT
if event & flags_write:
events |= EVENT_WRITE
if event & ~select.EPOLLOUT:
if event & flags_read:
events |= EVENT_READ

key = self._key_from_fd(fd)
Expand All @@ -492,6 +530,7 @@ class DevpollSelector(_PollLikeSelector):
_selector_cls = select.devpoll
_EVENT_READ = select.POLLIN
_EVENT_WRITE = select.POLLOUT
_EVENT_URGENT = select.POLLPRI

def fileno(self):
return self._selector.fileno()
Expand All @@ -505,6 +544,7 @@ def close(self):

class KqueueSelector(_BaseSelectorImpl):
"""Kqueue-based selector."""
_ACCEPTED_EVENTS = (EVENT_READ | EVENT_WRITE)

def __init__(self):
super().__init__()
Expand All @@ -514,6 +554,26 @@ def fileno(self):
return self._selector.fileno()

def register(self, fileobj, events, data=None):
"""Register a file object.

Parameters:
fileobj -- file object or file descriptor
events -- events to monitor
(bitwise mask of EVENT_READ|EVENT_WRITE)
data -- attached data

Returns:
SelectorKey instance

Raises:
ValueError if events is invalid
KeyError if fileobj is already registered
OSError if fileobj is closed or otherwise is unacceptable to
the underlying system call (if a system call is made)

Note:
OSError may or may not be raised
"""
key = super().register(fileobj, events, data)
try:
if events & EVENT_READ:
Expand Down Expand Up @@ -550,7 +610,40 @@ def unregister(self, fileobj):
pass
return key

def modify(self, fileobj, events, data=None):
"""Change a registered file object monitored events or
attached data.

Parameters:
fileobj -- file object or file descriptor
events -- events to monitor
(bitwise mask of EVENT_READ|EVENT_WRITE)
data -- attached data

Returns:
SelectorKey instance

Raises:
Anything that unregister() or register() raises
"""
return super().modify(fileobj, events, data)

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 (key, events) for ready file objects
`events` is a bitwise mask of EVENT_READ|EVENT_WRITE
"""
timeout = None if timeout is None else max(timeout, 0)
max_ev = len(self._fd_to_key)
ready = []
Expand Down
Loading