test: stabilize load-sensitive release contracts

This commit is contained in:
Erik 2026-08-18 11:50:23 +02:00
parent c8c764a40e
commit dfc841b779
8 changed files with 199 additions and 43 deletions

View file

@ -478,25 +478,44 @@ public class PortalProjectionTests
Assert.Equal(4, warm.Count);
}
long before = GC.GetAllocatedBytesForCurrentThread();
int totalVertices = 0;
for (int i = 0; i < 1_000; i++)
// Cross the tiered-JIT/PGO thresholds before measuring. A single
// warm call does not make a 1,000-call loop a warmed hot path.
for (int i = 0; i < 2_000; i++)
{
using PortalProjection.ClipPolygonLease lease =
PortalProjection.ProjectToClipLease(
opening,
Matrix4x4.Identity,
viewProjection);
totalVertices += lease.Count;
_ = lease.Count;
}
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
Assert.Equal(4_000, totalVertices);
int totalVertices = 0;
long minimumAllocated = long.MaxValue;
for (int sample = 0; sample < 5; sample++)
{
long before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < 1_000; i++)
{
using PortalProjection.ClipPolygonLease lease =
PortalProjection.ProjectToClipLease(
opening,
Matrix4x4.Identity,
viewProjection);
totalVertices += lease.Count;
}
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
minimumAllocated = Math.Min(minimumAllocated, allocated);
}
Assert.Equal(20_000, totalVertices);
// A tiered-JIT/ArrayPool bookkeeping transition can contribute a few hundred fixed bytes
// to the first measured batch on some runtimes. Keep the ceiling far below the former
// per-call result-array regression (~448 KB / 1,000 calls), while accepting that fixed
// process noise so this test measures linear hot-path allocation rather than JIT timing.
Assert.True(allocated <= 1_024, $"pooled projections allocated {allocated:N0} bytes");
// to an individual batch on some runtimes. At least one of five warmed batches must stay
// far below the former per-call result-array regression (~448 KB / 1,000 calls), which
// measures linear hot-path allocation without treating JIT timing as product allocation.
Assert.True(
minimumAllocated <= 1_024,
$"best warmed pooled-projection batch allocated {minimumAllocated:N0} bytes");
}
[Fact]
@ -529,20 +548,31 @@ public class PortalProjectionTests
Assert.Equal(expected, firstSnapshot);
Assert.Equal(expected, second);
long before = GC.GetAllocatedBytesForCurrentThread();
float checksum = 0f;
for (int i = 0; i < 1_000; i++)
for (int i = 0; i < 2_000; i++)
{
store.ResetUsage();
Vector2[] reused = PortalProjection.ClipToRegion(
subject.AsSpan(), region, store);
checksum += reused[0].X;
_ = PortalProjection.ClipToRegion(subject.AsSpan(), region, store);
}
float checksum = 0f;
long minimumAllocated = long.MaxValue;
for (int sample = 0; sample < 5; sample++)
{
long before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < 1_000; i++)
{
store.ResetUsage();
Vector2[] reused = PortalProjection.ClipToRegion(
subject.AsSpan(), region, store);
checksum += reused[0].X;
}
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
minimumAllocated = Math.Min(minimumAllocated, allocated);
}
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
Assert.True(float.IsFinite(checksum));
Assert.True(allocated <= 4_096,
$"frame-owned portal clipping allocated {allocated:N0} bytes");
Assert.True(minimumAllocated <= 4_096,
$"best warmed frame-owned clip batch allocated {minimumAllocated:N0} bytes");
}
[Fact]

View file

@ -193,32 +193,57 @@ public sealed class LandblockBuildFactoryTests
}
[Fact]
public async Task Build_UsesTheSuppliedSharedReaderGate()
public void Build_UsesTheSuppliedSharedReaderGate()
{
var dat = CreateDat(out RecordingDatProxy proxy);
proxy.Add(LandblockId, new LandBlock { Id = LandblockId });
object gate = new();
var factory = Factory(dat, gate);
using var started = new ManualResetEventSlim();
LandblockBuild? result = null;
Exception? workerError = null;
var worker = new Thread(() =>
{
started.Set();
try
{
result = factory.Build(Request(LandblockStreamJobKind.LoadFar));
}
catch (Exception error)
{
workerError = error;
}
})
{
IsBackground = true,
Name = "LandblockBuildFactory shared-gate contract",
};
Monitor.Enter(gate);
Task<LandblockBuild?> build;
bool startedInTime;
bool blockedOnGate;
bool readWhileBlocked;
try
{
build = Task.Run(() =>
{
started.Set();
return factory.Build(Request(LandblockStreamJobKind.LoadFar));
});
Assert.True(started.Wait(TimeSpan.FromSeconds(2)));
Assert.False(proxy.ReadObserved.Wait(TimeSpan.FromMilliseconds(100)));
worker.Start();
startedInTime = started.Wait(TimeSpan.FromSeconds(5));
blockedOnGate = startedInTime && SpinWait.SpinUntil(
() => (worker.ThreadState & ThreadState.WaitSleepJoin) != 0,
TimeSpan.FromSeconds(5));
readWhileBlocked = proxy.ReadObserved.IsSet;
}
finally
{
Monitor.Exit(gate);
}
Assert.NotNull(await build);
bool joined = worker.Join(TimeSpan.FromSeconds(10));
Assert.True(startedInTime, "the dedicated build thread did not start");
Assert.True(blockedOnGate, "the build thread never blocked on the supplied gate");
Assert.False(readWhileBlocked, "the DAT read bypassed the supplied gate");
Assert.True(joined, "the build thread did not finish after the gate was released");
Assert.Null(workerError);
Assert.NotNull(result);
Assert.True(proxy.ReadObserved.IsSet);
}

View file

@ -961,6 +961,8 @@ public sealed class StreamingWorkBudgetTests
nearRadius: 1,
farRadius: 1,
presentationPipeline: presentation,
workTimestamp: static () => 0,
workTimestampFrequency: 1_000,
workBudgetOptions: options);
}