Skip to content

Commit

Permalink
Add helper method for safer realm Find<T>
Browse files Browse the repository at this point in the history
  • Loading branch information
peppy committed Aug 16, 2023
1 parent 88295a4 commit 6e11162
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 41 deletions.
12 changes: 8 additions & 4 deletions osu.Game/Database/ModelManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using osu.Framework.Platform;
Expand Down Expand Up @@ -47,13 +48,16 @@ private void performFileOperation(TModel item, Action<TModel> 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<TModel>(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<TModel>(item.ID);
Debug.Assert(managed != null);
operation(managed);
item.Files.Clear();
item.Files.AddRange(i.Files.Detach());
item.Files.AddRange(managed.Files.Detach());
});
}
else
Expand Down
25 changes: 25 additions & 0 deletions osu.Game/Database/RealmExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@ namespace osu.Game.Database
{
public static class RealmExtensions
{
/// <summary>
/// Performs a <see cref="Realm.Find{T}(System.Nullable{long})"/>.
/// If a match was not found, a <see cref="Realm.Refresh"/> 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.
/// </summary>
/// <param name="realm"></param>
/// <param name="id"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T? FindWithRefresh<T>(this Realm realm, Guid id) where T : IRealmObject
{
var found = realm.Find<T>(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<T>(id);
}

return found;
}

/// <summary>
/// Perform a write operation against the provided realm instance.
/// </summary>
Expand Down
41 changes: 4 additions & 37 deletions osu.Game/Database/RealmLive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,6 @@ public RealmLive(T data, RealmAccess realm)
dataIsFromUpdateThread = ThreadSafety.IsUpdateThread;
}

/// <summary>
/// Construct a new instance of live realm data from an ID.
/// </summary>
/// <param name="id">The ID of an already-persisting realm instance.</param>
/// <param name="realm">The realm factory the data was sourced from. May be null for an unmanaged object.</param>
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;
}

/// <summary>
/// Perform a read operation on this live object.
/// </summary>
Expand All @@ -80,7 +62,7 @@ public override void PerformRead(Action<T> perform)
return;
}
perform(retrieveFromID(r));
perform(r.FindWithRefresh<T>(ID)!);
RealmLiveStatistics.USAGE_ASYNC.Value++;
});
}
Expand All @@ -102,7 +84,7 @@ public override TReturn PerformRead<TReturn>(Func<T, TReturn> perform)

return realm.Run(r =>
{
var returnData = perform(retrieveFromID(r));
var returnData = perform(r.FindWithRefresh<T>(ID)!);
RealmLiveStatistics.USAGE_ASYNC.Value++;
if (returnData is RealmObjectBase realmObject && realmObject.IsManaged)
Expand Down Expand Up @@ -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<T>(ID);
data = realm.Realm.FindWithRefresh<T>(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<T>(ID)!;
}

return found;
RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++;
}
}

Expand Down

0 comments on commit 6e11162

Please sign in to comment.