From f5ba746ae5522e6c1ba34f6a34218491ff4aa626 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 17:31:28 +0900 Subject: [PATCH 1/3] Fail all API requests sent to DummyAPIAccess Until now, API requests sent to dummy API were just lost in the void. In most cases this somehow worked as expected, but any logic which is waiting on a request to finish will potentially never get a response. Going forward, I'm not 100% sure that every `Wait` on a web response will have local timeout logic (I think there is a certain amount of assumption that this is being managed for us by `APIAccess`), so I've made this change to better handle such cases going forward. Now, rather than nothing happening, requests will trigger a failure via the existing exception logic rather than silently pretending the request never arrived. --- osu.Game/Online/API/APIRequest.cs | 8 ++++++-- osu.Game/Online/API/DummyAPIAccess.cs | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index a7174324d8..16d1b3ab17 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -181,9 +181,13 @@ namespace osu.Game.Online.API /// Whether we are in a failed or cancelled state. private bool checkAndScheduleFailure() { - if (API == null || pendingFailure == null) return cancelled; + if (pendingFailure == null) return cancelled; + + if (API == null) + pendingFailure(); + else + API.Schedule(pendingFailure); - API.Schedule(pendingFailure); pendingFailure = null; return true; } diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 943b52db88..3cb22381c1 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -55,7 +55,12 @@ namespace osu.Game.Online.API public virtual void Queue(APIRequest request) { - HandleRequest?.Invoke(request); + if (HandleRequest != null) + HandleRequest.Invoke(request); + else + // this will fail due to not receiving an APIAccess, and trigger a failure on the request. + // this is intended - any request in testing that needs non-failures should use HandleRequest. + request.Perform(this); } public void Perform(APIRequest request) => HandleRequest?.Invoke(request); From ce452565f45bba3106ffe89a21c63d4662e8a40e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 17:50:31 +0900 Subject: [PATCH 2/3] Avoid firing any kind of failures after success --- osu.Game/Online/API/APIRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 16d1b3ab17..1a6868cfa4 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -131,8 +131,11 @@ namespace osu.Game.Online.API { } + private bool succeeded; + internal virtual void TriggerSuccess() { + succeeded = true; Success?.Invoke(); } @@ -145,10 +148,7 @@ namespace osu.Game.Online.API public void Fail(Exception e) { - if (WebRequest?.Completed == true) - return; - - if (cancelled) + if (succeeded || cancelled) return; cancelled = true; From aeff9bd8531962e7d509623407a819f3c7f69ae8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 18:08:32 +0900 Subject: [PATCH 3/3] Add return bool to HandleRequest to better trigger failures --- .../Online/TestDummyAPIRequestHandling.cs | 8 ++++++-- .../TestSceneSeasonalBackgroundLoader.cs | 4 +++- .../Online/TestSceneBeatmapListingOverlay.cs | 13 ++++++------ .../Online/TestSceneChangelogOverlay.cs | 6 ++++-- .../Visual/Online/TestSceneChatOverlay.cs | 20 +++++++++++++++++++ .../Online/TestSceneCommentsContainer.cs | 3 ++- .../Visual/Online/TestSceneNewsOverlay.cs | 3 ++- .../TestScenePlaylistsResultsScreen.cs | 13 ++++++++++++ .../TestSceneBeatmapRecommendations.cs | 4 +++- osu.Game/Online/API/DummyAPIAccess.cs | 9 +++++---- .../Multiplayer/TestMultiplayerRoomManager.cs | 18 +++++++++-------- 11 files changed, 75 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs index 42948c3731..aa29d76843 100644 --- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs +++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs @@ -23,8 +23,10 @@ namespace osu.Game.Tests.Online { case CommentVoteRequest cRequest: cRequest.TriggerSuccess(new CommentBundle()); - break; + return true; } + + return false; }); CommentVoteRequest request = null; @@ -108,8 +110,10 @@ namespace osu.Game.Tests.Online { case LeaveChannelRequest cRequest: cRequest.TriggerSuccess(); - break; + return true; } + + return false; }); } } diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs index e7cf830db0..dc5a4f4a3e 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs @@ -135,13 +135,15 @@ namespace osu.Game.Tests.Visual.Background dummyAPI.HandleRequest = request => { if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest)) - return; + return false; backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds { Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(), EndDate = endDate }); + + return true; }; }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 1349264bf9..156d6b744e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -30,13 +30,14 @@ namespace osu.Game.Tests.Visual.Online ((DummyAPIAccess)API).HandleRequest = req => { - if (req is SearchBeatmapSetsRequest searchBeatmapSetsRequest) + if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false; + + searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse { - searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse - { - BeatmapSets = setsForResponse, - }); - } + BeatmapSets = setsForResponse, + }); + + return true; }; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index cd2c4e9346..8818ac75b1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -63,13 +63,15 @@ namespace osu.Game.Tests.Visual.Online Builds = builds.Values.ToList() }; changelogRequest.TriggerSuccess(changelogResponse); - break; + return true; case GetChangelogBuildRequest buildRequest: if (requestedBuild != null) buildRequest.TriggerSuccess(requestedBuild); - break; + return true; } + + return false; }; Child = changelog = new TestChangelogOverlay(); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index fca642ad6c..b13dd34ebc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -11,6 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Chat.Selection; @@ -64,6 +66,24 @@ namespace osu.Game.Tests.Visual.Online }); } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => + { + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case JoinChannelRequest _: + return true; + } + + return false; + }; + }); + } + [Test] public void TestHideOverlay() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index c2a18330c9..cd22bb2513 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -85,9 +85,10 @@ namespace osu.Game.Tests.Visual.Online dummyAPI.HandleRequest = request => { if (!(request is GetCommentsRequest getCommentsRequest)) - return; + return false; getCommentsRequest.TriggerSuccess(commentBundle); + return true; }; }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index 37d51c16d2..6ebe8fcc07 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -33,9 +33,10 @@ namespace osu.Game.Tests.Visual.Online dummyAPI.HandleRequest = request => { if (!(request is GetNewsRequest getNewsRequest)) - return; + return false; getNewsRequest.TriggerSuccess(r); + return true; }; }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index be8032cde8..61d49e4018 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -170,6 +170,17 @@ namespace osu.Game.Tests.Visual.Playlists private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => { + // pre-check for requests we should be handling (as they are scheduled below). + switch (request) + { + case ShowPlaylistUserScoreRequest _: + case IndexPlaylistScoresRequest _: + break; + + default: + return false; + } + requestComplete = false; double delay = delayed ? 3000 : 0; @@ -196,6 +207,8 @@ namespace osu.Game.Tests.Visual.Playlists break; } }, delay); + + return true; }; private void triggerSuccess(APIRequest req, T result) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 53a956c77c..5e2d5eba5d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -32,8 +32,10 @@ namespace osu.Game.Tests.Visual.SongSelect { case GetUserRequest userRequest: userRequest.TriggerSuccess(getUser(userRequest.Ruleset.ID)); - break; + return true; } + + return false; }; }); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 3cb22381c1..52f2365165 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -34,8 +34,9 @@ namespace osu.Game.Online.API /// /// Provide handling logic for an arbitrary API request. + /// Should return true is a request was handled. If null or false return, the request will be failed with a . /// - public Action HandleRequest; + public Func HandleRequest; private readonly Bindable state = new Bindable(APIState.Online); @@ -55,12 +56,12 @@ namespace osu.Game.Online.API public virtual void Queue(APIRequest request) { - if (HandleRequest != null) - HandleRequest.Invoke(request); - else + if (HandleRequest?.Invoke(request) != true) + { // this will fail due to not receiving an APIAccess, and trigger a failure on the request. // this is intended - any request in testing that needs non-failures should use HandleRequest. request.Perform(this); + } } public void Perform(APIRequest request) => HandleRequest?.Invoke(request); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 7e824c4d7c..315be510a3 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -52,15 +52,15 @@ namespace osu.Game.Tests.Visual.Multiplayer Rooms.Add(createdRoom); createRoomRequest.TriggerSuccess(createdRoom); - break; + return true; case JoinRoomRequest joinRoomRequest: joinRoomRequest.TriggerSuccess(); - break; + return true; case PartRoomRequest partRoomRequest: partRoomRequest.TriggerSuccess(); - break; + return true; case GetRoomsRequest getRoomsRequest: var roomsWithoutParticipants = new List(); @@ -76,11 +76,11 @@ namespace osu.Game.Tests.Visual.Multiplayer } getRoomsRequest.TriggerSuccess(roomsWithoutParticipants); - break; + return true; case GetRoomRequest getRoomRequest: getRoomRequest.TriggerSuccess(Rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId)); - break; + return true; case GetBeatmapSetRequest getBeatmapSetRequest: var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type); @@ -89,11 +89,11 @@ namespace osu.Game.Tests.Visual.Multiplayer // Get the online API from the game's dependencies. game.Dependencies.Get().Queue(onlineReq); - break; + return true; case CreateRoomScoreRequest createRoomScoreRequest: createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 }); - break; + return true; case SubmitRoomScoreRequest submitRoomScoreRequest: submitRoomScoreRequest.TriggerSuccess(new MultiplayerScore @@ -108,8 +108,10 @@ namespace osu.Game.Tests.Visual.Multiplayer User = api.LocalUser.Value, Statistics = new Dictionary() }); - break; + return true; } + + return false; }; }