From 33cf04b1c8b3c3d3237720cbb8e8a587770a06b8 Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Sun, 17 Aug 2025 15:55:36 +0000 Subject: [PATCH 1/5] feat(log): improve SignalR log --- .gitignore | 3 ++- app/log.py | 12 ++++++++++++ app/signalr/hub/hub.py | 4 ++++ app/signalr/hub/multiplayer.py | 36 +++++++++++++++++++++++++++++++++- app/signalr/hub/spectator.py | 9 +++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 23b1cc8..5c9e495 100644 --- a/.gitignore +++ b/.gitignore @@ -52,7 +52,7 @@ coverage.xml .pytest_cache/ cover/ # 测试用的证书 -test-cert/ +test-cert/ # Translations *.mo @@ -215,3 +215,4 @@ bancho.py-master/* storage/ replays/ osu-master/* +logs/ diff --git a/app/log.py b/app/log.py index 6fe64df..4b29f17 100644 --- a/app/log.py +++ b/app/log.py @@ -127,6 +127,16 @@ logger.add( level=settings.log_level, diagnose=settings.debug, ) +logger.add( + "logs/{time:YYYY-MM-DD}.log", + rotation="00:00", + retention="30 days", + colorize=False, + format="{time:YYYY-MM-DD HH:mm:ss} {level} | {message}", + level=settings.log_level, + diagnose=settings.debug, + encoding="utf8", +) logging.basicConfig(handlers=[InterceptHandler()], level=settings.log_level, force=True) uvicorn_loggers = [ @@ -140,3 +150,5 @@ for logger_name in uvicorn_loggers: uvicorn_logger = logging.getLogger(logger_name) uvicorn_logger.handlers = [InterceptHandler()] uvicorn_logger.propagate = False + +logging.getLogger("httpx").setLevel("WARNING") diff --git a/app/signalr/hub/hub.py b/app/signalr/hub/hub.py index bbcfdfc..4d9906c 100644 --- a/app/signalr/hub/hub.py +++ b/app/signalr/hub/hub.py @@ -273,6 +273,10 @@ class Hub[TState: UserState]: result = await self.invoke_method(client, packet.target, args) except InvokeException as e: error = e.message + logger.debug( + f"Client {client.connection_token} call {packet.target}" + f" failed: {error}" + ) except Exception as e: logger.exception( f"Error invoking method {packet.target} for " diff --git a/app/signalr/hub/multiplayer.py b/app/signalr/hub/multiplayer.py index 128fb91..87eceb8 100644 --- a/app/signalr/hub/multiplayer.py +++ b/app/signalr/hub/multiplayer.py @@ -364,7 +364,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]): user = next((u for u in room.users if u.user_id == client.user_id), None) if user is None: raise InvokeException("You are not in this room") - + logger.info( + f"[MultiplayerHub] {client.user_id} adding " + f"beatmap {item.beatmap_id} to room {room.room_id}" + ) await server_room.queue.add_item( item, user, @@ -383,6 +386,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]): if user is None: raise InvokeException("You are not in this room") + logger.info( + f"[MultiplayerHub] {client.user_id} editing " + f"item {item.id} in room {room.room_id}" + ) await server_room.queue.edit_item( item, user, @@ -401,6 +408,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]): if user is None: raise InvokeException("You are not in this room") + logger.info( + f"[MultiplayerHub] {client.user_id} removing " + f"item {item_id} from room {room.room_id}" + ) await server_room.queue.remove_item( item_id, user, @@ -666,6 +677,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]): if user.state == state: return + logger.info( + f"[MultiplayerHub] {user.user_id}'s state " + f"changed from {user.state} to {state}" + ) match state: case MultiplayerUserState.IDLE: if user.state.is_playing: @@ -684,6 +699,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): or room.state == MultiplayerRoomState.WAITING_FOR_LOAD ): await self.call_noblock(client, "LoadRequested") + await self.update_room_state(server_room) async def change_user_state( @@ -767,6 +783,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]): async def change_room_state( self, room: ServerMultiplayerRoom, state: MultiplayerRoomState ): + logger.debug( + f"[MultiplayerHub] Room {room.room.room_id} state " + f"changed from {room.room.state} to {state}" + ) room.room.state = state await self.broadcast_group_call( self.group_id(room.room.room_id), @@ -799,6 +819,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): if all(u.state != MultiplayerUserState.READY for u in room.users): raise InvokeException("Can't start match when no users are ready.") + logger.info(f"[MultiplayerHub] Room {room.room_id} match started") await self.start_match(server_room) async def start_match(self, room: ServerMultiplayerRoom): @@ -971,6 +992,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): room.room.host.user_id, ) del self.rooms[room.room.room_id] + logger.info(f"[MultiplayerHub] Room {room.room.room_id} ended") async def LeaveRoom(self, client: Client): store = self.get_or_create_state(client) @@ -989,6 +1011,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): user.user_id, ) await self.make_user_leave(client, server_room, user) + logger.info(f"[MultiplayerHub] {client.user_id} left room {room.room_id}") async def KickUser(self, client: Client, user_id: int): store = self.get_or_create_state(client) @@ -1017,6 +1040,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]): if target_client is None: return await self.make_user_leave(target_client, server_room, user, kicked=True) + logger.info( + f"[MultiplayerHub] {user.user_id} was kicked from room {room.room_id}" + f"by {client.user_id}" + ) async def set_host(self, room: ServerMultiplayerRoom, user: MultiplayerRoomUser): room.room.host = user @@ -1046,6 +1073,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]): new_host.user_id, ) await self.set_host(server_room, new_host) + logger.info( + f"[MultiplayerHub] {client.user_id} transferred host to {new_host.user_id}" + f" in room {room.room_id}" + ) async def AbortGameplay(self, client: Client): store = self.get_or_create_state(client) @@ -1100,6 +1131,9 @@ class MultiplayerHub(Hub[MultiplayerClientState]): GameplayAbortReason.HOST_ABORTED, ) await self.update_room_state(server_room) + logger.info( + f"[MultiplayerHub] {client.user_id} aborted match in room {room.room_id}" + ) async def change_user_match_state( self, room: ServerMultiplayerRoom, user: MultiplayerRoomUser diff --git a/app/signalr/hub/spectator.py b/app/signalr/hub/spectator.py index 5485732..5abe880 100644 --- a/app/signalr/hub/spectator.py +++ b/app/signalr/hub/spectator.py @@ -6,6 +6,7 @@ import lzma import struct import time from typing import override +from venv import logger from app.config import settings from app.database import Beatmap, User @@ -217,6 +218,7 @@ class SpectatorHub(Hub[StoreClientState]): maximum_statistics=state.maximum_statistics, ) ) + logger.info(f"[SpectatorHub] {client.user_id} began playing {state.beatmap_id}") await self.broadcast_group_call( self.group_id(user_id), "UserBeganPlaying", @@ -320,6 +322,10 @@ class SpectatorHub(Hub[StoreClientState]): async def _end_session(self, user_id: int, state: SpectatorState) -> None: if state.state == SpectatedUserState.Playing: state.state = SpectatedUserState.Quit + logger.info( + f"[SpectatorHub] {user_id} finished playing {state.beatmap_id} " + f"with {state.state}" + ) await self.broadcast_group_call( self.group_id(user_id), "UserFinishedPlaying", @@ -330,7 +336,9 @@ class SpectatorHub(Hub[StoreClientState]): async def StartWatchingUser(self, client: Client, target_id: int) -> None: user_id = int(client.connection_id) target_store = self.state.get(target_id) + logger.info(f"[SpectatorHub] {user_id} started watching {target_id}") if target_store and target_store.state: + logger.debug(f"[SpectatorHub] {target_id} is {target_store.state}") await self.call_noblock( client, "UserBeganPlaying", @@ -361,3 +369,4 @@ class SpectatorHub(Hub[StoreClientState]): store.watched_user.discard(target_id) if (target_client := self.get_client_by_id(str(target_id))) is not None: await self.call_noblock(target_client, "UserEndedWatching", user_id) + logger.info(f"[SpectatorHub] {user_id} ended watching {target_id}") From 3476c71c3df9f174f43f2079218cf93f1f0538db Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Sun, 17 Aug 2025 15:59:21 +0000 Subject: [PATCH 2/5] fix(chat): don't record last_msg & last_read_id --- app/router/chat/message.py | 3 ++- app/router/chat/server.py | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/router/chat/message.py b/app/router/chat/message.py index 84f5c41..e6caf05 100644 --- a/app/router/chat/message.py +++ b/app/router/chat/message.py @@ -167,7 +167,8 @@ async def mark_as_read( if db_channel is None: raise HTTPException(status_code=404, detail="Channel not found") assert db_channel.channel_id - await server.mark_as_read(db_channel.channel_id, message) + assert current_user.id + await server.mark_as_read(db_channel.channel_id, current_user.id, message) class PMReq(BaseModel): diff --git a/app/router/chat/server.py b/app/router/chat/server.py index b91dd02..61e0877 100644 --- a/app/router/chat/server.py +++ b/app/router/chat/server.py @@ -69,8 +69,8 @@ class ChatServer: if client: await self.send_event(client, event) - async def mark_as_read(self, channel_id: int, message_id: int): - await self.redis.set(f"chat:{channel_id}:last_msg", message_id) + async def mark_as_read(self, channel_id: int, user_id: int, message_id: int): + await self.redis.set(f"chat:{channel_id}:last_read:{user_id}", message_id) async def send_message_to_channel( self, message: ChatMessageResp, is_bot_command: bool = False @@ -91,7 +91,10 @@ class ChatServer: ) ) assert message.message_id - await self.mark_as_read(message.channel_id, message.message_id) + await self.mark_as_read( + message.channel_id, message.sender_id, message.message_id + ) + await self.redis.set(f"chat:{message.channel_id}:last_msg", message.message_id) async def batch_join_channel( self, users: list[User], channel: ChatChannel, session: AsyncSession From 58fa355c802dee1a05f1e18ad1933fca93a511e2 Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Sun, 17 Aug 2025 16:16:59 +0000 Subject: [PATCH 3/5] refactor(multiplayer): reuse code & sync settings to database --- app/signalr/hub/multiplayer.py | 142 ++++++++++----------------------- 1 file changed, 41 insertions(+), 101 deletions(-) diff --git a/app/signalr/hub/multiplayer.py b/app/signalr/hub/multiplayer.py index 87eceb8..36c468d 100644 --- a/app/signalr/hub/multiplayer.py +++ b/app/signalr/hub/multiplayer.py @@ -336,12 +336,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): async def ChangeBeatmapAvailability( self, client: Client, beatmap_availability: BeatmapAvailability ): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) if user is None: @@ -353,12 +348,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): ) async def AddPlaylistItem(self, client: Client, item: PlaylistItem): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) @@ -374,12 +364,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): ) async def EditPlaylistItem(self, client: Client, item: PlaylistItem): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) @@ -396,12 +381,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): ) async def RemovePlaylistItem(self, client: Client, item_id: int): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) @@ -417,7 +397,25 @@ class MultiplayerHub(Hub[MultiplayerClientState]): user, ) + async def change_db_settings(self, room: ServerMultiplayerRoom): + async with AsyncSession(engine) as session: + await session.execute( + update(Room) + .where(col(Room.id) == room.room.room_id) + .values( + name=room.room.settings.name, + type=room.room.settings.match_type, + queue_mode=room.room.settings.queue_mode, + auto_skip=room.room.settings.auto_skip, + auto_start_duration=int( + room.room.settings.auto_start_duration.total_seconds() + ), + host_id=room.room.host.user_id, + ) + ) + async def setting_changed(self, room: ServerMultiplayerRoom, beatmap_changed: bool): + await self.change_db_settings(room) await self.validate_styles(room) await self.unready_all_users(room, beatmap_changed) await self.broadcast_group_call( @@ -455,12 +453,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): async def ChangeUserStyle( self, client: Client, beatmap_id: int | None, ruleset_id: int | None ): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) if user is None: @@ -583,12 +576,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): ) async def ChangeUserMods(self, client: Client, new_mods: list[APIMod]): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) if user is None: @@ -645,7 +633,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): "Cannot ready up while all items have been played." ) case MultiplayerUserState.WAITING_FOR_LOAD: - raise InvokeException("Cannot change state from {old} to {new}") + raise InvokeException(f"Cannot change state from {old} to {new}") case MultiplayerUserState.LOADED: if old != MultiplayerUserState.WAITING_FOR_LOAD: raise InvokeException(f"Cannot change state from {old} to {new}") @@ -658,18 +646,13 @@ class MultiplayerHub(Hub[MultiplayerClientState]): if old != MultiplayerUserState.PLAYING: raise InvokeException(f"Cannot change state from {old} to {new}") case MultiplayerUserState.RESULTS: - raise InvokeException("Cannot change state from {old} to {new}") + raise InvokeException(f"Cannot change state from {old} to {new}") case MultiplayerUserState.SPECTATING: if old not in (MultiplayerUserState.IDLE, MultiplayerUserState.READY): raise InvokeException(f"Cannot change state from {old} to {new}") async def ChangeState(self, client: Client, state: MultiplayerUserState): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) if user is None: @@ -795,18 +778,12 @@ class MultiplayerHub(Hub[MultiplayerClientState]): ) async def StartMatch(self, client: Client): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) if user is None: raise InvokeException("You are not in this room") - if room.host is None or room.host.user_id != client.user_id: - raise InvokeException("You are not the host of this room") + self._ensure_host(client, server_room) # Check host state - host must be ready or spectating if room.host.state not in ( @@ -998,9 +975,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): store = self.get_or_create_state(client) if store.room_id == 0: return - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) if user is None: @@ -1014,16 +989,9 @@ class MultiplayerHub(Hub[MultiplayerClientState]): logger.info(f"[MultiplayerHub] {client.user_id} left room {room.room_id}") async def KickUser(self, client: Client, user_id: int): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room - - if room.host is None or room.host.user_id != client.user_id: - raise InvokeException("You are not the host of this room") + self._ensure_host(client, server_room) if user_id == client.user_id: raise InvokeException("Can't kick self") @@ -1047,6 +1015,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): async def set_host(self, room: ServerMultiplayerRoom, user: MultiplayerRoomUser): room.room.host = user + await self.change_db_settings(room) await self.broadcast_group_call( self.group_id(room.room.room_id), "HostChanged", @@ -1054,16 +1023,9 @@ class MultiplayerHub(Hub[MultiplayerClientState]): ) async def TransferHost(self, client: Client, user_id: int): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room - - if room.host is None or room.host.user_id != client.user_id: - raise InvokeException("You are not the host of this room") + self._ensure_host(client, server_room) new_host = next((u for u in room.users if u.user_id == user_id), None) if new_host is None: @@ -1079,12 +1041,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): ) async def AbortGameplay(self, client: Client): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) if user is None: @@ -1101,16 +1058,9 @@ class MultiplayerHub(Hub[MultiplayerClientState]): await self.update_room_state(server_room) async def AbortMatch(self, client: Client): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room - - if room.host is None or room.host.user_id != client.user_id: - raise InvokeException("You are not the host of this room") + self._ensure_host(client, server_room) if ( room.state != MultiplayerRoomState.PLAYING @@ -1175,12 +1125,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): await self.update_room_state(server_room) async def SendMatchRequest(self, client: Client, request: MatchRequest): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) if user is None: @@ -1190,7 +1135,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): if room.host and room.host.user_id != user.user_id: raise InvokeException("You are not the host of this room") if room.state != MultiplayerRoomState.OPEN: - raise InvokeException("Cannot start a countdown during ongoing play") + raise InvokeException("Cannot start match countdown when not open") await server_room.start_countdown( MatchStartCountdown(time_remaining=request.duration), self.start_match, @@ -1215,12 +1160,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]): await server_room.match_type_handler.handle_request(user, request) async def InvitePlayer(self, client: Client, user_id: int): - store = self.get_or_create_state(client) - if store.room_id == 0: - raise InvokeException("You are not in a room") - if store.room_id not in self.rooms: - raise InvokeException("Room does not exist") - server_room = self.rooms[store.room_id] + server_room = self._ensure_in_room(client) room = server_room.room user = next((u for u in room.users if u.user_id == client.user_id), None) if user is None: From 103ce7b2e54bd9a9f88b08ec8478b67791a141ac Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Sun, 17 Aug 2025 16:42:19 +0000 Subject: [PATCH 4/5] fix(multiplayer): handle user leave logic and improve room state updates --- app/signalr/hub/multiplayer.py | 57 +++++++++++++++++----------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/app/signalr/hub/multiplayer.py b/app/signalr/hub/multiplayer.py index 36c468d..ba87e14 100644 --- a/app/signalr/hub/multiplayer.py +++ b/app/signalr/hub/multiplayer.py @@ -892,34 +892,21 @@ class MultiplayerHub(Hub[MultiplayerClientState]): async def make_user_leave( self, - client: Client, + client: Client | None, room: ServerMultiplayerRoom, user: MultiplayerRoomUser, kicked: bool = False, ): - self.remove_from_group(client, self.group_id(room.room.room_id)) + if client: + self.remove_from_group(client, self.group_id(room.room.room_id)) room.room.users.remove(user) - if len(room.room.users) == 0: - await self.end_room(room) - await self.update_room_state(room) - if ( - len(room.room.users) != 0 - and room.room.host - and room.room.host.user_id == user.user_id - ): - next_host = room.room.users[0] - await self.set_host(room, next_host) + target_store = self.state.get(user.user_id) + if target_store: + target_store.room_id = 0 - if kicked: - await self.call_noblock(client, "UserKicked", user) - await self.broadcast_group_call( - self.group_id(room.room.room_id), "UserKicked", user - ) - else: - await self.broadcast_group_call( - self.group_id(room.room.room_id), "UserLeft", user - ) + redis = get_redis() + await redis.publish("chat:room:left", f"{room.room.channel_id}:{user.user_id}") async with AsyncSession(engine) as session: async with session.begin(): @@ -939,12 +926,28 @@ class MultiplayerHub(Hub[MultiplayerClientState]): raise InvokeException("Room does not exist in database") db_room.participant_count -= 1 - target_store = self.state.get(user.user_id) - if target_store: - target_store.room_id = 0 + if len(room.room.users) == 0: + await self.end_room(room) + return + await self.update_room_state(room) + if ( + len(room.room.users) != 0 + and room.room.host + and room.room.host.user_id == user.user_id + ): + next_host = room.room.users[0] + await self.set_host(room, next_host) - redis = get_redis() - await redis.publish("chat:room:left", f"{room.room.channel_id}:{user.user_id}") + if kicked: + if client: + await self.call_noblock(client, "UserKicked", user) + await self.broadcast_group_call( + self.group_id(room.room.room_id), "UserKicked", user + ) + else: + await self.broadcast_group_call( + self.group_id(room.room.room_id), "UserLeft", user + ) async def end_room(self, room: ServerMultiplayerRoom): assert room.room.host @@ -1005,8 +1008,6 @@ class MultiplayerHub(Hub[MultiplayerClientState]): user.user_id, ) target_client = self.get_client_by_id(str(user.user_id)) - if target_client is None: - return await self.make_user_leave(target_client, server_room, user, kicked=True) logger.info( f"[MultiplayerHub] {user.user_id} was kicked from room {room.room_id}" From 05aef00512585d7faee191dfb351d7f616792a9a Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Sun, 17 Aug 2025 16:43:19 +0000 Subject: [PATCH 5/5] fix(signalr): do not show exception to client --- app/signalr/hub/hub.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/signalr/hub/hub.py b/app/signalr/hub/hub.py index 4d9906c..cfa4739 100644 --- a/app/signalr/hub/hub.py +++ b/app/signalr/hub/hub.py @@ -277,12 +277,12 @@ class Hub[TState: UserState]: f"Client {client.connection_token} call {packet.target}" f" failed: {error}" ) - except Exception as e: + except Exception: logger.exception( f"Error invoking method {packet.target} for " f"client {client.connection_id}" ) - error = str(e) + error = "Unknown error occured in server" if packet.invocation_id is not None: await client.send_packet( CompletionPacket(