From d69a4914e05f5f5d1c2802fcad8551f9c1fe52c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jan 2021 17:28:47 +0900 Subject: [PATCH] Add method to block all realm access during migration operation --- osu.Game/Database/IRealmFactory.cs | 2 +- osu.Game/Database/RealmContextFactory.cs | 87 ++++++++++++++----- osu.Game/OsuGameBase.cs | 8 +- .../KeyBinding/KeyBindingsSubsection.cs | 4 +- 4 files changed, 73 insertions(+), 28 deletions(-) diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs index 4a92a5683b..025c44f440 100644 --- a/osu.Game/Database/IRealmFactory.cs +++ b/osu.Game/Database/IRealmFactory.cs @@ -15,7 +15,7 @@ namespace osu.Game.Database /// /// Get a fresh context for read usage. /// - Realm GetForRead(); + RealmContextFactory.RealmUsage GetForRead(); /// /// Request a context for write usage. diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0ff4f857b9..c5d0061143 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -3,6 +3,7 @@ using System; using System.Threading; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; @@ -30,6 +31,9 @@ namespace osu.Game.Database private static readonly GlobalStatistic refreshes = GlobalStatistics.Get("Realm", "Dirty Refreshes"); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get("Realm", "Contexts (Created)"); private static readonly GlobalStatistic pending_writes = GlobalStatistics.Get("Realm", "Pending writes"); + private static readonly GlobalStatistic active_usages = GlobalStatistics.Get("Realm", "Active usages"); + + private readonly ManualResetEventSlim blockingResetEvent = new ManualResetEventSlim(true); private Realm context; @@ -57,10 +61,10 @@ namespace osu.Game.Database this.storage = storage; } - public Realm GetForRead() + public RealmUsage GetForRead() { reads.Value++; - return createContext(); + return new RealmUsage(this); } public RealmWriteUsage GetForWrite() @@ -83,6 +87,8 @@ namespace osu.Game.Database private Realm createContext() { + blockingResetEvent.Wait(); + contexts_created.Value++; return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) @@ -107,24 +113,69 @@ namespace osu.Game.Database { base.Dispose(isDisposing); - FlushConnections(); + BlockAllOperations(); + } + + public IDisposable BlockAllOperations() + { + blockingResetEvent.Reset(); + flushContexts(); + + return new InvokeOnDisposal(this, r => endBlockingSection()); + } + + private void endBlockingSection() + { + blockingResetEvent.Set(); + } + + private void flushContexts() + { + var previousContext = context; + context = null; + + // wait for all threaded usages to finish + while (active_usages.Value > 0) + Thread.Sleep(50); + + previousContext?.Dispose(); + } + + /// + /// A usage of realm from an arbitrary thread. + /// + public class RealmUsage : IDisposable + { + public readonly Realm Realm; + + protected readonly RealmContextFactory Factory; + + internal RealmUsage(RealmContextFactory factory) + { + Factory = factory; + Realm = factory.createContext(); + } + + /// + /// Disposes this instance, calling the initially captured action. + /// + public virtual void Dispose() + { + Realm?.Dispose(); + active_usages.Value--; + } } /// /// A transaction used for making changes to realm data. /// - public class RealmWriteUsage : IDisposable + public class RealmWriteUsage : RealmUsage { - public readonly Realm Realm; - - private readonly RealmContextFactory factory; private readonly Transaction transaction; internal RealmWriteUsage(RealmContextFactory factory) + : base(factory) { - this.factory = factory; - - Realm = factory.createContext(); transaction = Realm.BeginWrite(); } @@ -141,24 +192,16 @@ namespace osu.Game.Database /// /// Disposes this instance, calling the initially captured action. /// - public virtual void Dispose() + public override void Dispose() { // rollback if not explicitly committed. transaction?.Dispose(); - Realm?.Dispose(); - Monitor.Exit(factory.writeLock); + base.Dispose(); + + Monitor.Exit(Factory.writeLock); pending_writes.Value--; } } - - public void FlushConnections() - { - var previousContext = context; - context = null; - previousContext?.Dispose(); - while (previousContext?.IsClosed == false) - Thread.Sleep(50); - } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4bd4a6ae7f..7806a38cd9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -512,9 +512,11 @@ namespace osu.Game public void Migrate(string path) { - contextFactory.FlushConnections(); - realmFactory.FlushConnections(); - (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + using (realmFactory.BlockAllOperations()) + { + contextFactory.FlushConnections(); + (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + } } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 1cd600a72d..ac77440dfa 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -39,8 +39,8 @@ namespace osu.Game.Overlays.KeyBinding List bindings; - using (var realm = realmFactory.GetForRead()) - bindings = realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); + using (var usage = realmFactory.GetForRead()) + bindings = usage.Realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) {