bpo-30014: make poll-like selector's modify() method faster (#1030)
* #30014: make selectors.DefaultSelector.modify() faster by relying on selector's modify() method instead of un/register()ing the fd * #30014: add unit test * speedup poll/epoll/devpoll modify() method by using internal modify() call * update doc * address PR comments * update NEWS entries * use != instead of 'is not'
This commit is contained in:
parent
894a654a9c
commit
fbfaa6fd57
|
@ -234,6 +234,9 @@ Optimizations
|
||||||
expressions <re>`. Searching some patterns can now be up to 20 times faster.
|
expressions <re>`. Searching some patterns can now be up to 20 times faster.
|
||||||
(Contributed by Serhiy Storchaka in :issue:`30285`.)
|
(Contributed by Serhiy Storchaka in :issue:`30285`.)
|
||||||
|
|
||||||
|
* :meth:`selectors.EpollSelector.modify`, :meth:`selectors.PollSelector.modify`
|
||||||
|
and :meth:`selectors.DevpollSelector.modify` may be around 10% faster under
|
||||||
|
heavy loads. (Contributed by Giampaolo Rodola' in :issue:`30014`)
|
||||||
|
|
||||||
Build and C API Changes
|
Build and C API Changes
|
||||||
=======================
|
=======================
|
||||||
|
|
|
@ -252,7 +252,6 @@ class _BaseSelectorImpl(BaseSelector):
|
||||||
return key
|
return key
|
||||||
|
|
||||||
def modify(self, fileobj, events, data=None):
|
def modify(self, fileobj, events, data=None):
|
||||||
# TODO: Subclasses can probably optimize this even further.
|
|
||||||
try:
|
try:
|
||||||
key = self._fd_to_key[self._fileobj_lookup(fileobj)]
|
key = self._fd_to_key[self._fileobj_lookup(fileobj)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -342,6 +341,8 @@ class SelectSelector(_BaseSelectorImpl):
|
||||||
class _PollLikeSelector(_BaseSelectorImpl):
|
class _PollLikeSelector(_BaseSelectorImpl):
|
||||||
"""Base class shared between poll, epoll and devpoll selectors."""
|
"""Base class shared between poll, epoll and devpoll selectors."""
|
||||||
_selector_cls = None
|
_selector_cls = None
|
||||||
|
_EVENT_READ = None
|
||||||
|
_EVENT_WRITE = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -371,6 +372,33 @@ class _PollLikeSelector(_BaseSelectorImpl):
|
||||||
pass
|
pass
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
def modify(self, fileobj, events, data=None):
|
||||||
|
try:
|
||||||
|
key = self._fd_to_key[self._fileobj_lookup(fileobj)]
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(f"{fileobj!r} is not registered") from None
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
if events != key.events:
|
||||||
|
selector_events = 0
|
||||||
|
if events & EVENT_READ:
|
||||||
|
selector_events |= self._EVENT_READ
|
||||||
|
if events & EVENT_WRITE:
|
||||||
|
selector_events |= self._EVENT_WRITE
|
||||||
|
try:
|
||||||
|
self._selector.modify(key.fd, selector_events)
|
||||||
|
except Exception:
|
||||||
|
super().unregister(fileobj)
|
||||||
|
raise
|
||||||
|
changed = True
|
||||||
|
if data != key.data:
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
key = key._replace(events=events, data=data)
|
||||||
|
self._fd_to_key[key.fd] = key
|
||||||
|
return key
|
||||||
|
|
||||||
def select(self, timeout=None):
|
def select(self, timeout=None):
|
||||||
# This is shared between poll() and epoll().
|
# This is shared between poll() and epoll().
|
||||||
# epoll() has a different signature and handling of timeout parameter.
|
# epoll() has a different signature and handling of timeout parameter.
|
||||||
|
|
|
@ -175,6 +175,33 @@ class BaseSelectorTestCase(unittest.TestCase):
|
||||||
self.assertFalse(s.register.called)
|
self.assertFalse(s.register.called)
|
||||||
self.assertFalse(s.unregister.called)
|
self.assertFalse(s.unregister.called)
|
||||||
|
|
||||||
|
def test_modify_unregister(self):
|
||||||
|
# Make sure the fd is unregister()ed in case of error on
|
||||||
|
# modify(): http://bugs.python.org/issue30014
|
||||||
|
if self.SELECTOR.__name__ == 'EpollSelector':
|
||||||
|
patch = unittest.mock.patch(
|
||||||
|
'selectors.EpollSelector._selector_cls')
|
||||||
|
elif self.SELECTOR.__name__ == 'PollSelector':
|
||||||
|
patch = unittest.mock.patch(
|
||||||
|
'selectors.PollSelector._selector_cls')
|
||||||
|
elif self.SELECTOR.__name__ == 'DevpollSelector':
|
||||||
|
patch = unittest.mock.patch(
|
||||||
|
'selectors.DevpollSelector._selector_cls')
|
||||||
|
else:
|
||||||
|
raise self.skipTest("")
|
||||||
|
|
||||||
|
with patch as m:
|
||||||
|
m.return_value.modify = unittest.mock.Mock(
|
||||||
|
side_effect=ZeroDivisionError)
|
||||||
|
s = self.SELECTOR()
|
||||||
|
self.addCleanup(s.close)
|
||||||
|
rd, wr = self.make_socketpair()
|
||||||
|
s.register(rd, selectors.EVENT_READ)
|
||||||
|
self.assertEqual(len(s._map), 1)
|
||||||
|
with self.assertRaises(ZeroDivisionError):
|
||||||
|
s.modify(rd, selectors.EVENT_WRITE)
|
||||||
|
self.assertEqual(len(s._map), 0)
|
||||||
|
|
||||||
def test_close(self):
|
def test_close(self):
|
||||||
s = self.SELECTOR()
|
s = self.SELECTOR()
|
||||||
self.addCleanup(s.close)
|
self.addCleanup(s.close)
|
||||||
|
|
|
@ -350,6 +350,9 @@ Extension Modules
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- bpo-30014: modify() method of poll(), epoll() and devpoll() based classes of
|
||||||
|
selectors module is around 10% faster. Patch by Giampaolo Rodola'.
|
||||||
|
|
||||||
- bpo-30418: On Windows, subprocess.Popen.communicate() now also ignore EINVAL
|
- bpo-30418: On Windows, subprocess.Popen.communicate() now also ignore EINVAL
|
||||||
on stdin.write() if the child process is still running but closed the pipe.
|
on stdin.write() if the child process is still running but closed the pipe.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue