Skip to content

bpo-44951: Allow setting EPOLLEXCLUSIVE on selectors.EpollSelector #27819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Doc/library/selectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,26 @@ constants below:

:func:`select.epoll`-based selector.

.. attribute:: exclusive

Check if *EPOLLEXCLUSIVE* will be set on newly registered
files.

.. method:: fileno()

This returns the file descriptor used by the underlying
:func:`select.epoll` object.

.. method:: set_exclusive(value)

Enable or disable setting *EPOLLEXCLUSIVE* on newly
registered files. A file descriptor with *EPOLLEXCLUSIVE* set on it
will raise :exc:`OSError` if you attempt to
:meth:`~BaseSelector.modify` it. You must unregister and
re-register a file descriptor to enable or disable *EPOLLEXCLUSIVE*.

*value* must be a boolean.

.. class:: DevpollSelector()

:func:`select.devpoll`-based selector.
Expand Down
34 changes: 29 additions & 5 deletions Lib/selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,15 +348,16 @@ def __init__(self):
super().__init__()
self._selector = self._selector_cls()

def register(self, fileobj, events, data=None):
def register(self, fileobj, events, data=None, register_flags=None):
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is in a method literally called register, I'd name the new argument simply flags.

key = super().register(fileobj, events, data)
poller_events = 0
if register_flags is None:
register_flags = 0
if events & EVENT_READ:
poller_events |= self._EVENT_READ
register_flags |= self._EVENT_READ
if events & EVENT_WRITE:
poller_events |= self._EVENT_WRITE
register_flags |= self._EVENT_WRITE
try:
self._selector.register(key.fd, poller_events)
self._selector.register(key.fd, register_flags)
except:
super().unregister(fileobj)
raise
Expand Down Expand Up @@ -445,6 +446,29 @@ class EpollSelector(_PollLikeSelector):
_EVENT_READ = select.EPOLLIN
_EVENT_WRITE = select.EPOLLOUT

def __init__(self):
super().__init__()
self._exclusive = False

@property
def exclusive(self):
return self._exclusive

def set_exclusive(self, value):
Copy link
Contributor

Choose a reason for hiding this comment

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

So far selectors haven't had this but looking at asyncio, ssl, socket, threading, and multiprocessing code, we usually either use properties for both getting and setting a value, or we use both a get_* and a set_* method.

Since we already have get_key and get_map in selectors.py, and there are no public fields nor properties here, I'd change the exclusive getter to an explicit get_exclusive() method.

if not isinstance(value, bool):
raise ValueError("Not a boolean")
if not hasattr(select, "EPOLLEXCLUSIVE"):
raise ValueError("Python was not compiled "
"with EPOLLEXCLUSIVE")
self._exclusive = value

def register(self, fileobj, events, data=None):
register_flags = 0
if self._exclusive:
register_flags |= select.EPOLLEXCLUSIVE
return super().register(fileobj, events,
data=data, register_flags=register_flags)

def fileno(self):
return self._selector.fileno()

Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_selectors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import errno
import os
import random
import select
import selectors
import signal
import socket
Expand Down Expand Up @@ -533,6 +534,25 @@ def test_register_file(self):
s.get_key(f)


@unittest.skipUnless(hasattr(selectors, 'EpollSelector'),
"Test needs selectors.EpollSelector")
@unittest.skipUnless(hasattr(select, 'EPOLLEXCLUSIVE'),
"Test needs select.EPOLLEXCLUSIVE")
class EpollExclusiveSelectorTestCase(BaseSelectorTestCase,
ScalableSelectorMixIn):

@property
def SELECTOR(self):
sel = getattr(selectors, 'EpollSelector')()
sel.set_exclusive(True)
return lambda: sel

def test_modify(self):
with self.assertRaises(OSError) as ex:
super().test_modify()
self.assertEqual(ex.exception.errno, errno.EINVAL)


@unittest.skipUnless(hasattr(selectors, 'KqueueSelector'),
"Test needs selectors.KqueueSelector)")
class KqueueSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn,
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ Subhendu Ghosh
Jonathan Giddy
Johannes Gijsbers
Michael Gilfix
David Gilman
Julian Gindi
Yannick Gingras
Neil Girdhar
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`selectors.EpollSelector.set_exclusive` added to enable setting *EPOLLEXCLUSIVE* automatically on polled file descriptors.