Skip to content

Commit fbfaa6f

Browse files
giampaolovstinner
authored andcommitted
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'
1 parent 894a654 commit fbfaa6f

File tree

4 files changed

+62
-1
lines changed

4 files changed

+62
-1
lines changed

Doc/whatsnew/3.7.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ Optimizations
234234
expressions <re>`. Searching some patterns can now be up to 20 times faster.
235235
(Contributed by Serhiy Storchaka in :issue:`30285`.)
236236

237+
* :meth:`selectors.EpollSelector.modify`, :meth:`selectors.PollSelector.modify`
238+
and :meth:`selectors.DevpollSelector.modify` may be around 10% faster under
239+
heavy loads. (Contributed by Giampaolo Rodola' in :issue:`30014`)
237240

238241
Build and C API Changes
239242
=======================

Lib/selectors.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,6 @@ def unregister(self, fileobj):
252252
return key
253253

254254
def modify(self, fileobj, events, data=None):
255-
# TODO: Subclasses can probably optimize this even further.
256255
try:
257256
key = self._fd_to_key[self._fileobj_lookup(fileobj)]
258257
except KeyError:
@@ -342,6 +341,8 @@ def select(self, timeout=None):
342341
class _PollLikeSelector(_BaseSelectorImpl):
343342
"""Base class shared between poll, epoll and devpoll selectors."""
344343
_selector_cls = None
344+
_EVENT_READ = None
345+
_EVENT_WRITE = None
345346

346347
def __init__(self):
347348
super().__init__()
@@ -371,6 +372,33 @@ def unregister(self, fileobj):
371372
pass
372373
return key
373374

375+
def modify(self, fileobj, events, data=None):
376+
try:
377+
key = self._fd_to_key[self._fileobj_lookup(fileobj)]
378+
except KeyError:
379+
raise KeyError(f"{fileobj!r} is not registered") from None
380+
381+
changed = False
382+
if events != key.events:
383+
selector_events = 0
384+
if events & EVENT_READ:
385+
selector_events |= self._EVENT_READ
386+
if events & EVENT_WRITE:
387+
selector_events |= self._EVENT_WRITE
388+
try:
389+
self._selector.modify(key.fd, selector_events)
390+
except Exception:
391+
super().unregister(fileobj)
392+
raise
393+
changed = True
394+
if data != key.data:
395+
changed = True
396+
397+
if changed:
398+
key = key._replace(events=events, data=data)
399+
self._fd_to_key[key.fd] = key
400+
return key
401+
374402
def select(self, timeout=None):
375403
# This is shared between poll() and epoll().
376404
# epoll() has a different signature and handling of timeout parameter.

Lib/test/test_selectors.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,33 @@ def test_modify(self):
175175
self.assertFalse(s.register.called)
176176
self.assertFalse(s.unregister.called)
177177

178+
def test_modify_unregister(self):
179+
# Make sure the fd is unregister()ed in case of error on
180+
# modify(): http://bugs.python.org/issue30014
181+
if self.SELECTOR.__name__ == 'EpollSelector':
182+
patch = unittest.mock.patch(
183+
'selectors.EpollSelector._selector_cls')
184+
elif self.SELECTOR.__name__ == 'PollSelector':
185+
patch = unittest.mock.patch(
186+
'selectors.PollSelector._selector_cls')
187+
elif self.SELECTOR.__name__ == 'DevpollSelector':
188+
patch = unittest.mock.patch(
189+
'selectors.DevpollSelector._selector_cls')
190+
else:
191+
raise self.skipTest("")
192+
193+
with patch as m:
194+
m.return_value.modify = unittest.mock.Mock(
195+
side_effect=ZeroDivisionError)
196+
s = self.SELECTOR()
197+
self.addCleanup(s.close)
198+
rd, wr = self.make_socketpair()
199+
s.register(rd, selectors.EVENT_READ)
200+
self.assertEqual(len(s._map), 1)
201+
with self.assertRaises(ZeroDivisionError):
202+
s.modify(rd, selectors.EVENT_WRITE)
203+
self.assertEqual(len(s._map), 0)
204+
178205
def test_close(self):
179206
s = self.SELECTOR()
180207
self.addCleanup(s.close)

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,9 @@ Extension Modules
350350
Library
351351
-------
352352

353+
- bpo-30014: modify() method of poll(), epoll() and devpoll() based classes of
354+
selectors module is around 10% faster. Patch by Giampaolo Rodola'.
355+
353356
- bpo-30418: On Windows, subprocess.Popen.communicate() now also ignore EINVAL
354357
on stdin.write() if the child process is still running but closed the pipe.
355358

0 commit comments

Comments
 (0)