Merge branch 'realm-clean-up' into realm-block-timeout-assert-failure

This commit is contained in:
Dean Herbert
2022-01-25 13:58:14 +09:00
51 changed files with 212 additions and 230 deletions

View File

@ -101,15 +101,15 @@ namespace osu.Game.Database
{
using (var ef = efContextFactory.Get())
{
realm.Write(realm =>
realm.Write(r =>
{
// Before beginning, ensure realm is in an empty state.
// Migrations which are half-completed could lead to issues if the user tries a second time.
// Note that we only do this for beatmaps and scores since the other migrations are yonks old.
realm.RemoveAll<BeatmapSetInfo>();
realm.RemoveAll<BeatmapInfo>();
realm.RemoveAll<BeatmapMetadata>();
realm.RemoveAll<ScoreInfo>();
r.RemoveAll<BeatmapSetInfo>();
r.RemoveAll<BeatmapInfo>();
r.RemoveAll<BeatmapMetadata>();
r.RemoveAll<ScoreInfo>();
});
migrateSettings(ef);
@ -158,11 +158,11 @@ namespace osu.Game.Database
int count = existingBeatmapSets.Count();
realm.Run(realm =>
realm.Run(r =>
{
log($"Found {count} beatmaps in EF");
var transaction = realm.BeginWrite();
var transaction = r.BeginWrite();
int written = 0;
try
@ -172,7 +172,7 @@ namespace osu.Game.Database
if (++written % 1000 == 0)
{
transaction.Commit();
transaction = realm.BeginWrite();
transaction = r.BeginWrite();
log($"Migrated {written}/{count} beatmaps...");
}
@ -186,11 +186,11 @@ namespace osu.Game.Database
Protected = beatmapSet.Protected,
};
migrateFiles(beatmapSet, realm, realmBeatmapSet);
migrateFiles(beatmapSet, r, realmBeatmapSet);
foreach (var beatmap in beatmapSet.Beatmaps)
{
var ruleset = realm.Find<RulesetInfo>(beatmap.RulesetInfo.ShortName);
var ruleset = r.Find<RulesetInfo>(beatmap.RulesetInfo.ShortName);
var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata);
var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata)
@ -225,7 +225,7 @@ namespace osu.Game.Database
realmBeatmapSet.Beatmaps.Add(realmBeatmap);
}
realm.Add(realmBeatmapSet);
r.Add(realmBeatmapSet);
}
}
finally
@ -280,11 +280,11 @@ namespace osu.Game.Database
int count = existingScores.Count();
realm.Run(realm =>
realm.Run(r =>
{
log($"Found {count} scores in EF");
var transaction = realm.BeginWrite();
var transaction = r.BeginWrite();
int written = 0;
try
@ -294,12 +294,12 @@ namespace osu.Game.Database
if (++written % 1000 == 0)
{
transaction.Commit();
transaction = realm.BeginWrite();
transaction = r.BeginWrite();
log($"Migrated {written}/{count} scores...");
}
var beatmap = realm.All<BeatmapInfo>().First(b => b.Hash == score.BeatmapInfo.Hash);
var ruleset = realm.Find<RulesetInfo>(score.Ruleset.ShortName);
var beatmap = r.All<BeatmapInfo>().First(b => b.Hash == score.BeatmapInfo.Hash);
var ruleset = r.Find<RulesetInfo>(score.Ruleset.ShortName);
var user = new RealmUser
{
OnlineID = score.User.OnlineID,
@ -329,9 +329,9 @@ namespace osu.Game.Database
APIMods = score.APIMods,
};
migrateFiles(score, realm, realmScore);
migrateFiles(score, r, realmScore);
realm.Add(realmScore);
r.Add(realmScore);
}
}
finally
@ -369,13 +369,13 @@ namespace osu.Game.Database
break;
}
realm.Run(realm =>
realm.Run(r =>
{
using (var transaction = realm.BeginWrite())
using (var transaction = r.BeginWrite())
{
// only migrate data if the realm database is empty.
// note that this cannot be written as: `realm.All<SkinInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
if (!realm.All<SkinInfo>().Any(s => !s.Protected))
// note that this cannot be written as: `r.All<SkinInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
if (!r.All<SkinInfo>().Any(s => !s.Protected))
{
log($"Migrating {existingSkins.Count} skins");
@ -390,9 +390,9 @@ namespace osu.Game.Database
InstantiationInfo = skin.InstantiationInfo,
};
migrateFiles(skin, realm, realmSkin);
migrateFiles(skin, r, realmSkin);
realm.Add(realmSkin);
r.Add(realmSkin);
if (skin.ID == userSkinInt)
userSkinChoice.Value = realmSkin.ID.ToString();
@ -428,12 +428,12 @@ namespace osu.Game.Database
log("Beginning settings migration to realm");
realm.Run(realm =>
realm.Run(r =>
{
using (var transaction = realm.BeginWrite())
using (var transaction = r.BeginWrite())
{
// only migrate data if the realm database is empty.
if (!realm.All<RealmRulesetSetting>().Any())
if (!r.All<RealmRulesetSetting>().Any())
{
log($"Migrating {existingSettings.Count} settings");
@ -447,7 +447,7 @@ namespace osu.Game.Database
if (string.IsNullOrEmpty(shortName))
continue;
realm.Add(new RealmRulesetSetting
r.Add(new RealmRulesetSetting
{
Key = dkb.Key,
Value = dkb.StringValue,

View File

@ -30,7 +30,7 @@ using Realms.Exceptions;
namespace osu.Game.Database
{
/// <summary>
/// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
/// A factory which provides safe access to the realm storage backend.
/// </summary>
public class RealmAccess : IDisposable
{
@ -57,9 +57,9 @@ namespace osu.Game.Database
private const int schema_version = 13;
/// <summary>
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking context creation during blocking periods.
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
/// </summary>
private readonly SemaphoreSlim realmCreationLock = new SemaphoreSlim(1);
private readonly SemaphoreSlim realmRetrievalLock = new SemaphoreSlim(1);
private readonly ThreadLocal<bool> currentThreadCanCreateRealmInstances = new ThreadLocal<bool>();
@ -76,7 +76,7 @@ namespace osu.Game.Database
/// <summary>
/// Holds a map of functions registered via <see cref="RegisterForNotifications{T}"/> and a coinciding action which when triggered,
/// fires a change set event with an empty collection. This is used to inform subscribers when a realm context goes away, and ensure they don't use invalidated
/// fires a change set event with an empty collection. This is used to inform subscribers when the main realm instance gets recycled, and ensure they don't use invalidated
/// managed realm objects from a previous firing.
/// </summary>
private readonly Dictionary<Func<Realm, IDisposable?>, Action> notificationsResetMap = new Dictionary<Func<Realm, IDisposable?>, Action>();
@ -364,7 +364,7 @@ namespace osu.Game.Database
{
if (!currentThreadCanCreateRealmInstances.Value)
{
realmCreationLock.Wait();
realmRetrievalLock.Wait();
currentThreadCanCreateRealmInstances.Value = true;
tookSemaphoreLock = true;
}
@ -383,7 +383,7 @@ namespace osu.Game.Database
{
if (tookSemaphoreLock)
{
realmCreationLock.Release();
realmRetrievalLock.Release();
currentThreadCanCreateRealmInstances.Value = false;
}
}
@ -589,14 +589,14 @@ namespace osu.Game.Database
try
{
realmCreationLock.Wait();
realmRetrievalLock.Wait();
lock (realmLock)
{
if (updateRealm == null)
{
// null context means the update thread has not yet retrieved its context.
// we don't need to worry about reviving the update context in this case, so don't bother with the SynchronizationContext.
// null realm means the update thread has not yet retrieved its instance.
// we don't need to worry about reviving the update instance in this case, so don't bother with the SynchronizationContext.
Debug.Assert(!ThreadSafety.IsUpdateThread);
}
else
@ -648,7 +648,7 @@ namespace osu.Game.Database
void restoreOperation()
{
Logger.Log(@"Restoring realm operations.", LoggingTarget.Database);
realmCreationLock.Release();
realmRetrievalLock.Release();
// Post back to the update thread to revive any subscriptions.
syncContext?.Post(_ => ensureUpdateRealm(), null);
}
@ -668,9 +668,9 @@ namespace osu.Game.Database
if (!isDisposed)
{
// intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal.
realmCreationLock.Wait();
realmCreationLock.Dispose();
// intentionally block realm retrieval indefinitely. this ensures that nothing can start consuming a new instance after disposal.
realmRetrievalLock.Wait();
realmRetrievalLock.Dispose();
isDisposed = true;
}

View File

@ -51,10 +51,7 @@ namespace osu.Game.Database
return;
}
realm.Run(realm =>
{
perform(retrieveFromID(realm, ID));
});
realm.Run(r => perform(retrieveFromID(r, ID)));
}
/// <summary>
@ -66,9 +63,9 @@ namespace osu.Game.Database
if (!IsManaged)
return perform(data);
return realm.Run(realm =>
return realm.Run(r =>
{
var returnData = perform(retrieveFromID(realm, ID));
var returnData = perform(retrieveFromID(r, ID));
if (returnData is RealmObjectBase realmObject && realmObject.IsManaged)
throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}.");