90 lines
2.7 KiB
Python
90 lines
2.7 KiB
Python
"""斗鱼外部单协作台账的进程内实时事件总线。
|
|
|
|
写接口提交成功后只发布「台账失效」事件,前端收到后按自己的筛选静默重拉,
|
|
不在事件里携带整行数据。当前生产单 worker,进程内队列足够;多实例时再换 Redis。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import itertools
|
|
import threading
|
|
from datetime import UTC, datetime
|
|
from typing import Any
|
|
|
|
|
|
class CollabEventHub:
|
|
def __init__(self) -> None:
|
|
self._lock = threading.Lock()
|
|
self._revisions = itertools.count(1)
|
|
self._current_revision = 0
|
|
self._loop: asyncio.AbstractEventLoop | None = None
|
|
self._subscribers: set[asyncio.Queue[dict[str, Any]]] = set()
|
|
|
|
def subscribe(self) -> asyncio.Queue[dict[str, Any]]:
|
|
loop = asyncio.get_running_loop()
|
|
queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue(maxsize=100)
|
|
with self._lock:
|
|
self._loop = loop
|
|
self._subscribers.add(queue)
|
|
return queue
|
|
|
|
def unsubscribe(self, queue: asyncio.Queue[dict[str, Any]]) -> None:
|
|
with self._lock:
|
|
self._subscribers.discard(queue)
|
|
|
|
def ready_event(self) -> dict[str, Any]:
|
|
with self._lock:
|
|
revision = self._current_revision
|
|
return {"type": "collab.ready", "revision": revision}
|
|
|
|
def ping_event(self) -> dict[str, Any]:
|
|
with self._lock:
|
|
revision = self._current_revision
|
|
return {"type": "collab.ping", "revision": revision}
|
|
|
|
def publish(
|
|
self,
|
|
*,
|
|
scope: str,
|
|
action: str,
|
|
actor_id: int | None = None,
|
|
) -> dict[str, Any]:
|
|
with self._lock:
|
|
revision = next(self._revisions)
|
|
self._current_revision = revision
|
|
loop = self._loop
|
|
subscribers = list(self._subscribers)
|
|
|
|
event = {
|
|
"type": "collab.invalidate",
|
|
"revision": revision,
|
|
"scope": scope,
|
|
"action": action,
|
|
"actor_id": actor_id,
|
|
"at": datetime.now(UTC).isoformat(),
|
|
}
|
|
if loop is None or not subscribers:
|
|
return event
|
|
for queue in subscribers:
|
|
try:
|
|
loop.call_soon_threadsafe(self._offer, queue, event)
|
|
except RuntimeError:
|
|
continue
|
|
return event
|
|
|
|
@staticmethod
|
|
def _offer(queue: asyncio.Queue[dict[str, Any]], event: dict[str, Any]) -> None:
|
|
while queue.full():
|
|
try:
|
|
queue.get_nowait()
|
|
except asyncio.QueueEmpty:
|
|
break
|
|
try:
|
|
queue.put_nowait(event)
|
|
except asyncio.QueueFull:
|
|
pass
|
|
|
|
|
|
collab_events = CollabEventHub()
|