From 12f32219ff6a7e0eef6b914589d47066480404de Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Fri, 16 Feb 2024 15:20:00 +0100 Subject: add an async read method to EventFD Adding an async write does not work for values larger than 1, because the fd becomes writable once a value of 1 can be written, but a larger value might still cause EAGAIN putting us into a busy loop. Hitting the limit with writing ones is implausible, so async code can just use the synchronous write method. --- linuxnamespaces/syscalls.py | 31 +++++++++++++++++++++++++++++++ tests/test_simple.py | 15 +++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/linuxnamespaces/syscalls.py b/linuxnamespaces/syscalls.py index 338e602..06b5025 100644 --- a/linuxnamespaces/syscalls.py +++ b/linuxnamespaces/syscalls.py @@ -5,9 +5,11 @@ for Linux namespaces including the new mount API. """ +import asyncio import ctypes import dataclasses import enum +import errno import os import typing @@ -419,6 +421,35 @@ class EventFD: call_libc("eventfd_read", self.fd, ctypes.byref(cvalue)) return cvalue.value + def __handle_readable(self, fd: int, fut: asyncio.Future[int]) -> None: + """Internal helper of aread.""" + try: + if fd != self.fd: + raise RuntimeError("EventFD file descriptor changed") + try: + result = self.read() + except OSError as err: + if err.errno == errno.EAGAIN: + return + raise + except Exception as exc: + fut.get_loop().remove_reader(fd) + fut.set_exception(exc) + else: + fut.get_loop().remove_reader(fd) + fut.set_result(result) + + def aread(self) -> typing.Awaitable[int]: + """Decrease the value of the eventfd asynchronously. It mst have been + constructed using EventFDFlags.NONBLOCK. + """ + if self.fd < 0: + raise ValueError("attempt to read from closed eventfd") + loop = asyncio.get_running_loop() + fut: asyncio.Future[int] = loop.create_future() + loop.add_reader(self.fd, self.__handle_readable, self.fd, fut) + return fut + def write(self, value: int = 1) -> None: """Add the given value to the eventfd using eventfd_write.""" if self.fd < 0: diff --git a/tests/test_simple.py b/tests/test_simple.py index 960bf02..5889e25 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -1,6 +1,7 @@ # Copyright 2024 Helmut Grohne # SPDX-License-Identifier: GPL-3 +import asyncio import functools import os import pathlib @@ -48,6 +49,20 @@ class IDAllocationTest(unittest.TestCase): self.assertIn(alloc.allocate(3), (1, 2)) +class EventFDTest(unittest.IsolatedAsyncioTestCase): + async def test_async(self) -> None: + efd = linuxnamespaces.EventFD(1, linuxnamespaces.EventFDFlags.NONBLOCK) + fut = efd.aread() + await asyncio.sleep(0.000001) # Let the loop run + self.assertTrue(fut.done()) + self.assertEqual(await fut, 1) + fut = efd.aread() + await asyncio.sleep(0.000001) # Let the loop run + self.assertFalse(fut.done()) + efd.write() + self.assertEqual(await fut, 1) + + class UnshareTest(unittest.TestCase): @pytest.mark.forked def test_unshare_user(self) -> None: -- cgit v1.2.3