diff options
author | Helmut Grohne <helmut@subdivi.de> | 2024-02-16 15:20:00 +0100 |
---|---|---|
committer | Helmut Grohne <helmut@subdivi.de> | 2024-02-16 15:20:00 +0100 |
commit | 12f32219ff6a7e0eef6b914589d47066480404de (patch) | |
tree | 790ae738e5a0b2117143d5cec508ec4b37ea2a7d | |
parent | d8ecc510108426fba8f9a53e0d5fa54d5942e75f (diff) | |
download | python-linuxnamespaces-12f32219ff6a7e0eef6b914589d47066480404de.tar.gz |
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.
-rw-r--r-- | linuxnamespaces/syscalls.py | 31 | ||||
-rw-r--r-- | tests/test_simple.py | 15 |
2 files changed, 46 insertions, 0 deletions
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 <helmut@subdivi.de> # 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: |