]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-110038: KqueueSelector must count all read/write events (#110039)
authorDavide Rizzo <sorcio@gmail.com>
Thu, 28 Sep 2023 17:25:10 +0000 (19:25 +0200)
committerGitHub <noreply@github.com>
Thu, 28 Sep 2023 17:25:10 +0000 (17:25 +0000)
Lib/selectors.py
Lib/test/test_selectors.py
Misc/NEWS.d/next/Library/2023-09-28-18-50-33.gh-issue-110038.nx_gCu.rst [new file with mode: 0644]

index 20367c9152f331daae9482162e3425aafd3ba51b..b8e5f6a4f77d8941c0782f6b5d2332d3766ebf70 100644 (file)
@@ -491,6 +491,7 @@ if hasattr(select, 'kqueue'):
         def __init__(self):
             super().__init__()
             self._selector = select.kqueue()
+            self._max_events = 0
 
         def fileno(self):
             return self._selector.fileno()
@@ -502,10 +503,12 @@ if hasattr(select, 'kqueue'):
                     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
@@ -516,6 +519,7 @@ if hasattr(select, 'kqueue'):
             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:
@@ -525,6 +529,7 @@ if hasattr(select, 'kqueue'):
             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:
@@ -537,7 +542,7 @@ if hasattr(select, 'kqueue'):
             # 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)
index 33417ca6a11af0fd5025144ddb1ca8962c2bd2b2..677349c2bfca93da8b4ac6b56feea291978e20cf 100644 (file)
@@ -285,6 +285,35 @@ class BaseSelectorTestCase:
 
         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)
diff --git a/Misc/NEWS.d/next/Library/2023-09-28-18-50-33.gh-issue-110038.nx_gCu.rst b/Misc/NEWS.d/next/Library/2023-09-28-18-50-33.gh-issue-110038.nx_gCu.rst
new file mode 100644 (file)
index 0000000..6b2abd8
--- /dev/null
@@ -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.