summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--linuxnamespaces/syscalls.py31
-rw-r--r--tests/test_simple.py15
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: