xiio

really simple async runtime
git clone https://git.ce9e.org/xiio.git

commit
fc000f4a6a23c0aa964652959f2296015a985d76
parent
62f05c12a608b69bda7bb5eff30a0be859df96a4
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2026-02-26 22:16
add futures

If multiple coroutines are waiting for a message on the same socket, it
can be helpful to have a mechanism to wake up the right one. Futures are
a pure python abstraction that does exactly that.

Diffstat

M tests.py 25 +++++++++++++++++++++++++
M xiio.py 27 ++++++++++++++++++++++++++-

2 files changed, 51 insertions, 1 deletions


diff --git a/tests.py b/tests.py

@@ -1,4 +1,6 @@
    1     1 import contextlib
   -1     2 import functools
   -1     3 import inspect
    2     4 import time
    3     5 import unittest
    4     6 from unittest import mock
@@ -7,6 +9,15 @@ import xiio
    7     9 
    8    10 
    9    11 class XiioTestCase(unittest.TestCase):
   -1    12     def _callTestMethod(self, method):  # noqa
   -1    13         if not inspect.iscoroutinefunction(method):
   -1    14             return super()._callTestMethod(method)
   -1    15 
   -1    16         @functools.wraps(method)
   -1    17         def wrapper(*args, **kwargs):
   -1    18             xiio.run(method(*args, **kwargs))
   -1    19         return super()._callTestMethod(wrapper)
   -1    20 
   10    21     @contextlib.contextmanager
   11    22     def assert_duration(self, expected, *, places=2):
   12    23         start = time.monotonic()
@@ -17,6 +28,20 @@ class XiioTestCase(unittest.TestCase):
   17    28             self.assertAlmostEqual(actual, expected, places=places)
   18    29 
   19    30 
   -1    31 class TestFuture(XiioTestCase):
   -1    32     async def test_set_result(self):
   -1    33         future = xiio.Future()
   -1    34         future.set_result('test')
   -1    35         result = await future
   -1    36         self.assertEqual(result, 'test')
   -1    37 
   -1    38     async def test_set_exception(self):
   -1    39         future = xiio.Future()
   -1    40         future.set_exception(TypeError)
   -1    41         with self.assertRaises(TypeError):
   -1    42             await future
   -1    43 
   -1    44 
   20    45 class TestRun(XiioTestCase):
   21    46     def test_sleep(self):
   22    47         async def foo():

diff --git a/xiio.py b/xiio.py

@@ -7,8 +7,9 @@ from selectors import EVENT_WRITE as WRITE
    7     7 
    8     8 
    9     9 class Condition:
   10    -1     def __init__(self, *, files={}, time=math.inf):
   -1    10     def __init__(self, *, files={}, futures=set(), time=math.inf):
   11    11         self.files = files or {}
   -1    12         self.futures = futures or set()
   12    13         self.time = time
   13    14 
   14    15     def __await__(self):
@@ -16,6 +17,8 @@ class Condition:
   16    17 
   17    18     def select(self):
   18    19         timeout = self.time - time.monotonic()
   -1    20         if any(future.done for future in self.futures):
   -1    21             timeout = 0
   19    22         with selectors.DefaultSelector() as sel:
   20    23             for fileno, events in self.files.items():
   21    24                 sel.register(fileno, events)
@@ -44,6 +47,28 @@ async def writeall(file, data):
   44    47         data = data[size:]
   45    48 
   46    49 
   -1    50 class Future:
   -1    51     def __init__(self):
   -1    52         self.result = None
   -1    53         self.exc = None
   -1    54         self.done = False
   -1    55 
   -1    56     def set_result(self, value):
   -1    57         self.result = value
   -1    58         self.done = True
   -1    59 
   -1    60     def set_exception(self, exc):
   -1    61         self.exc = exc
   -1    62         self.done = True
   -1    63 
   -1    64     def __await__(self):
   -1    65         yield Condition(futures={self})
   -1    66         if self.exc:
   -1    67             raise self.exc
   -1    68         else:
   -1    69             return self.result
   -1    70 
   -1    71 
   47    72 def run(coro):
   48    73     gen = coro.__await__()
   49    74     try: