Skip to content

Commit c794103

Browse files
authored
[3.12] gh-110038: KqueueSelector must count all read/write events (GH-110039) (#110043)
[3.12] gh-110038: KqueueSelector must count all read/write events (GH-110039). (cherry picked from commit b14f0ab)
1 parent 4bdcb99 commit c794103

File tree

3 files changed

+38
-1
lines changed

3 files changed

+38
-1
lines changed

Lib/selectors.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ class KqueueSelector(_BaseSelectorImpl):
509509
def __init__(self):
510510
super().__init__()
511511
self._selector = select.kqueue()
512+
self._max_events = 0
512513

513514
def fileno(self):
514515
return self._selector.fileno()
@@ -520,10 +521,12 @@ def register(self, fileobj, events, data=None):
520521
kev = select.kevent(key.fd, select.KQ_FILTER_READ,
521522
select.KQ_EV_ADD)
522523
self._selector.control([kev], 0, 0)
524+
self._max_events += 1
523525
if events & EVENT_WRITE:
524526
kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
525527
select.KQ_EV_ADD)
526528
self._selector.control([kev], 0, 0)
529+
self._max_events += 1
527530
except:
528531
super().unregister(fileobj)
529532
raise
@@ -534,6 +537,7 @@ def unregister(self, fileobj):
534537
if key.events & EVENT_READ:
535538
kev = select.kevent(key.fd, select.KQ_FILTER_READ,
536539
select.KQ_EV_DELETE)
540+
self._max_events -= 1
537541
try:
538542
self._selector.control([kev], 0, 0)
539543
except OSError:
@@ -543,6 +547,7 @@ def unregister(self, fileobj):
543547
if key.events & EVENT_WRITE:
544548
kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
545549
select.KQ_EV_DELETE)
550+
self._max_events -= 1
546551
try:
547552
self._selector.control([kev], 0, 0)
548553
except OSError:
@@ -555,7 +560,7 @@ def select(self, timeout=None):
555560
# If max_ev is 0, kqueue will ignore the timeout. For consistent
556561
# behavior with the other selector classes, we prevent that here
557562
# (using max). See https://bugs.python.org/issue29255
558-
max_ev = max(len(self._fd_to_key), 1)
563+
max_ev = self._max_events or 1
559564
ready = []
560565
try:
561566
kev_list = self._selector.control(None, max_ev, timeout)

Lib/test/test_selectors.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,35 @@ def test_select(self):
279279

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

282+
def test_select_read_write(self):
283+
# gh-110038: when a file descriptor is registered for both read and
284+
# write, the two events must be seen on a single call to select().
285+
s = self.SELECTOR()
286+
self.addCleanup(s.close)
287+
288+
sock1, sock2 = self.make_socketpair()
289+
sock2.send(b"foo")
290+
my_key = s.register(sock1, selectors.EVENT_READ | selectors.EVENT_WRITE)
291+
292+
seen_read, seen_write = False, False
293+
result = s.select()
294+
# We get the read and write either in the same result entry or in two
295+
# distinct entries with the same key.
296+
self.assertLessEqual(len(result), 2)
297+
for key, events in result:
298+
self.assertTrue(isinstance(key, selectors.SelectorKey))
299+
self.assertEqual(key, my_key)
300+
self.assertFalse(events & ~(selectors.EVENT_READ |
301+
selectors.EVENT_WRITE))
302+
if events & selectors.EVENT_READ:
303+
self.assertFalse(seen_read)
304+
seen_read = True
305+
if events & selectors.EVENT_WRITE:
306+
self.assertFalse(seen_write)
307+
seen_write = True
308+
self.assertTrue(seen_read)
309+
self.assertTrue(seen_write)
310+
282311
def test_context_manager(self):
283312
s = self.SELECTOR()
284313
self.addCleanup(s.close)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed an issue that caused :meth:`KqueueSelector.select` to not return all
2+
the ready events in some cases when a file descriptor is registered for both
3+
read and write.

0 commit comments

Comments
 (0)