feat(signalr): graceful state manager

This commit is contained in:
MingxuanGame
2025-07-28 08:46:20 +00:00
parent 722a6e57d8
commit f60283a6c2
9 changed files with 234 additions and 109 deletions

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import asyncio
from collections.abc import Coroutine
from typing import override
from app.database.relationship import Relationship, RelationshipType
from app.dependencies.database import engine
@@ -16,32 +17,32 @@ from sqlmodel.ext.asyncio.session import AsyncSession
ONLINE_PRESENCE_WATCHERS_GROUP = "metadata:online-presence-watchers"
class MetadataHub(Hub):
class MetadataHub(Hub[MetadataClientState]):
def __init__(self) -> None:
super().__init__()
self.state: dict[int, MetadataClientState] = {}
@staticmethod
def online_presence_watchers_group() -> str:
return ONLINE_PRESENCE_WATCHERS_GROUP
def broadcast_tasks(
self, user_id: int, store: MetadataClientState
self, user_id: int, store: MetadataClientState | None
) -> set[Coroutine]:
if not store.pushable:
if store is not None and not store.pushable:
return set()
data = store.to_dict() if store else None
return {
self.broadcast_group_call(
self.online_presence_watchers_group(),
"UserPresenceUpdated",
user_id,
store.to_dict(),
data,
),
self.broadcast_group_call(
self.friend_presence_watchers_group(user_id),
"FriendPresenceUpdated",
user_id,
store.to_dict(),
data,
),
}
@@ -49,11 +50,21 @@ class MetadataHub(Hub):
def friend_presence_watchers_group(user_id: int):
return f"metadata:friend-presence-watchers:{user_id}"
@override
async def _clean_state(self, state: MetadataClientState) -> None:
if state.pushable:
await asyncio.gather(*self.broadcast_tasks(int(state.connection_id), None))
@override
def create_state(self, client: Client) -> MetadataClientState:
return MetadataClientState(
connection_id=client.connection_id,
connection_token=client.connection_token,
)
async def on_client_connect(self, client: Client) -> None:
user_id = int(client.connection_id)
if store := self.state.get(user_id):
store = MetadataClientState()
self.state[user_id] = store
self.get_or_create_state(client)
async with AsyncSession(engine) as session:
async with session.begin():
@@ -73,6 +84,7 @@ class MetadataHub(Hub):
if (
friend_state := self.state.get(friend_id)
) and friend_state.pushable:
print("Pushed")
tasks.append(
self.broadcast_group_call(
self.friend_presence_watchers_group(friend_id),
@@ -86,14 +98,10 @@ class MetadataHub(Hub):
async def UpdateStatus(self, client: Client, status: int) -> None:
status_ = OnlineStatus(status)
user_id = int(client.connection_id)
store = self.state.get(user_id)
if store:
if store.status is not None and store.status == status_:
return
store.status = OnlineStatus(status_)
else:
store = MetadataClientState(status=OnlineStatus(status_))
self.state[user_id] = store
store = self.get_or_create_state(client)
if store.status is not None and store.status == status_:
return
store.status = OnlineStatus(status_)
tasks = self.broadcast_tasks(user_id, store)
tasks.add(
self.call_noblock(
@@ -112,14 +120,8 @@ class MetadataHub(Hub):
if activity_dict
else None
)
store = self.state.get(user_id)
if store:
store.user_activity = activity
else:
store = MetadataClientState(
user_activity=activity,
)
self.state[user_id] = store
store = self.get_or_create_state(client)
store.user_activity = activity
tasks = self.broadcast_tasks(user_id, store)
tasks.add(
self.call_noblock(
@@ -144,9 +146,7 @@ class MetadataHub(Hub):
if store.pushable
]
)
self.groups.setdefault(self.online_presence_watchers_group(), set()).add(client)
self.add_to_group(client, self.online_presence_watchers_group())
async def EndWatchingUserPresence(self, client: Client) -> None:
self.groups.setdefault(self.online_presence_watchers_group(), set()).discard(
client
)
self.remove_from_group(client, self.online_presence_watchers_group())