Upstream changelog: https://gitlab.gnome.org/GNOME/pygobject/-/blob/3.52.3/NEWS?ref_type=tags Patch updated to the version that applies to 3.52.3. Signed-off-by: Fiona Klute (WIWA) <fiona.klute@gmx.de> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
165 lines
5.8 KiB
Diff
165 lines
5.8 KiB
Diff
From 4b97688d527276fc1e835fed71dbb73bbd09ac11 Mon Sep 17 00:00:00 2001
|
|
From: Fiona Klute <fiona.klute@gmx.de>
|
|
Date: Tue, 25 Mar 2025 13:29:04 +0100
|
|
Subject: [PATCH] gi.events._Selector.get_map(): look up file objects by file
|
|
descriptor
|
|
|
|
File objects and their underlying file descriptors must be treated as
|
|
equivalent for lookup, or one may incorrectly be treated as not
|
|
registered when the other was used for the registration, leading to
|
|
bugs.
|
|
|
|
Fixes: #689
|
|
Signed-off-by: Fiona Klute <fiona.klute@gmx.de>
|
|
Upstream: https://gitlab.gnome.org/GNOME/pygobject/-/commit/4b97688d527276fc1e835fed71dbb73bbd09ac11
|
|
---
|
|
gi/events.py | 36 +++++++++++++++++++++-----
|
|
tests/test_events.py | 61 ++++++++++++++++++++++++++++++++++++++++++++
|
|
2 files changed, 90 insertions(+), 7 deletions(-)
|
|
|
|
diff --git a/gi/events.py b/gi/events.py
|
|
index da4775dc..9eba1629 100644
|
|
--- a/gi/events.py
|
|
+++ b/gi/events.py
|
|
@@ -28,6 +28,7 @@ import threading
|
|
import selectors
|
|
import weakref
|
|
import warnings
|
|
+from collections.abc import Mapping
|
|
from contextlib import contextmanager
|
|
from . import _ossighelper
|
|
|
|
@@ -508,9 +509,33 @@ if sys.platform != 'win32':
|
|
# Subclass to attach _tag
|
|
pass
|
|
|
|
+ class _FileObjectMapping(Mapping):
|
|
+ def __init__(self, fd_dict):
|
|
+ self.fd_dict = fd_dict
|
|
+
|
|
+ def __len__(self):
|
|
+ return len(self.fd_dict)
|
|
+
|
|
+ def get(self, fileobj, default=None):
|
|
+ fd = _fileobj_to_fd(fileobj)
|
|
+ return self.fd_dict.get(fd, default)
|
|
+
|
|
+ def __getitem__(self, fileobj):
|
|
+ value = self.get(fileobj)
|
|
+ if value is None:
|
|
+ raise KeyError("{!r} is not registered".format(fileobj))
|
|
+ return value
|
|
+
|
|
+ def __iter__(self):
|
|
+ return iter(self.fd_dict)
|
|
+
|
|
class _Selector(_SelectorMixin, selectors.BaseSelector):
|
|
"""A Selector for gi.events.GLibEventLoop registering python IO with GLib."""
|
|
|
|
+ def __init__(self, context, loop):
|
|
+ super().__init__(context, loop)
|
|
+ self._map = _FileObjectMapping(self._fd_to_key)
|
|
+
|
|
def attach(self):
|
|
self._source.attach(self._loop._context)
|
|
|
|
@@ -559,15 +584,12 @@ if sys.platform != 'win32':
|
|
# We could override modify, but it is only slightly when the "events" change.
|
|
|
|
def get_key(self, fileobj):
|
|
- fd = _fileobj_to_fd(fileobj)
|
|
- return self._fd_to_key[fd]
|
|
+ return self._map[fileobj]
|
|
|
|
def get_map(self):
|
|
- """Return a mapping of file objects to selector keys."""
|
|
- # Horribly inefficient
|
|
- # It should never be called and exists just to prevent issues if e.g.
|
|
- # python decides to use it for debug purposes.
|
|
- return {k.fileobj: k for k in self._fd_to_key.values()}
|
|
+ """Return a mapping of file objects or file descriptors to
|
|
+ selector keys."""
|
|
+ return self._map
|
|
|
|
|
|
else:
|
|
diff --git a/tests/test_events.py b/tests/test_events.py
|
|
index a6585468..9dbf3827 100644
|
|
--- a/tests/test_events.py
|
|
+++ b/tests/test_events.py
|
|
@@ -46,6 +46,7 @@ import sys
|
|
import gi
|
|
import gi.events
|
|
import asyncio
|
|
+import socket
|
|
import threading
|
|
from gi.repository import GLib, Gio
|
|
|
|
@@ -344,3 +345,63 @@ class GLibEventLoopPolicyTests(unittest.TestCase):
|
|
self.assertEqual(order, [GLib.PRIORITY_HIGH] * 3 +
|
|
[GLib.PRIORITY_DEFAULT] * 3 +
|
|
[GLib.PRIORITY_DEFAULT_IDLE] * 3)
|
|
+
|
|
+ @unittest.skipIf(sys.platform == 'win32', 'add reader/writer not implemented')
|
|
+ def test_source_fileobj_fd(self):
|
|
+ """Regression test for
|
|
+ https://gitlab.gnome.org/GNOME/pygobject/-/issues/689
|
|
+ """
|
|
+ class Echo:
|
|
+ def __init__(self, sock, expect_bytes):
|
|
+ self.sock = sock
|
|
+ self.sent_bytes = 0
|
|
+ self.expect_bytes = expect_bytes
|
|
+ self.done = asyncio.Future()
|
|
+ self.data = bytes()
|
|
+
|
|
+ def send(self):
|
|
+ if self.done.done():
|
|
+ return
|
|
+ if self.sent_bytes < len(self.data):
|
|
+ self.sent_bytes += self.sock.send(
|
|
+ self.data[self.sent_bytes:])
|
|
+ print('sent', self.data)
|
|
+ if self.sent_bytes >= self.expect_bytes:
|
|
+ self.done.set_result(None)
|
|
+ self.sock.shutdown(socket.SHUT_WR)
|
|
+
|
|
+ def recv(self):
|
|
+ if self.done.done():
|
|
+ return
|
|
+ self.data += self.sock.recv(self.expect_bytes)
|
|
+ print('received', self.data)
|
|
+ if len(self.data) >= self.expect_bytes:
|
|
+ self.sock.shutdown(socket.SHUT_RD)
|
|
+
|
|
+ async def run():
|
|
+ loop = asyncio.get_running_loop()
|
|
+ s1, s2 = socket.socketpair()
|
|
+ sample = b'Hello!'
|
|
+ e = Echo(s1, len(sample))
|
|
+ # register using file object and file descriptor
|
|
+ loop.add_reader(s1, e.recv)
|
|
+ loop.add_writer(s1.fileno(), e.send)
|
|
+ s2.sendall(sample)
|
|
+ await asyncio.wait_for(e.done, timeout=2.0)
|
|
+ echo = bytes()
|
|
+ for _ in range(len(sample)):
|
|
+ echo += s2.recv(len(sample))
|
|
+ if len(echo) == len(sample):
|
|
+ break
|
|
+ # remove using file object and file descriptor
|
|
+ loop.remove_reader(s1)
|
|
+ loop.remove_writer(s1.fileno())
|
|
+ s1.close()
|
|
+ s2.close()
|
|
+ # check if the data was echoed correctly
|
|
+ self.assertEqual(sample, echo)
|
|
+
|
|
+ policy = self.create_policy()
|
|
+ loop = policy.get_event_loop()
|
|
+ loop.run_until_complete(run())
|
|
+ loop.close()
|
|
--
|
|
2.49.0
|
|
|