xiio

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

commit
62f05c12a608b69bda7bb5eff30a0be859df96a4
parent
8ed7defd595a8e895c90830093d01959b19a03cc
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2026-02-26 18:42
add support for reading/writing files

So far we can wait for timeouts. In a real system, we also want to be
able to wait for files. So we replace the `Timeout` class by a more
generic `Condition` class together with the `sleep()`, `read()` and
`write()` helpers. We use the `selectors` module to wait for files to
become available.

Diffstat

M tests.py 8 ++++----
M xiio.py 50 +++++++++++++++++++++++++++++++++++++++++---------

2 files changed, 45 insertions, 13 deletions


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

@@ -20,7 +20,7 @@ class XiioTestCase(unittest.TestCase):
   20    20 class TestRun(XiioTestCase):
   21    21     def test_sleep(self):
   22    22         async def foo():
   23    -1             await xiio.Timeout(0.1)
   -1    23             await xiio.sleep(0.1)
   24    24             return 'Hello World'
   25    25 
   26    26         with self.assert_duration(0.1):
@@ -32,11 +32,11 @@ class TestRun(XiioTestCase):
   32    32 
   33    33         async def foo():
   34    34             try:
   35    -1                 await xiio.Timeout(0.1)
   -1    35                 await xiio.sleep(0.1)
   36    36             finally:
   37    37                 stack.append(1)
   38    38 
   39    -1         with mock.patch('time.sleep', side_effect=KeyboardInterrupt):
   -1    39         with mock.patch('xiio.Condition.select', side_effect=KeyboardInterrupt):
   40    40             with self.assertRaises(KeyboardInterrupt):
   41    41                 xiio.run(foo())
   42    42         self.assertEqual(stack, [1])
@@ -46,7 +46,7 @@ class TestRun(XiioTestCase):
   46    46             try:
   47    47                 raise ValueError
   48    48             finally:
   49    -1                 await xiio.Timeout(0.1)
   -1    49                 await xiio.sleep(0.1)
   50    50 
   51    51         with self.assertRaises(ValueError):
   52    52             with self.assert_duration(0.1):

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

@@ -1,27 +1,59 @@
   -1     1 import math
   -1     2 import os
   -1     3 import selectors
    1     4 import time
   -1     5 from selectors import EVENT_READ as READ
   -1     6 from selectors import EVENT_WRITE as WRITE
    2     7 
    3     8 
    4    -1 class Timeout:
    5    -1     def __init__(self, seconds):
    6    -1         self.seconds = seconds
   -1     9 class Condition:
   -1    10     def __init__(self, *, files={}, time=math.inf):
   -1    11         self.files = files or {}
   -1    12         self.time = time
    7    13 
    8    14     def __await__(self):
    9    15         yield self
   10    16 
   11    -1     def wait(self):
   12    -1         time.sleep(self.seconds)
   -1    17     def select(self):
   -1    18         timeout = self.time - time.monotonic()
   -1    19         with selectors.DefaultSelector() as sel:
   -1    20             for fileno, events in self.files.items():
   -1    21                 sel.register(fileno, events)
   -1    22             sel.select(None if timeout == math.inf else timeout)
   -1    23 
   -1    24 
   -1    25 async def sleep(seconds):
   -1    26     await Condition(time=time.monotonic() + seconds)
   -1    27 
   -1    28 
   -1    29 async def read(file, size):
   -1    30     fileno = file if isinstance(file, int) else file.fileno()
   -1    31     await Condition(files={fileno: READ})
   -1    32     return os.read(fileno, size)
   -1    33 
   -1    34 
   -1    35 async def write(file, data):
   -1    36     fileno = file if isinstance(file, int) else file.fileno()
   -1    37     await Condition(files={fileno: WRITE})
   -1    38     return os.write(fileno, data)
   -1    39 
   -1    40 
   -1    41 async def writeall(file, data):
   -1    42     while data:
   -1    43         size = await write(file, data)
   -1    44         data = data[size:]
   13    45 
   14    46 
   15    47 def run(coro):
   16    48     gen = coro.__await__()
   17    49     try:
   18    -1         timeout = next(gen)
   -1    50         condition = next(gen)
   19    51         while True:
   20    52             try:
   21    -1                 timeout.wait()
   -1    53                 condition.select()
   22    54             except BaseException as e:
   23    -1                 timeout = gen.throw(e)
   -1    55                 condition = gen.throw(e)
   24    56             else:
   25    -1                 timeout = next(gen)
   -1    57                 condition = next(gen)
   26    58     except StopIteration as e:
   27    59         return e.value