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))
{