Skip to content

Commit a8c8d97

Browse files
committed
refactor: move reject_futures to EventQueue, fix test latency and doctest
- Move future rejection logic from AsyncEngine into EventQueue.reject_futures() to respect encapsulation (avoids reaching into PriorityQueue internals) - Reduce asyncio.sleep in test_issue509 from 0.1s to 0.01s - Convert plain python block in docs/async.md to a testable doctest
1 parent a3afd7e commit a8c8d97

4 files changed

Lines changed: 25 additions & 14 deletions

File tree

docs/async.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,16 @@ compound states, parallel states, history pseudo-states, eventless transitions,
193193
and `done.state` events — are fully supported in async code. The same
194194
`activate_initial_state()` pattern applies:
195195

196-
```python
197-
async def run():
198-
sm = MyStateChart()
199-
await sm.activate_initial_state()
200-
await sm.send("event")
196+
```py
197+
>>> async def run():
198+
... sm = AsyncStateMachine()
199+
... await sm.activate_initial_state()
200+
... result = await sm.send("advance")
201+
... return result
202+
203+
>>> asyncio.run(run())
204+
42
205+
201206
```
202207

203208
### Concurrent event sending

statemachine/engines/async_.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,8 @@ def _reject_future(future: "asyncio.Future[object] | None", exc: Exception):
7070
future.set_exception(exc)
7171

7272
def _reject_pending_futures(self, exc: Exception):
73-
"""Reject all unresolved futures in the external queue.
74-
75-
Called when the processing loop exits abnormally so that coroutines
76-
awaiting their futures don't hang forever.
77-
"""
78-
with self.external_queue.queue.mutex:
79-
for trigger_data in self.external_queue.queue.queue:
80-
self._reject_future(trigger_data.future, exc)
73+
"""Reject all unresolved futures in the external queue."""
74+
self.external_queue.reject_futures(exc)
8175

8276
# --- Callback dispatch overrides (async versions of BaseEngine methods) ---
8377

statemachine/engines/base.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ def clear(self):
5959
with self.queue.mutex:
6060
self.queue.queue.clear()
6161

62+
def reject_futures(self, exc: Exception):
63+
"""Reject all unresolved futures in the queue.
64+
65+
Called when the processing loop exits abnormally so that coroutines
66+
awaiting their futures don't hang forever.
67+
"""
68+
with self.queue.mutex:
69+
for trigger_data in self.queue.queue:
70+
future = trigger_data.future
71+
if future is not None and not future.done():
72+
future.set_exception(exc)
73+
6274
def remove(self, send_id: str):
6375
# We use the internal `queue` to make thins faster as the mutex
6476
# is protecting the block below

tests/testcases/test_issue509.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class Issue509SC(StateChart):
3131
)
3232

3333
async def do_nothing(self, name):
34-
await asyncio.sleep(0.1)
34+
await asyncio.sleep(0.01)
3535
return f"Did nothing via {name}"
3636

3737
def raise_exception(self):

0 commit comments

Comments
 (0)