From 44980b711f710317dfe7efe0cce6b3c25e15429c Mon Sep 17 00:00:00 2001 From: Davide Rizzo Date: Thu, 28 Sep 2023 18:45:07 +0200 Subject: [PATCH] gh-110038: KqueueSelector must count all read/write events --- Lib/selectors.py | 7 ++++++- Lib/test/test_selectors.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Lib/selectors.py b/Lib/selectors.py index 20367c9152f331..986353bca29b24 100644 --- a/Lib/selectors.py +++ b/Lib/selectors.py @@ -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() @@ -499,10 +500,12 @@ def register(self, fileobj, events, data=None): key = super().register(fileobj, events, data) try: if events & EVENT_READ: + self._max_events += 1 kev = select.kevent(key.fd, select.KQ_FILTER_READ, select.KQ_EV_ADD) self._selector.control([kev], 0, 0) if events & EVENT_WRITE: + self._max_events += 1 kev = select.kevent(key.fd, select.KQ_FILTER_WRITE, select.KQ_EV_ADD) self._selector.control([kev], 0, 0) @@ -514,6 +517,7 @@ def register(self, fileobj, events, data=None): def unregister(self, fileobj): key = super().unregister(fileobj) if key.events & EVENT_READ: + self._max_events -= 1 kev = select.kevent(key.fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE) try: @@ -523,6 +527,7 @@ def unregister(self, fileobj): # was registered. pass if key.events & EVENT_WRITE: + self._max_events -= 1 kev = select.kevent(key.fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE) try: @@ -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) diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py index 33417ca6a11af0..677349c2bfca93 100644 --- a/Lib/test/test_selectors.py +++ b/Lib/test/test_selectors.py @@ -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)