import logging import threading from tornado.ioloop import IOLoop 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): self._handlers = set() self._lock = threading.Lock() self._loop = IOLoop.current() def register(self, handler): with self._lock: self._handlers.add(handler) def unregister(self, handler): with self._lock: self._handlers.discard(handler) def publish(self, value): self._loop.add_callback(self._fan_out, value) def _fan_out(self, value): 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. logging.debug("Failed to push to an SSE client; dropping it") self.unregister(handler)