Skip to content

Commit

Permalink
pythongh-110038: KqueueSelector must count all read/write events (pyt…
Browse files Browse the repository at this point in the history
  • Loading branch information
sorcio authored Sep 28, 2023
1 parent 7e0fbf5 commit b14f0ab
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 1 deletion.
7 changes: 6 additions & 1 deletion Lib/selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ class KqueueSelector(_BaseSelectorImpl):
def __init__(self):
super().__init__()
self._selector = select.kqueue()
self._max_events = 0

def fileno(self):
return self._selector.fileno()
Expand All @@ -502,10 +503,12 @@ def register(self, fileobj, events, data=None):
kev = select.kevent(key.fd, select.KQ_FILTER_READ,
select.KQ_EV_ADD)
self._selector.control([kev], 0, 0)
self._max_events += 1
if events & EVENT_WRITE:
kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
select.KQ_EV_ADD)
self._selector.control([kev], 0, 0)
self._max_events += 1
except:
super().unregister(fileobj)
raise
Expand All @@ -516,6 +519,7 @@ def unregister(self, fileobj):
if key.events & EVENT_READ:
kev = select.kevent(key.fd, select.KQ_FILTER_READ,
select.KQ_EV_DELETE)
self._max_events -= 1
try:
self._selector.control([kev], 0, 0)
except OSError:
Expand All @@ -525,6 +529,7 @@ def unregister(self, fileobj):
if key.events & EVENT_WRITE:
kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
select.KQ_EV_DELETE)
self._max_events -= 1
try:
self._selector.control([kev], 0, 0)
except OSError:
Expand All @@ -537,7 +542,7 @@ def select(self, timeout=None):
# If max_ev is 0, kqueue will ignore the timeout. For consistent
# behavior with the other selector classes, we prevent that here
# (using max). See https://bugs.python.org/issue29255
max_ev = len(self._fd_to_key) or 1
max_ev = self._max_events or 1
ready = []
try:
kev_list = self._selector.control(None, max_ev, timeout)
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,35 @@ def test_select(self):

self.assertEqual([(wr_key, selectors.EVENT_WRITE)], result)

def test_select_read_write(self):
# gh-110038: when a file descriptor is registered for both read and
# write, the two events must be seen on a single call to select().
s = self.SELECTOR()
self.addCleanup(s.close)

sock1, sock2 = self.make_socketpair()
sock2.send(b"foo")
my_key = s.register(sock1, selectors.EVENT_READ | selectors.EVENT_WRITE)

seen_read, seen_write = False, False
result = s.select()
# We get the read and write either in the same result entry or in two
# distinct entries with the same key.
self.assertLessEqual(len(result), 2)
for key, events in result:
self.assertTrue(isinstance(key, selectors.SelectorKey))
self.assertEqual(key, my_key)
self.assertFalse(events & ~(selectors.EVENT_READ |
selectors.EVENT_WRITE))
if events & selectors.EVENT_READ:
self.assertFalse(seen_read)
seen_read = True
if events & selectors.EVENT_WRITE:
self.assertFalse(seen_write)
seen_write = True
self.assertTrue(seen_read)
self.assertTrue(seen_write)

def test_context_manager(self):
s = self.SELECTOR()
self.addCleanup(s.close)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed an issue that caused :meth:`KqueueSelector.select` to not return all
the ready events in some cases when a file descriptor is registered for both
read and write.

0 comments on commit b14f0ab

Please sign in to comment.