xiio

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

commit
782851e16a4988987e94d22388efb84d2ab988ac
parent
0df8fb90d90a5f881d9815ac411c82c769ee5a00
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2026-02-27 00:46
add timeout()

Just a higher level abstraction on top of TaskGroup.

Diffstat

M README.md 15 ++++++++-------
M tests.py 28 ++++++++++++++++++++++++++++
M xiio.py 23 +++++++++++++++++++++++

3 files changed, 59 insertions, 7 deletions


diff --git a/README.md b/README.md

@@ -19,13 +19,14 @@ async def greet(name):
   19    19 
   20    20 
   21    21 async def main():
   22    -1     name1 = (await xiio.read(sys.stdin, 32)).decode()
   23    -1     name2 = (await xiio.read(sys.stdin, 32)).decode()
   24    -1 
   25    -1     await xiio.gather([
   26    -1         greet(name1),
   27    -1         greet(name2),
   28    -1     ])
   -1    22     async with xiio.timeout(10):
   -1    23         name1 = (await xiio.read(sys.stdin, 32)).decode()
   -1    24         name2 = (await xiio.read(sys.stdin, 32)).decode()
   -1    25 
   -1    26         await xiio.gather([
   -1    27             greet(name1),
   -1    28             greet(name2),
   -1    29         ])
   29    30 
   30    31 
   31    32 xiio.run(main())

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

@@ -241,6 +241,34 @@ class TestGather(XiioTestCase):
  241   241         self.assertEqual(stack, [1])
  242   242 
  243   243 
   -1   244 class TestTimeout(XiioTestCase):
   -1   245     def test_timeout_finish(self):
   -1   246         async def foo():
   -1   247             async with xiio.timeout(1):
   -1   248                 await xiio.sleep(0.2)
   -1   249                 return 1
   -1   250 
   -1   251         with self.assert_duration(0.2):
   -1   252             result = xiio.run(foo())
   -1   253         self.assertEqual(result, 1)
   -1   254 
   -1   255     async def test_timeout_throw(self):
   -1   256         with self.assertRaises(TimeoutError):
   -1   257             with self.assert_duration(0.1):
   -1   258                 async with xiio.timeout(0.1):
   -1   259                     await xiio.sleep(0.3)
   -1   260 
   -1   261     async def test_timeout_no_throw(self):
   -1   262         with self.assert_duration(0.1):
   -1   263             async with xiio.timeout(0.1, throw=False):
   -1   264                 await xiio.sleep(0.3)
   -1   265 
   -1   266     async def test_timeout_none(self):
   -1   267         with self.assert_duration(0.1):
   -1   268             async with xiio.timeout(None):
   -1   269                 await xiio.sleep(0.1)
   -1   270 
   -1   271 
  244   272 class TestRun(XiioTestCase):
  245   273     def test_sleep(self):
  246   274         async def foo():

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

@@ -1,8 +1,10 @@
   -1     1 import contextlib
    1     2 import math
    2     3 import os
    3     4 import selectors
    4     5 import time
    5     6 import typing
   -1     7 from collections.abc import AsyncGenerator
    6     8 from collections.abc import Coroutine
    7     9 from collections.abc import Generator
    8    10 from selectors import EVENT_READ as READ
@@ -222,6 +224,27 @@ async def gather(coros: list[Coro[T]]) -> list[T]:
  222   224     return [typing.cast(T, task.result) for task in tasks]
  223   225 
  224   226 
   -1   227 @contextlib.asynccontextmanager
   -1   228 async def timeout(
   -1   229     seconds: float | None, *, throw: bool = True
   -1   230 ) -> AsyncGenerator[None, None]:
   -1   231     if seconds is None:
   -1   232         yield
   -1   233     else:
   -1   234         async def _timeout() -> typing.NoReturn:
   -1   235             await sleep(seconds)
   -1   236             raise TimeoutError
   -1   237 
   -1   238         try:
   -1   239             async with TaskGroup() as tg:
   -1   240                 task = tg.add_task(_timeout())
   -1   241                 yield
   -1   242                 task.cancel()
   -1   243         except TimeoutError:
   -1   244             if throw:
   -1   245                 raise
   -1   246 
   -1   247 
  225   248 def run(coro: Coro[T]) -> T:
  226   249     task = Task(coro.__await__())
  227   250     try: