Spaces:
Running
Running
| """In-process pub/sub for run events (Batch C3). | |
| The worker publishes run events without ever awaiting a subscriber, so a slow or stalled | |
| WebSocket client can never apply backpressure to the engine ("drop and replay"). If a | |
| subscriber's bounded queue fills up, it receives a single overflow sentinel and is expected to | |
| close and reconnect, replaying any missed events from the append-only ``run_events`` table by | |
| ``seq``. Durability lives in Postgres; this bus only carries the live tail. | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| from collections import defaultdict | |
| from typing import Any | |
| OVERFLOW: dict[str, Any] = {"__overflow__": True} | |
| class EventBus: | |
| def __init__(self, max_queue: int = 512) -> None: | |
| self._subs: dict[str, set[asyncio.Queue]] = defaultdict(set) | |
| self._max_queue = max_queue | |
| def subscribe(self, run_id: str) -> asyncio.Queue: | |
| queue: asyncio.Queue = asyncio.Queue(maxsize=self._max_queue) | |
| self._subs[run_id].add(queue) | |
| return queue | |
| def unsubscribe(self, run_id: str, queue: asyncio.Queue) -> None: | |
| subs = self._subs.get(run_id) | |
| if subs is not None: | |
| subs.discard(queue) | |
| if not subs: | |
| self._subs.pop(run_id, None) | |
| def publish_nowait(self, run_id: str, event: dict[str, Any]) -> None: | |
| """Fan out one event to every subscriber. Must run on the event loop thread. | |
| Never blocks: a full subscriber queue gets an overflow sentinel instead, signalling the | |
| consumer to drop and replay from the database. | |
| """ | |
| for queue in list(self._subs.get(run_id, ())): | |
| try: | |
| queue.put_nowait(event) | |
| except asyncio.QueueFull: | |
| self._overflow(queue) | |
| def _overflow(queue: asyncio.Queue) -> None: | |
| try: | |
| queue.put_nowait(OVERFLOW) | |
| except asyncio.QueueFull: | |
| pass | |
| __all__ = ["EventBus", "OVERFLOW"] | |