From 9ba99ed57d0abc9e67ee9bbab633077a7103976c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Apr 2022 14:42:54 +0900 Subject: [PATCH 1/5] Ensure all access to `MultiplayerClient.Room` is on the update thread This was an implicit requirement until now, but not well documented everywhere. Adding this makes it much easier to understand the requirement (and probably safer). --- .../Online/Multiplayer/MultiplayerClient.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index d6099e5f72..2084ba048d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Database; @@ -87,7 +88,21 @@ namespace osu.Game.Online.Multiplayer /// /// The joined . /// - public MultiplayerRoom? Room { get; private set; } + public MultiplayerRoom? Room + { + get + { + Debug.Assert(ThreadSafety.IsUpdateThread); + return room; + } + private set + { + Debug.Assert(ThreadSafety.IsUpdateThread); + room = value; + } + } + + private MultiplayerRoom? room; /// /// The users in the joined which are participating in the current gameplay loop. From d50f41225ffe644d1abb31681637c0cebeeacc38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Apr 2022 14:43:53 +0900 Subject: [PATCH 2/5] Rename `scheduleAsync` to `runOnUpdateThreadAsync` --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 2084ba048d..5cbf65478c 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -181,7 +181,7 @@ namespace osu.Game.Online.Multiplayer await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false); // Update the stored room (must be done on update thread for thread-safety). - await scheduleAsync(() => + await runOnUpdateThreadAsync(() => { Room = joinedRoom; APIRoom = room; @@ -228,7 +228,7 @@ namespace osu.Game.Online.Multiplayer // Leaving rooms is expected to occur instantaneously whilst the operation is finalised in the background. // However a few members need to be reset immediately to prevent other components from entering invalid states whilst the operation hasn't yet completed. // For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time. - var scheduledReset = scheduleAsync(() => + var scheduledReset = runOnUpdateThreadAsync(() => { APIRoom = null; Room = null; @@ -799,7 +799,7 @@ namespace osu.Game.Online.Multiplayer PlayingUserIds.Remove(userId); } - private Task scheduleAsync(Action action, CancellationToken cancellationToken = default) + private Task runOnUpdateThreadAsync(Action action, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource(); From 64c63fe93a4ae35a01a86ab5c25a0e65a633a036 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Apr 2022 14:52:56 +0900 Subject: [PATCH 3/5] Move null check in `JoinRoom` on to update thread --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 5cbf65478c..996a4e312f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -163,13 +163,13 @@ namespace osu.Game.Online.Multiplayer /// An optional password to use for the join operation. public async Task JoinRoom(Room room, string? password = null) { + if (Room != null) + throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); + var cancellationSource = joinCancellationSource = new CancellationTokenSource(); await joinOrLeaveTaskChain.Add(async () => { - if (Room != null) - throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); - Debug.Assert(room.RoomID.Value != null); // Join the server-side room. @@ -183,6 +183,8 @@ namespace osu.Game.Online.Multiplayer // Update the stored room (must be done on update thread for thread-safety). await runOnUpdateThreadAsync(() => { + Debug.Assert(Room == null); + Room = joinedRoom; APIRoom = room; From 24c2d465a9c04f61aad3fde9c87243a458447505 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Apr 2022 14:53:14 +0900 Subject: [PATCH 4/5] Move null assert in `MultiplayerPlayer` on to update thread --- osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 043315c790..70f8f1b752 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -133,6 +133,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer failAndBail(); } }), true); + } + + protected override void LoadComplete() + { + base.LoadComplete(); Debug.Assert(client.Room != null); } From 933a722cfcecb2911fcb2db9ebecfb1e50b76922 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Apr 2022 14:56:04 +0900 Subject: [PATCH 5/5] Remove secondary null checks which cannot exist (were on wrong thread) --- .../Online/Multiplayer/MultiplayerClient.cs | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 996a4e312f..7808d8939c 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -360,9 +360,6 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -395,9 +392,6 @@ namespace osu.Game.Online.Multiplayer async Task IMultiplayerClient.UserJoined(MultiplayerRoomUser user) { - if (Room == null) - return; - await PopulateUser(user).ConfigureAwait(false); Scheduler.Add(() => @@ -446,9 +440,6 @@ namespace osu.Game.Online.Multiplayer private Task handleUserLeft(MultiplayerRoomUser user, Action? callback) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -470,9 +461,6 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.HostChanged(int userId) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -503,9 +491,6 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.UserStateChanged(int userId, MultiplayerUserState state) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -523,9 +508,6 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.MatchUserStateChanged(int userId, MatchUserState state) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -540,9 +522,6 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.MatchRoomStateChanged(MatchRoomState state) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -557,9 +536,6 @@ namespace osu.Game.Online.Multiplayer public Task MatchEvent(MatchServerEvent e) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -580,9 +556,6 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { var user = Room?.Users.SingleOrDefault(u => u.UserID == userId); @@ -601,9 +574,6 @@ namespace osu.Game.Online.Multiplayer public Task UserModsChanged(int userId, IEnumerable mods) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { var user = Room?.Users.SingleOrDefault(u => u.UserID == userId); @@ -622,9 +592,6 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.LoadRequested() { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -638,9 +605,6 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.MatchStarted() { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -654,9 +618,6 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.ResultsReady() { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -670,9 +631,6 @@ namespace osu.Game.Online.Multiplayer public Task PlaylistItemAdded(MultiplayerPlaylistItem item) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -692,9 +650,6 @@ namespace osu.Game.Online.Multiplayer public Task PlaylistItemRemoved(long playlistItemId) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null) @@ -716,9 +671,6 @@ namespace osu.Game.Online.Multiplayer public Task PlaylistItemChanged(MultiplayerPlaylistItem item) { - if (Room == null) - return Task.CompletedTask; - Scheduler.Add(() => { if (Room == null)