]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.11] gh-110038: KqueueSelector must count all read/write events (GH-110039) (#110044)
authorDavide Rizzo <sorcio@gmail.com>
Thu, 28 Sep 2023 18:58:24 +0000 (20:58 +0200)
committerGitHub <noreply@github.com>
Thu, 28 Sep 2023 18:58:24 +0000 (20:58 +0200)
[3.11] gh-110038: KqueueSelector must count all read/write events (GH-110039).
(cherry picked from commit b14f0ab51cb4851b25935279617e388456dcf716)

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 af6a4f94b5008ad6c466922eb323338604c453fc..c3b065b52264aa5e6e611dc6f7be59045fb5d455 100644 (file)
@@ -509,6 +509,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()
@@ -520,10 +521,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
@@ -534,6 +537,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:
@@ -543,6 +547,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:
@@ -555,7 +560,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 = max(len(self._fd_to_key), 1)
+            max_ev = self._max_events or 1
             ready = []
             try:
                 kev_list = self._selector.control(None, max_ev, timeout)
index 12ecc50d550c4fc82ccc8a7e0f10ae519214da47..31757205ca37c57a733da9ec2b9fbcbcd2771d8e 100644 (file)
@@ -279,6 +279,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.