1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 17:52:56 +08:00

Merge branch 'realm-nested-context-creation-deadlock-fix' into realm-integration/compiled

This commit is contained in:
Dean Herbert 2021-11-29 18:28:07 +09:00
commit c411a755c7
2 changed files with 55 additions and 2 deletions

View File

@ -5,6 +5,9 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Game.Database;
using osu.Game.Models;
using Realms;
#nullable enable
@ -33,6 +36,37 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestNestedContextCreation()
{
RunTestWithRealm((realmFactory, _) =>
{
var mainContext = realmFactory.Context;
bool callbackRan = false;
var subscription = mainContext.All<RealmBeatmap>().SubscribeForNotifications((sender, changes, error) =>
{
realmFactory.CreateContext();
callbackRan = true;
});
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
{
threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
// will create a context but also run the callback above (Refresh is implicitly run when getting a new context).
realmFactory.CreateContext();
Assert.IsTrue(callbackRan);
subscription.Dispose();
});
}
[Test]
public void TestBlockOperationsWithContention()
{

View File

@ -52,6 +52,8 @@ namespace osu.Game.Database
/// </summary>
private readonly SemaphoreSlim contextCreationLock = new SemaphoreSlim(1);
private readonly ThreadLocal<bool> currentThreadCanCreateContexts = new ThreadLocal<bool>();
private static readonly GlobalStatistic<int> refreshes = GlobalStatistics.Get<int>(@"Realm", @"Dirty Refreshes");
private static readonly GlobalStatistic<int> contexts_created = GlobalStatistics.Get<int>(@"Realm", @"Contexts (Created)");
@ -151,9 +153,22 @@ namespace osu.Game.Database
if (isDisposed)
throw new ObjectDisposedException(nameof(RealmContextFactory));
bool tookSemaphoreLock = false;
try
{
contextCreationLock.Wait();
if (!currentThreadCanCreateContexts.Value)
{
contextCreationLock.Wait();
tookSemaphoreLock = true;
}
else
{
// the semaphore is used to handle blocking of all context creation during certain periods.
// once the semaphore has been taken by this code section, it is safe to create further contexts on the same thread.
// this can happen if a realm subscription is active and triggers a callback which has user code that calls `CreateContext`.
currentThreadCanCreateContexts.Value = true;
}
contexts_created.Value++;
@ -161,7 +176,11 @@ namespace osu.Game.Database
}
finally
{
contextCreationLock.Release();
if (tookSemaphoreLock)
{
contextCreationLock.Release();
currentThreadCanCreateContexts.Value = false;
}
}
}