From 7fa58427832e5dfaa6f9743650be7df15645a414 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 18:30:55 +0900 Subject: [PATCH 1/2] Add global statistics output for all realm reads/writes --- osu.Game/Database/RealmAccess.cs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 9bdbebfe89..f63e858b6f 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -14,14 +14,14 @@ using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; -using osu.Game.Configuration; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Models; -using osu.Game.Skinning; -using osu.Game.Stores; using osu.Game.Rulesets; using osu.Game.Scoring; +using osu.Game.Skinning; +using osu.Game.Stores; using Realms; using Realms.Exceptions; @@ -85,6 +85,14 @@ namespace osu.Game.Database private static readonly GlobalStatistic total_subscriptions = GlobalStatistics.Get(@"Realm", @"Subscriptions"); + private static readonly GlobalStatistic total_reads_update = GlobalStatistics.Get(@"Realm", @"Reads (Update)"); + + private static readonly GlobalStatistic total_reads_async = GlobalStatistics.Get(@"Realm", @"Reads (Async)"); + + private static readonly GlobalStatistic total_writes_update = GlobalStatistics.Get(@"Realm", @"Writes (Update)"); + + private static readonly GlobalStatistic total_writes_async = GlobalStatistics.Get(@"Realm", @"Writes (Async)"); + private readonly object realmLock = new object(); private Realm? updateRealm; @@ -213,8 +221,12 @@ namespace osu.Game.Database public T Run(Func action) { if (ThreadSafety.IsUpdateThread) + { + total_reads_update.Value++; return action(Realm); + } + total_reads_async.Value++; using (var realm = getRealmInstance()) return action(realm); } @@ -226,9 +238,13 @@ namespace osu.Game.Database public void Run(Action action) { if (ThreadSafety.IsUpdateThread) + { + total_reads_update.Value++; action(Realm); + } else { + total_reads_async.Value++; using (var realm = getRealmInstance()) action(realm); } @@ -241,9 +257,14 @@ namespace osu.Game.Database public void Write(Action action) { if (ThreadSafety.IsUpdateThread) + { + total_writes_update.Value++; Realm.Write(action); + } else { + total_writes_async.Value++; + using (var realm = getRealmInstance()) realm.Write(action); } From 9a117467b5ea36389da063102c051ce7eef86ed2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 18:31:33 +0900 Subject: [PATCH 2/2] Add `RealmAccess.WriteAsync` method --- .../RealmSubscriptionRegistrationTests.cs | 29 +++++++++++++++++++ osu.Game/Database/RealmAccess.cs | 12 ++++++++ 2 files changed, 41 insertions(+) diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index d62ce3b585..02d617d0e0 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Tests.Resources; @@ -18,6 +20,33 @@ namespace osu.Game.Tests.Database [TestFixture] public class RealmSubscriptionRegistrationTests : RealmTest { + [Test] + public void TestSubscriptionWithAsyncWrite() + { + ChangeSet? lastChanges = null; + + RunTestWithRealm((realm, _) => + { + var registration = realm.RegisterForNotifications(r => r.All(), onChanged); + + realm.Run(r => r.Refresh()); + + // Without forcing the write onto its own thread, realm will internally run the operation synchronously, which can cause a deadlock with `WaitSafely`. + Task.Run(async () => + { + await realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); + }).WaitSafely(); + + realm.Run(r => r.Refresh()); + + Assert.That(lastChanges?.InsertedIndices, Has.One.Items); + + registration.Dispose(); + }); + + void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error) => lastChanges = changes; + } + [Test] public void TestSubscriptionWithContextLoss() { diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index f63e858b6f..bf2b48ea52 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Development; using osu.Framework.Input.Bindings; @@ -270,6 +271,17 @@ namespace osu.Game.Database } } + /// + /// Write changes to realm asynchronously, guaranteeing order of execution. + /// + /// The work to run. + public async Task WriteAsync(Action action) + { + total_writes_async.Value++; + using (var realm = getRealmInstance()) + await realm.WriteAsync(action); + } + /// /// Subscribe to a realm collection and begin watching for asynchronous changes. ///