From 4a00662092a13cd1e6352400ec76403dff80f657 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 4 Mar 2025 14:02:45 +0900 Subject: [PATCH] Fix thread safety when kicking multiplayer users --- .../Online/Multiplayer/MultiplayerClient.cs | 61 ++++++++++--------- .../OnlinePlay/Multiplayer/Multiplayer.cs | 8 +-- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 59a4547e9e..91b4ed448c 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -486,18 +486,44 @@ namespace osu.Game.Online.Multiplayer }, false); } - Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) => - handleUserLeft(user, UserLeft); + Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) + { + Scheduler.Add(() => handleUserLeft(user, UserLeft), false); + return Task.CompletedTask; + } Task IMultiplayerClient.UserKicked(MultiplayerRoomUser user) { - if (LocalUser == null) - return Task.CompletedTask; + Scheduler.Add(() => + { + if (LocalUser == null) + return; - if (user.Equals(LocalUser)) - LeaveRoom(); + if (user.Equals(LocalUser)) + LeaveRoom(); - return handleUserLeft(user, UserKicked); + handleUserLeft(user, UserKicked); + }, false); + + return Task.CompletedTask; + } + + private void handleUserLeft(MultiplayerRoomUser user, Action? callback) + { + Debug.Assert(ThreadSafety.IsUpdateThread); + + if (Room == null) + return; + + Room.Users.Remove(user); + PlayingUserIds.Remove(user.UserID); + + Debug.Assert(APIRoom != null); + APIRoom.RecentParticipants = APIRoom.RecentParticipants.Where(u => u.Id != user.UserID).ToArray(); + APIRoom.ParticipantCount--; + + callback?.Invoke(user); + RoomUpdated?.Invoke(); } async Task IMultiplayerClient.Invited(int invitedBy, long roomID, string password) @@ -544,27 +570,6 @@ namespace osu.Game.Online.Multiplayer APIRoom.ParticipantCount++; } - private Task handleUserLeft(MultiplayerRoomUser user, Action? callback) - { - Scheduler.Add(() => - { - if (Room == null) - return; - - Room.Users.Remove(user); - PlayingUserIds.Remove(user.UserID); - - Debug.Assert(APIRoom != null); - APIRoom.RecentParticipants = APIRoom.RecentParticipants.Where(u => u.Id != user.UserID).ToArray(); - APIRoom.ParticipantCount--; - - callback?.Invoke(user); - RoomUpdated?.Invoke(); - }, false); - - return Task.CompletedTask; - } - Task IMultiplayerClient.HostChanged(int userId) { Scheduler.Add(() => diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index dfed32aebc..0b06a16d98 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -28,11 +28,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onRoomUpdated() { - if (client.Room == null) + if (client.Room == null || client.LocalUser == null) return; - Debug.Assert(client.LocalUser != null); - // If the user exits gameplay before score submission completes, we'll transition to idle when results has been prepared. if (client.LocalUser.State == MultiplayerUserState.Results && this.IsCurrentScreen()) transitionFromResults(); @@ -62,11 +60,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.OnResuming(e); - if (client.Room == null) + if (client.Room == null || client.LocalUser == null) return; - Debug.Assert(client.LocalUser != null); - if (!(e.Last is MultiplayerPlayerLoader playerLoader)) return;