simbiot-harness/venv/lib/python3.12/site-packages/openai/_send_queue.py
Simbiot Harness b60439152b Simbiot Harness v0.1: тактовая лестница на Strands + CQRS/ES-спина
- harness/es.py: append-only event store + проекции (образец — кооп-портал)
- harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу
- harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы
- harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе
  (глобальный asyncio-рантайм Strands не переживает 2+ create_harness)
- cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом)
- 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
2026-09-29 15:28:50 +00:00

120 lines
4.3 KiB
Python

from __future__ import annotations
import typing
import threading
from collections import deque
import anyio.to_thread
from ._exceptions import WebSocketQueueFullError
class SendQueue:
"""Bounded byte-size queue for outgoing WebSocket messages.
Messages are stored as pre-serialized strings. The queue enforces a
maximum byte budget so that unbounded buffering cannot occur during
reconnection windows.
"""
def __init__(self, max_bytes: int = 1_048_576) -> None:
self._queue: list[tuple[str, int]] = [] # (data, byte_length)
self._bytes: int = 0
self._max_bytes = max_bytes
self._lock = threading.Lock()
self._flush_done: threading.Event | None = None
def enqueue(self, data: str) -> None:
"""Append *data* to the queue.
Raises :class:`WebSocketQueueFullError` if the message would
exceed the byte-size limit.
"""
byte_length = len(data.encode("utf-8"))
with self._lock:
if self._bytes + byte_length > self._max_bytes:
raise WebSocketQueueFullError("send queue is full, message discarded")
self._queue.append((data, byte_length))
self._bytes += byte_length
def flush_sync(self, send: typing.Callable[[str], object], *, requeue_failed: bool = True) -> None:
"""Send every queued message via *send*.
If *send* raises, the failing message and all subsequent messages
are re-queued and the error is re-raised. When `requeue_failed` is
false, release the attempted message even on failure or interruption.
"""
while isinstance(pending := self._begin_flush(), threading.Event):
pending.wait()
try:
while pending:
data, byte_length = pending[0]
sent = False
try:
send(data)
sent = True
finally:
if sent or not requeue_failed:
with self._lock:
pending.popleft()
self._bytes -= byte_length
finally:
self._end_flush(pending)
async def flush_async(
self, send: typing.Callable[[str], typing.Awaitable[object]], *, requeue_failed: bool = True
) -> None:
"""Async variant of :meth:`flush_sync`."""
while isinstance(pending := self._begin_flush(), threading.Event):
# Waiting in a worker keeps the event loop responsive. Cancellation
# cannot strand ownership: the worker only waits, never acquires it.
await anyio.to_thread.run_sync(pending.wait, abandon_on_cancel=True)
try:
while pending:
data, byte_length = pending[0]
sent = False
try:
await send(data)
sent = True
finally:
if sent or not requeue_failed:
with self._lock:
pending.popleft()
self._bytes -= byte_length
finally:
self._end_flush(pending)
def _begin_flush(self) -> deque[tuple[str, int]] | threading.Event:
with self._lock:
if self._flush_done is not None:
return self._flush_done
pending = deque(self._queue)
self._queue.clear()
self._flush_done = threading.Event()
# Pending messages remain charged until their sends succeed.
return pending
def _end_flush(self, pending: deque[tuple[str, int]]) -> None:
with self._lock:
self._queue = list(pending) + self._queue
assert self._flush_done is not None
self._flush_done.set()
self._flush_done = None
def drain(self) -> list[str]:
"""Remove and return all queued messages."""
with self._lock:
items = [data for data, _ in self._queue]
self._bytes -= sum(byte_length for _, byte_length in self._queue)
self._queue.clear()
return items
def __len__(self) -> int:
with self._lock:
return len(self._queue)
def __bool__(self) -> bool:
with self._lock:
return len(self._queue) > 0