sharpemu/tests/SharpEmu.Libs.Tests/Memory/GpuMemory/RegionLockTests.cs

112 lines
4.6 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE.GpuMemory;
using Xunit;
namespace SharpEmu.Libs.Tests.Memory.GpuMemory;
[Collection(GpuMemoryStateCollection.Name)]
public sealed class RegionLockTests
{
private sealed class LockFailureException(string message) : Exception(message);
[Theory]
[InlineData(2, RegionLock.Category.MemoryTracker)]
[InlineData(8, RegionLock.Category.MemoryTracker)]
[InlineData(2, RegionLock.Category.ImageCache)]
[InlineData(8, RegionLock.Category.ImageCache)]
public async Task ContendingThreadsPublishCompleteUpdatesAndAllFinish(int workerCount, RegionLock.Category category)
{
GpuMemoryAccessProfile.Initialize();
var regionLock = new RegionLock(category);
var before = GpuMemoryAccessProfile.Snapshot();
using var start = new ManualResetEventSlim();
var sequence = 0;
var complement = ~sequence;
const int iterations = 5000;
var workers = Enumerable.Range(0, workerCount).Select(_ => Task.Factory.StartNew(() =>
{
start.Wait();
for (var iteration = 0; iteration < iterations; iteration++)
{
using var held = regionLock.Hold();
Assert.Equal(~sequence, complement);
sequence++;
Thread.SpinWait(8);
complement = ~sequence;
}
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)).ToArray();
start.Set();
await Task.WhenAll(workers).WaitAsync(TimeSpan.FromSeconds(30));
var after = GpuMemoryAccessProfile.Snapshot();
var holdOperation = category == RegionLock.Category.ImageCache
? GpuMemoryAccessProfile.Operation.ImageCacheLockHold : GpuMemoryAccessProfile.Operation.TrackerLockHold;
var waitOperation = category == RegionLock.Category.ImageCache
? GpuMemoryAccessProfile.Operation.ImageCacheLockWait : GpuMemoryAccessProfile.Operation.TrackerLockWait;
Assert.Equal(GpuMemoryAccessProfile.Enabled ? workerCount * iterations : 0,
after[(int)holdOperation].Calls - before[(int)holdOperation].Calls);
Assert.Equal(after[(int)waitOperation].Calls - before[(int)waitOperation].Calls,
after[(int)GpuMemoryAccessProfile.Operation.RegionLockWait].Calls - before[(int)GpuMemoryAccessProfile.Operation.RegionLockWait].Calls);
Assert.Equal(after[(int)waitOperation].Ticks - before[(int)waitOperation].Ticks,
after[(int)GpuMemoryAccessProfile.Operation.RegionLockWait].Ticks - before[(int)GpuMemoryAccessProfile.Operation.RegionLockWait].Ticks);
using var finalRead = regionLock.Hold();
Assert.Equal(workerCount * iterations, sequence);
Assert.Equal(~sequence, complement);
}
[Fact]
public void RecursionAndForeignReleaseKeepTheOriginalOwner()
{
var previousFatal = PageGuard.OnFatal;
PageGuard.OnFatal = message => throw new LockFailureException(message);
var regionLock = new RegionLock();
try
{
regionLock.Enter();
try
{
Assert.Throws<LockFailureException>(regionLock.Enter);
Exception? foreignFailure = null;
var foreignThread = new Thread(() =>
{
try { regionLock.Exit(); }
catch (Exception failure) { foreignFailure = failure; }
});
foreignThread.Start();
Assert.True(foreignThread.Join(TimeSpan.FromSeconds(5)));
Assert.IsType<LockFailureException>(foreignFailure);
Assert.Throws<LockFailureException>(regionLock.Enter);
}
finally
{
regionLock.Exit();
}
using var reacquired = regionLock.Hold();
}
finally
{
PageGuard.OnFatal = previousFatal;
}
}
[Theory]
[InlineData(RegionLock.Category.MemoryTracker)]
[InlineData(RegionLock.Category.ImageCache)]
public void UncontendedAcquisitionDoesNotAllocate(RegionLock.Category category)
{
var regionLock = new RegionLock(category);
void Acquire(int count)
{
using var fault = GpuMemoryAccessProfile.MeasureFault(FaultKind.Write);
for (var iteration = 0; iteration < count; iteration++)
{
using var held = regionLock.Hold();
}
}
var allocated = AllocationMeasurement.SteadyState(() => Acquire(100), () => Acquire(1000));
Assert.Equal(0, allocated);
}
}