1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-14 02:43:02 +08:00

Avoid attempting to fetch a non-managed RealmLive instance from the realm backing

For compatibility reasons, we quite often convert completely unmanaged
instances to `ILive`s so they fit the required parameters of a property
or method call. This ensures such cases will not cause any issues when
trying to interact with the underlying data.

Originally I had this allowing write operations, but that seems a bit
unsafe (when performing a write one would assume that the underlying
data is being persisted, whereas in this case it is not). We can change
this if the requirements change in the future, but I think throwing is
the safest bet for now.
This commit is contained in:
Dean Herbert 2021-11-26 14:39:35 +09:00
parent 3bc8f21935
commit 40d1b97af1
4 changed files with 41 additions and 4 deletions

View File

@ -30,6 +30,23 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestAccessNonManaged()
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive();
Assert.IsFalse(beatmap.Hidden);
Assert.IsFalse(liveBeatmap.Value.Hidden);
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
Assert.Throws<InvalidOperationException>(() => liveBeatmap.PerformWrite(l => l.Hidden = true));
Assert.IsFalse(beatmap.Hidden);
Assert.IsFalse(liveBeatmap.Value.Hidden);
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
}
[Test]
public void TestValueAccessWithOpenContext()
{

View File

@ -9,6 +9,7 @@ namespace osu.Game.Database
{
public EntityFrameworkLive(T item)
{
IsManaged = true; // no way to really know.
Value = item;
}
@ -29,6 +30,8 @@ namespace osu.Game.Database
perform(Value);
}
public bool IsManaged { get; }
public T Value { get; }
}
}

View File

@ -31,6 +31,11 @@ namespace osu.Game.Database
/// <param name="perform">The action to perform.</param>
void PerformWrite(Action<T> perform);
/// <summary>
/// Whether this instance is tracking data which is managed by the database backing.
/// </summary>
bool IsManaged { get; }
/// <summary>
/// Resolve the value of this instance on the current thread's context.
/// </summary>

View File

@ -17,6 +17,8 @@ namespace osu.Game.Database
{
public Guid ID { get; }
public bool IsManaged { get; }
private readonly SynchronizationContext? fetchedContext;
private readonly int fetchedThreadId;
@ -33,8 +35,13 @@ namespace osu.Game.Database
{
this.data = data;
fetchedContext = SynchronizationContext.Current;
fetchedThreadId = Thread.CurrentThread.ManagedThreadId;
if (data.IsManaged)
{
IsManaged = true;
fetchedContext = SynchronizationContext.Current;
fetchedThreadId = Thread.CurrentThread.ManagedThreadId;
}
ID = data.ID;
}
@ -75,13 +82,18 @@ namespace osu.Game.Database
/// Perform a write operation on this live object.
/// </summary>
/// <param name="perform">The action to perform.</param>
public void PerformWrite(Action<T> perform) =>
public void PerformWrite(Action<T> perform)
{
if (!IsManaged)
throw new InvalidOperationException("Can't perform writes on a non-managed underlying value");
PerformRead(t =>
{
var transaction = t.Realm.BeginWrite();
perform(t);
transaction.Commit();
});
}
public T Value
{
@ -102,7 +114,7 @@ namespace osu.Game.Database
}
}
private bool originalDataValid => isCorrectThread && data.IsValid;
private bool originalDataValid => !IsManaged || (isCorrectThread && data.IsValid);
// this matches realm's internal thread validation (see https://github.com/realm/realm-dotnet/blob/903b4d0b304f887e37e2d905384fb572a6496e70/Realm/Realm/Native/SynchronizationContextScheduler.cs#L72)
private bool isCorrectThread