1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-24 06:07:25 +08:00
osu-lazer/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs
Dean Herbert 1a22377e19 Fix test deadlock due to TPL threadpool saturation
As found in aggressive CI runs:

```csharp
00007F6F527F32B0 00007f74f937c72d (MethodDesc 00007f74f5545a78 + 0x22d System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken)), calling (MethodDesc 00007f74f51c0838 + 0 System.Threading.Monitor.Wait(System.Object, Int32))
00007F6F527F3330 00007f74f93861ca (MethodDesc 00007f74f5259100 + 0x10a System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken)), calling (MethodDesc 00007f74f5545a78 + 0 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken))
00007F6F527F3390 00007f74f9385d11 (MethodDesc 00007f74f52590e8 + 0x81 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken)), calling (MethodDesc 00007f74f5259100 + 0 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken))
00007F6F527F33E0 00007f74fc3823af (MethodDesc 00007f74f5934c08 + 0x4f System.Threading.Tasks.Task`1[[System.Boolean, System.Private.CoreLib]].GetResultCore(Boolean)), calling (MethodDesc 00007f74f52590e8 + 0 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken))
00007F6F527F3400 00007f74fb0aad92 (MethodDesc 00007f74fb28b000 + 0x122 osu.Game.Tests.Visual.OnlinePlay.OnlinePlayTestScene+<>c__DisplayClass21_0.<Setup>b__1(osu.Game.Online.API.APIRequest)), calling (MethodDesc 00007f74f5934be8 + 0 System.Threading.Tasks.Task`1[[System.Boolean, System.Private.CoreLib]].get_Result())
00007F6F527F3470 00007f74fa9c6dd7 (MethodDesc 00007f74f921f6c8 + 0x77 osu.Game.Online.API.DummyAPIAccess.PerformAsync(osu.Game.Online.API.APIRequest))
00007F6F527F34C0 00007f74fb13c718 (MethodDesc 00007f74fb4e0c50 + 0x538 osu.Game.Database.OnlineLookupCache`3+<performLookup>d__13[[System.Int32, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].MoveNext())
```

Going with the simplest solution, as what we had was pretty ugly to start with and I don't want to overthink this, just push a fix and hope for the best.
2022-06-26 16:42:29 +09:00

133 lines
5.8 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay;
namespace osu.Game.Tests.Visual.OnlinePlay
{
/// <summary>
/// A base test scene for all online play components and screens.
/// </summary>
public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestSceneDependencies
{
public Bindable<Room> SelectedRoom => OnlinePlayDependencies?.SelectedRoom;
public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager;
public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies?.OngoingOperationTracker;
public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies?.AvailabilityTracker;
public TestUserLookupCache UserLookupCache => OnlinePlayDependencies?.UserLookupCache;
public BeatmapLookupCache BeatmapLookupCache => OnlinePlayDependencies?.BeatmapLookupCache;
/// <summary>
/// All dependencies required for online play components and screens.
/// </summary>
protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies;
protected override Container<Drawable> Content => content;
private readonly Container content;
private readonly Container drawableDependenciesContainer;
private DelegatedDependencyContainer dependencies;
protected OnlinePlayTestScene()
{
base.Content.AddRange(new Drawable[]
{
drawableDependenciesContainer = new Container { RelativeSizeAxes = Axes.Both },
content = new Container { RelativeSizeAxes = Axes.Both },
});
}
protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent));
return dependencies;
}
[SetUp]
public void Setup() => Schedule(() =>
{
// Reset the room dependencies to a fresh state.
drawableDependenciesContainer.Clear();
dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies();
drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents);
var handler = OnlinePlayDependencies.RequestsHandler;
// Resolving the BeatmapManager in the test scene will inject the game-wide BeatmapManager, while many test scenes cache their own BeatmapManager instead.
// To get around this, the BeatmapManager is looked up from the dependencies provided to the children of the test scene instead.
var beatmapManager = dependencies.Get<BeatmapManager>();
((DummyAPIAccess)API).HandleRequest = request =>
{
try
{
return handler.HandleRequest(request, API.LocalUser.Value, beatmapManager);
}
catch (ObjectDisposedException)
{
// These requests can be fired asynchronously, but potentially arrive after game components
// have been disposed (ie. realm in BeatmapManager).
// This only happens in tests and it's easiest to ignore them for now.
Logger.Log($"Handled {nameof(ObjectDisposedException)} in test request handling");
return true;
}
};
});
/// <summary>
/// Creates the room dependencies. Called every <see cref="Setup"/>.
/// </summary>
/// <remarks>
/// Any custom dependencies required for online play sub-classes should be added here.
/// </remarks>
protected virtual OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestSceneDependencies();
/// <summary>
/// A <see cref="IReadOnlyDependencyContainer"/> providing a mutable lookup source for online play dependencies.
/// </summary>
private class DelegatedDependencyContainer : IReadOnlyDependencyContainer
{
/// <summary>
/// The online play dependencies.
/// </summary>
public OnlinePlayTestSceneDependencies OnlinePlayDependencies { get; set; }
private readonly IReadOnlyDependencyContainer parent;
private readonly DependencyContainer injectableDependencies;
/// <summary>
/// Creates a new <see cref="DelegatedDependencyContainer"/>.
/// </summary>
/// <param name="parent">The fallback <see cref="IReadOnlyDependencyContainer"/> to use when <see cref="OnlinePlayDependencies"/> cannot satisfy a dependency.</param>
public DelegatedDependencyContainer(IReadOnlyDependencyContainer parent)
{
this.parent = parent;
injectableDependencies = new DependencyContainer(this);
}
public object Get(Type type)
=> OnlinePlayDependencies?.Get(type) ?? parent.Get(type);
public object Get(Type type, CacheInfo info)
=> OnlinePlayDependencies?.Get(type, info) ?? parent.Get(type, info);
public void Inject<T>(T instance)
where T : class
=> injectableDependencies.Inject(instance);
}
}
}