diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index 56aa0843a091..39dae61d3605 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using osu.Framework.Platform; @@ -47,13 +48,16 @@ private void performFileOperation(TModel item, Action operation) // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - // We use RealmLive here as it handled re-retrieval and refreshing of realm if required. - new RealmLive(item.ID, Realm).PerformWrite(i => + // Importantly, begin the realm write *before* re-fetching, else the update realm may not be in a consistent state + // (ie. if an async import finished very recently). + Realm.Realm.Write(realm => { - operation(i); + var managed = realm.FindWithRefresh(item.ID); + Debug.Assert(managed != null); + operation(managed); item.Files.Clear(); - item.Files.AddRange(i.Files.Detach()); + item.Files.AddRange(managed.Files.Detach()); }); } else diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index 13c4defb8328..ee76f1aa792d 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.cs @@ -8,6 +8,31 @@ namespace osu.Game.Database { public static class RealmExtensions { + /// + /// Performs a . + /// If a match was not found, a is performed before trying a second time. + /// This ensures that an instance is found even if the realm requested against was not in a consistent state. + /// + /// + /// + /// + /// + public static T? FindWithRefresh(this Realm realm, Guid id) where T : IRealmObject + { + var found = realm.Find(id); + + if (found == null) + { + // It may be that we access this from the update thread before a refresh has taken place. + // To ensure that behaviour matches what we'd expect (the object *is* available), force + // a refresh to bring in any off-thread changes immediately. + realm.Refresh(); + found = realm.Find(id); + } + + return found; + } + /// /// Perform a write operation against the provided realm instance. /// diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index bfb755c42a32..9e99cba45c20 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -41,24 +41,6 @@ public RealmLive(T data, RealmAccess realm) dataIsFromUpdateThread = ThreadSafety.IsUpdateThread; } - /// - /// Construct a new instance of live realm data from an ID. - /// - /// The ID of an already-persisting realm instance. - /// The realm factory the data was sourced from. May be null for an unmanaged object. - public RealmLive(Guid id, RealmAccess realm) - : base(id) - { - data = retrieveFromID(realm.Realm); - - if (data.IsNull()) - throw new ArgumentException("Realm instance for provided ID could not be found.", nameof(id)); - - this.realm = realm; - - dataIsFromUpdateThread = ThreadSafety.IsUpdateThread; - } - /// /// Perform a read operation on this live object. /// @@ -80,7 +62,7 @@ public override void PerformRead(Action perform) return; } - perform(retrieveFromID(r)); + perform(r.FindWithRefresh(ID)!); RealmLiveStatistics.USAGE_ASYNC.Value++; }); } @@ -102,7 +84,7 @@ public override TReturn PerformRead(Func perform) return realm.Run(r => { - var returnData = perform(retrieveFromID(r)); + var returnData = perform(r.FindWithRefresh(ID)!); RealmLiveStatistics.USAGE_ASYNC.Value++; if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) @@ -159,24 +141,9 @@ private void ensureDataIsFromUpdateThread() } dataIsFromUpdateThread = true; - data = retrieveFromID(realm.Realm); - RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++; - } - - private T retrieveFromID(Realm realm) - { - var found = realm.Find(ID); + data = realm.Realm.FindWithRefresh(ID)!; - if (found == null) - { - // It may be that we access this from the update thread before a refresh has taken place. - // To ensure that behaviour matches what we'd expect (the object *is* available), force - // a refresh to bring in any off-thread changes immediately. - realm.Refresh(); - found = realm.Find(ID)!; - } - - return found; + RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++; } }