1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-06 05:53:11 +08:00

Merge pull request #15853 from peppy/realm-nested-context-creation-deadlock-fix

Fix potential deadlock on nested context creation requests
This commit is contained in:
Dan Balasescu 2021-11-30 18:53:57 +09:00 committed by GitHub
commit f849ff67d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 2 deletions

View File

@ -5,6 +5,8 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Game.Models;
using Realms;
#nullable enable
@ -33,6 +35,39 @@ namespace osu.Game.Tests.Database
});
}
/// <summary>
/// Test to ensure that a `CreateContext` call nested inside a subscription doesn't cause any deadlocks
/// due to context fetching semaphores.
/// </summary>
[Test]
public void TestNestedContextCreationWithSubscription()
{
RunTestWithRealm((realmFactory, _) =>
{
bool callbackRan = false;
using (var context = realmFactory.CreateContext())
{
var subscription = context.All<RealmBeatmap>().SubscribeForNotifications((sender, changes, error) =>
{
using (realmFactory.CreateContext())
{
callbackRan = true;
}
});
// Force the callback above to run.
using (realmFactory.CreateContext())
{
}
subscription.Dispose();
}
Assert.IsTrue(callbackRan);
});
}
[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,17 +153,34 @@ namespace osu.Game.Database
if (isDisposed)
throw new ObjectDisposedException(nameof(RealmContextFactory));
bool tookSemaphoreLock = false;
try
{
if (!currentThreadCanCreateContexts.Value)
{
contextCreationLock.Wait();
currentThreadCanCreateContexts.Value = true;
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`.
}
contexts_created.Value++;
return Realm.GetInstance(getConfiguration());
}
finally
{
if (tookSemaphoreLock)
{
contextCreationLock.Release();
currentThreadCanCreateContexts.Value = false;
}
}
}