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] [Test]
public void TestValueAccessWithOpenContext() public void TestValueAccessWithOpenContext()
{ {

View File

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

View File

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

View File

@ -17,6 +17,8 @@ namespace osu.Game.Database
{ {
public Guid ID { get; } public Guid ID { get; }
public bool IsManaged { get; }
private readonly SynchronizationContext? fetchedContext; private readonly SynchronizationContext? fetchedContext;
private readonly int fetchedThreadId; private readonly int fetchedThreadId;
@ -33,8 +35,13 @@ namespace osu.Game.Database
{ {
this.data = data; this.data = data;
if (data.IsManaged)
{
IsManaged = true;
fetchedContext = SynchronizationContext.Current; fetchedContext = SynchronizationContext.Current;
fetchedThreadId = Thread.CurrentThread.ManagedThreadId; fetchedThreadId = Thread.CurrentThread.ManagedThreadId;
}
ID = data.ID; ID = data.ID;
} }
@ -75,13 +82,18 @@ namespace osu.Game.Database
/// Perform a write operation on this live object. /// Perform a write operation on this live object.
/// </summary> /// </summary>
/// <param name="perform">The action to perform.</param> /// <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 => PerformRead(t =>
{ {
var transaction = t.Realm.BeginWrite(); var transaction = t.Realm.BeginWrite();
perform(t); perform(t);
transaction.Commit(); transaction.Commit();
}); });
}
public T Value 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) // 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 private bool isCorrectThread