From 8c3955d34136026b2387864223b065c7f57b185e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 9 Jan 2021 21:38:20 +0100 Subject: [PATCH] Improve safety of ongoing operation tracker Finishing an operation started via `OngoingOperationTracker.BeginOperation()` was risky in cases where the operation ended at a callback on another thread (which, in the case of multiplayer, is *most* cases). In particular, if any consumer registered a callback that mutates transforms when the operation ends, it would result in crashes after the framework-side safety checks. Rework `OngoingOperationTracker` into an always-present component residing in the drawable hierarchy, and ensure that the `operationInProgress` bindable is always updated on the update thread. This way consumers don't have to add local schedules in multiple places. --- .../TestSceneCreateMultiplayerMatchButton.cs | 7 +------ .../Screens/OnlinePlay/OngoingOperationTracker.cs | 11 +++++++++-- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 5 +++-- .../Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 3 ++- .../Multiplayer/TestMultiplayerRoomContainer.cs | 4 ++++ 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs index 381270c5aa..2f0398c6ef 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs @@ -3,18 +3,13 @@ using System; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneCreateMultiplayerMatchButton : MultiplayerTestScene { - [Cached] - private OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); - private CreateMultiplayerMatchButton button; public override void SetUpSteps() @@ -36,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer assertButtonEnableState(true); - AddStep("begin joining room", () => joiningRoomOperation = ongoingOperationTracker.BeginOperation()); + AddStep("begin joining room", () => joiningRoomOperation = OngoingOperationTracker.BeginOperation()); assertButtonEnableState(false); AddStep("end joining room", () => joiningRoomOperation.Dispose()); diff --git a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs index c34d39136e..5c9e9ce90b 100644 --- a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; namespace osu.Game.Screens.OnlinePlay { @@ -11,7 +12,7 @@ namespace osu.Game.Screens.OnlinePlay /// Utility class to track ongoing online operations' progress. /// Can be used to disable interactivity while waiting for a response from online sources. /// - public class OngoingOperationTracker + public class OngoingOperationTracker : Component { /// /// Whether there is an online operation in progress. @@ -22,6 +23,11 @@ namespace osu.Game.Screens.OnlinePlay private LeasedBindable leasedInProgress; + public OngoingOperationTracker() + { + AlwaysPresent = true; + } + /// /// Begins tracking a new online operation. /// @@ -37,7 +43,8 @@ namespace osu.Game.Screens.OnlinePlay leasedInProgress = inProgress.BeginLease(true); leasedInProgress.Value = true; - return new InvokeOnDisposal(endOperation); + // for extra safety, marshal the end of operation back to the update thread if necessary. + return new InvokeOnDisposal(() => Scheduler.Add(endOperation, false)); } private void endOperation() diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index b73e0a7c52..71fd0d5c76 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); [Cached] - private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); + private OngoingOperationTracker ongoingOperationTracker { get; set; } [Resolved(CanBeNull = true)] private MusicController music { get; set; } @@ -144,7 +144,8 @@ namespace osu.Game.Screens.OnlinePlay }; button.Action = () => OpenNewRoom(); }), - RoomManager = CreateRoomManager() + RoomManager = CreateRoomManager(), + ongoingOperationTracker = new OngoingOperationTracker() } }; diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 75ddb34685..a87b22affe 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public Bindable Filter { get; } [Cached] - public OngoingOperationTracker OngoingOperationTracker { get; } = new OngoingOperationTracker(); + public OngoingOperationTracker OngoingOperationTracker { get; } protected override Container Content => content; private readonly TestMultiplayerRoomContainer content; @@ -39,6 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Client = content.Client; RoomManager = content.RoomManager; Filter = content.Filter; + OngoingOperationTracker = content.OngoingOperationTracker; } [SetUp] diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs index ad3e2f7105..860caef071 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs @@ -25,6 +25,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached] public readonly Bindable Filter = new Bindable(new FilterCriteria()); + [Cached] + public readonly OngoingOperationTracker OngoingOperationTracker; + public TestMultiplayerRoomContainer() { RelativeSizeAxes = Axes.Both; @@ -33,6 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Client = new TestMultiplayerClient(), RoomManager = new TestMultiplayerRoomManager(), + OngoingOperationTracker = new OngoingOperationTracker(), content = new Container { RelativeSizeAxes = Axes.Both } }); }