mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-21 14:57:42 +00:00
49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
import logging
|
|
import threading
|
|
from typing import Any
|
|
|
|
from tornado.ioloop import IOLoop
|
|
from tornado_eventsource.handler import EventSourceHandler
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SSEBroadcaster:
|
|
"""Bridge between DataStore listener callbacks (which fire on provider threads) to Tornado's async SSE handlers
|
|
(which live on the IOLoop thread) to avoid any interdependency between them."""
|
|
|
|
def __init__(self) -> None:
|
|
self._handlers: set[EventSourceHandler] = set()
|
|
self._lock = threading.Lock()
|
|
self._loop: IOLoop | None = None
|
|
|
|
def bind_to_web_server_loop(self) -> None:
|
|
self._loop = IOLoop.current()
|
|
|
|
def register(self, handler: EventSourceHandler) -> None:
|
|
with self._lock:
|
|
self._handlers.add(handler)
|
|
|
|
def unregister(self, handler: EventSourceHandler) -> None:
|
|
with self._lock:
|
|
self._handlers.discard(handler)
|
|
|
|
@property
|
|
def client_count(self) -> int:
|
|
with self._lock:
|
|
return len(self._handlers)
|
|
|
|
def publish(self, value: Any) -> None:
|
|
self._loop.add_callback(self._broadcast, value)
|
|
|
|
def _broadcast(self, value: Any) -> None:
|
|
with self._lock:
|
|
handlers = list(self._handlers)
|
|
for handler in handlers:
|
|
try:
|
|
handler.callback(value)
|
|
except Exception:
|
|
# Connection probably dropped, ignore and de-register the handler to stop getting future items.
|
|
logger.debug("Failed to push to an SSE client; dropping it", exc_info=True)
|
|
self.unregister(handler)
|