1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 21:02:55 +08:00

Merge pull request #19006 from peppy/realm-locking-test-safety

Fix intermittent realm migration test failures
This commit is contained in:
Dan Balasescu 2022-07-05 16:23:43 +09:00 committed by GitHub
commit 9c1fda6048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -66,7 +66,10 @@ namespace osu.Game.Database
/// </summary>
private readonly SemaphoreSlim realmRetrievalLock = new SemaphoreSlim(1);
private readonly ThreadLocal<bool> currentThreadCanCreateRealmInstances = new ThreadLocal<bool>();
/// <summary>
/// <c>true</c> when the current thread has already entered the <see cref="realmRetrievalLock"/>.
/// </summary>
private readonly ThreadLocal<bool> currentThreadHasRealmRetrievalLock = new ThreadLocal<bool>();
/// <summary>
/// Holds a map of functions registered via <see cref="RegisterCustomSubscription"/> and <see cref="RegisterForNotifications{T}"/> and a coinciding action which when triggered,
@ -584,10 +587,11 @@ namespace osu.Game.Database
try
{
if (!currentThreadCanCreateRealmInstances.Value)
// Ensure that the thread that currently has the `realmRetrievalLock` can retrieve nested contexts and not deadlock on itself.
if (!currentThreadHasRealmRetrievalLock.Value)
{
realmRetrievalLock.Wait();
currentThreadCanCreateRealmInstances.Value = true;
currentThreadHasRealmRetrievalLock.Value = true;
tookSemaphoreLock = true;
}
else
@ -611,7 +615,7 @@ namespace osu.Game.Database
if (tookSemaphoreLock)
{
realmRetrievalLock.Release();
currentThreadCanCreateRealmInstances.Value = false;
currentThreadHasRealmRetrievalLock.Value = false;
}
}
}
@ -916,16 +920,39 @@ namespace osu.Game.Database
void restoreOperation()
{
// Release of lock needs to happen here rather than on the update thread, as there may be another
// operation already blocking the update thread waiting for the blocking operation to complete.
Logger.Log(@"Restoring realm operations.", LoggingTarget.Database);
realmRetrievalLock.Release();
if (syncContext == null) return;
ManualResetEventSlim updateRealmReestablished = new ManualResetEventSlim();
// Post back to the update thread to revive any subscriptions.
// In the case we are on the update thread, let's also require this to run synchronously.
// This requirement is mostly due to test coverage, but shouldn't cause any harm.
if (ThreadSafety.IsUpdateThread)
syncContext?.Send(_ => ensureUpdateRealm(), null);
{
syncContext.Send(_ =>
{
ensureUpdateRealm();
updateRealmReestablished.Set();
}, null);
}
else
syncContext?.Post(_ => ensureUpdateRealm(), null);
{
syncContext.Post(_ =>
{
ensureUpdateRealm();
updateRealmReestablished.Set();
}, null);
}
// Wait for the post to complete to ensure a second `Migrate` operation doesn't start in the mean time.
// This is important to ensure `ensureUpdateRealm` is run before another blocking migration operation starts.
if (!updateRealmReestablished.Wait(10000))
throw new TimeoutException(@"Reestablishing update realm after block took too long");
}
}