test: stabilize load-sensitive release contracts
This commit is contained in:
parent
c8c764a40e
commit
dfc841b779
8 changed files with 199 additions and 43 deletions
|
|
@ -134,7 +134,7 @@ identifiable in the lane report.
|
||||||
| T-011 51 output-only methods | classified in batch C | The reviewed current set is 51 methods / 70 cases. All carry `Purpose=Diagnostic`, preserving the apparatus while removing it from release pass totals. Contract-shaped names remain explicitly flagged until a stable oracle exists. |
|
| T-011 51 output-only methods | classified in batch C | The reviewed current set is 51 methods / 70 cases. All carry `Purpose=Diagnostic`, preserving the apparatus while removing it from release pass totals. Contract-shaped names remain explicitly flagged until a stable oracle exists. |
|
||||||
| T-012 source-text freezes | requires semantic replacement map | Retain whole-tree dependency rules; remove exact-text freezes only when an equivalent semantic/behavioral guard is identified. |
|
| T-012 source-text freezes | requires semantic replacement map | Retain whole-tree dependency rules; remove exact-text freezes only when an equivalent semantic/behavioral guard is identified. |
|
||||||
| T-013 controller self-comparison | high-confidence cleanup batch A | Capture the first controller next to the first body and compare every retry with that reference. |
|
| T-013 controller self-comparison | high-confidence cleanup batch A | Capture the first controller next to the first body and compare every retry with that reference. |
|
||||||
| T-014 seven load-sensitive tests | active; first mechanism reproduced | `PortalProjectionTests.ClipToRegion_FrameOwnedStore_ReusesExactResultArray` reported 6,904 B only while three App test hosts ran concurrently, then passed in an isolated run. Preserve both observations and remove ambient process-allocation sensitivity rather than loosening the zero-allocation oracle. |
|
| T-014 seven load-sensitive tests | six mechanisms repaired in batch F; one product defect classified | Virtual/wall-clock mixing, tiered-JIT allocation noise, a live controller clock, and a ThreadPool-start timing oracle were removed without weakening behavioral contracts. `DatSoundCache` #321 is a real in-flight-entry race and now runs as `Status=KnownFailure` pending a product fix. |
|
||||||
| T-015 four non-prerequisite skips | resolved in batches A/B | PVS scaffold deleted with rationale preserved; redundant chat/radar generators deleted in favor of the comprehensive Manual lane; tower oracle is `Status=KnownFailure`. |
|
| T-015 four non-prerequisite skips | resolved in batches A/B | PVS scaffold deleted with rationale preserved; redundant chat/radar generators deleted in favor of the comprehensive Manual lane; tower oracle is `Status=KnownFailure`. |
|
||||||
| T-016 historical test taxonomy | open | Rename/re-home only after each test's durable owner and oracle are established. |
|
| T-016 historical test taxonomy | open | Rename/re-home only after each test's durable owner and oracle are established. |
|
||||||
| T-017 Avalonia ownership | reopened and closed in batch D | The full gate exposed the same compositor ownership class between six newer `MainWindowViewTests` facts. Their six named assertion phases now run in one owned Avalonia application session; 25 fresh-process stress iterations and the complete gate pass. |
|
| T-017 Avalonia ownership | reopened and closed in batch D | The full gate exposed the same compositor ownership class between six newer `MainWindowViewTests` facts. Their six named assertion phases now run in one owned Avalonia application session; 25 fresh-process stress iterations and the complete gate pass. |
|
||||||
|
|
@ -330,3 +330,43 @@ recognition. The public production factory remains wired directly to
|
||||||
The focused class passes 4/4 in 17 ms with exact 10 ms and 600 ms virtual
|
The focused class passes 4/4 in 17 ms with exact 10 ms and 600 ms virtual
|
||||||
intervals. The complete Release build then passed with 0 warnings/errors, and
|
intervals. The complete Release build then passed with 0 warnings/errors, and
|
||||||
the no-retry hermetic gate passed 14,392/14,392 with zero skips or failures.
|
the no-retry hermetic gate passed 14,392/14,392 with zero skips or failures.
|
||||||
|
|
||||||
|
## Batch F load-sensitive contracts
|
||||||
|
|
||||||
|
The seven cases in T-014 have now been separated into six unreliable test
|
||||||
|
mechanisms and one real product defect:
|
||||||
|
|
||||||
|
- #308 `NakEmissionTests.LossSoak...` no longer mixes its virtual transport
|
||||||
|
clock with a 60-second `DateTime.UtcNow` cutoff and fixed sleeps. Its two
|
||||||
|
convergence phases each permit exactly 120 half-second virtual steps and
|
||||||
|
yield to the background receiver. The full 10,000-message oracle is intact;
|
||||||
|
25 fresh-process repetitions pass.
|
||||||
|
- #302 and #346, the two `PortalProjectionTests` allocation contracts, now
|
||||||
|
cross tiered-JIT/PGO thresholds before measuring and take the minimum of five
|
||||||
|
warmed 1,000-operation batches. A linear result-array regression would
|
||||||
|
allocate in every batch and still fail far above the existing ceilings.
|
||||||
|
- #336 `RuntimeCollisionReportingStateTests.WarmedSteadyContactRefreshDoesNotAllocate`
|
||||||
|
now earns the word "warmed": it performs one complete 10,000-refresh warmup
|
||||||
|
and requires at least one of five subsequent 10,000-refresh batches to
|
||||||
|
allocate exactly zero bytes. It passes in 25 fresh processes.
|
||||||
|
- #340 `StreamingWorkBudgetTests.DestinationAndEmptyUnloadPriorityNeverBypassPublicationBudget`
|
||||||
|
now supplies a frozen meter clock through an internal test-only constructor.
|
||||||
|
The public production constructor still supplies `Stopwatch.GetTimestamp`
|
||||||
|
and `Stopwatch.Frequency` exactly as before.
|
||||||
|
- #402 `LandblockBuildFactoryTests.Build_UsesTheSuppliedSharedReaderGate` now
|
||||||
|
starts a dedicated thread and observes it in `WaitSleepJoin` on the held
|
||||||
|
monitor before checking that no DAT read occurred. It no longer treats a
|
||||||
|
ThreadPool start within two seconds or an arbitrary 100 ms wait as evidence.
|
||||||
|
- #321 `DatSoundCacheTests.GetWave_ConcurrentSameId_PublishesOneCanonicalWaveAndDecodesOnce`
|
||||||
|
exposes a real race. A caller can pass the resident-cache check, pause, and
|
||||||
|
reach `_inflight` after the winning caller has admitted the wave and removed
|
||||||
|
its `Lazy`, producing a second decode. The assertion and rationale are
|
||||||
|
preserved under `Status=KnownFailure`; R3 makes no cache behavior change.
|
||||||
|
|
||||||
|
The four changed App cases pass 100/100 across 25 fresh processes. The Runtime
|
||||||
|
allocation case passes 25/25, and the Core.Net loss soak passes 25/25. Complete
|
||||||
|
batch verification then passed: the 44-project Release build reports zero
|
||||||
|
warnings/errors, the refreshed inventory reports two explicit known-failure
|
||||||
|
methods and 15 remaining direct `Thread.Sleep` methods, and the no-retry
|
||||||
|
hermetic gate passed 14,391/14,391 with zero skips or failures. The one-pass
|
||||||
|
reduction is exactly #321 leaving the release lane.
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ public sealed class StreamingController
|
||||||
_isPublicationBlockedByRetirement;
|
_isPublicationBlockedByRetirement;
|
||||||
private readonly StreamingWorkBudgetOptions _configuredWorkBudgetOptions;
|
private readonly StreamingWorkBudgetOptions _configuredWorkBudgetOptions;
|
||||||
private StreamingWorkBudget _workBudget;
|
private StreamingWorkBudget _workBudget;
|
||||||
|
private readonly Func<long> _workTimestamp;
|
||||||
|
private readonly long _workTimestampFrequency;
|
||||||
private StreamingWorkMeter? _activeWorkMeter;
|
private StreamingWorkMeter? _activeWorkMeter;
|
||||||
private StreamingWorkMeterSnapshot _lastWorkMeter;
|
private StreamingWorkMeterSnapshot _lastWorkMeter;
|
||||||
private readonly StreamingCompletionQueue _completionQueue = new();
|
private readonly StreamingCompletionQueue _completionQueue = new();
|
||||||
|
|
@ -376,6 +378,37 @@ public sealed class StreamingController
|
||||||
LandblockPresentationPipeline presentationPipeline,
|
LandblockPresentationPipeline presentationPipeline,
|
||||||
Action? clearPendingLoads = null,
|
Action? clearPendingLoads = null,
|
||||||
StreamingWorkBudgetOptions? workBudgetOptions = null)
|
StreamingWorkBudgetOptions? workBudgetOptions = null)
|
||||||
|
: this(
|
||||||
|
enqueueLoad,
|
||||||
|
enqueueUnload,
|
||||||
|
completionSource,
|
||||||
|
state,
|
||||||
|
nearRadius,
|
||||||
|
farRadius,
|
||||||
|
presentationPipeline,
|
||||||
|
Stopwatch.GetTimestamp,
|
||||||
|
Stopwatch.Frequency,
|
||||||
|
clearPendingLoads,
|
||||||
|
workBudgetOptions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deterministic meter-clock seam for scheduler policy tests. Production
|
||||||
|
/// construction always uses the public overload and <see cref="Stopwatch"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal StreamingController(
|
||||||
|
Action<uint, LandblockStreamJobKind, ulong> enqueueLoad,
|
||||||
|
Action<uint, ulong> enqueueUnload,
|
||||||
|
ILandblockCompletionSource completionSource,
|
||||||
|
GpuWorldState state,
|
||||||
|
int nearRadius,
|
||||||
|
int farRadius,
|
||||||
|
LandblockPresentationPipeline presentationPipeline,
|
||||||
|
Func<long> workTimestamp,
|
||||||
|
long workTimestampFrequency,
|
||||||
|
Action? clearPendingLoads = null,
|
||||||
|
StreamingWorkBudgetOptions? workBudgetOptions = null)
|
||||||
{
|
{
|
||||||
_enqueueLoad = enqueueLoad;
|
_enqueueLoad = enqueueLoad;
|
||||||
_enqueueUnload = enqueueUnload;
|
_enqueueUnload = enqueueUnload;
|
||||||
|
|
@ -390,6 +423,11 @@ public sealed class StreamingController
|
||||||
_configuredWorkBudgetOptions =
|
_configuredWorkBudgetOptions =
|
||||||
workBudgetOptions ?? StreamingWorkBudgetOptions.Default;
|
workBudgetOptions ?? StreamingWorkBudgetOptions.Default;
|
||||||
_workBudget = _configuredWorkBudgetOptions.ToBudget();
|
_workBudget = _configuredWorkBudgetOptions.ToBudget();
|
||||||
|
_workTimestamp = workTimestamp
|
||||||
|
?? throw new ArgumentNullException(nameof(workTimestamp));
|
||||||
|
if (workTimestampFrequency <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(workTimestampFrequency));
|
||||||
|
_workTimestampFrequency = workTimestampFrequency;
|
||||||
if (!_presentation.MatchesState(_state))
|
if (!_presentation.MatchesState(_state))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(
|
throw new ArgumentException(
|
||||||
|
|
@ -679,6 +717,8 @@ public sealed class StreamingController
|
||||||
_configuredWorkBudgetOptions
|
_configuredWorkBudgetOptions
|
||||||
.HoldDestinationCeilingMilliseconds)
|
.HoldDestinationCeilingMilliseconds)
|
||||||
: _workBudget,
|
: _workBudget,
|
||||||
|
_workTimestamp,
|
||||||
|
_workTimestampFrequency,
|
||||||
destinationReservationActive: destinationHold);
|
destinationReservationActive: destinationHold);
|
||||||
_activeWorkMeter = meter;
|
_activeWorkMeter = meter;
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -478,25 +478,44 @@ public class PortalProjectionTests
|
||||||
Assert.Equal(4, warm.Count);
|
Assert.Equal(4, warm.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
// Cross the tiered-JIT/PGO thresholds before measuring. A single
|
||||||
int totalVertices = 0;
|
// warm call does not make a 1,000-call loop a warmed hot path.
|
||||||
for (int i = 0; i < 1_000; i++)
|
for (int i = 0; i < 2_000; i++)
|
||||||
{
|
{
|
||||||
using PortalProjection.ClipPolygonLease lease =
|
using PortalProjection.ClipPolygonLease lease =
|
||||||
PortalProjection.ProjectToClipLease(
|
PortalProjection.ProjectToClipLease(
|
||||||
opening,
|
opening,
|
||||||
Matrix4x4.Identity,
|
Matrix4x4.Identity,
|
||||||
viewProjection);
|
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
|
// 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
|
// to an individual batch on some runtimes. At least one of five warmed batches must stay
|
||||||
// per-call result-array regression (~448 KB / 1,000 calls), while accepting that fixed
|
// far below the former per-call result-array regression (~448 KB / 1,000 calls), which
|
||||||
// process noise so this test measures linear hot-path allocation rather than JIT timing.
|
// measures linear hot-path allocation without treating JIT timing as product allocation.
|
||||||
Assert.True(allocated <= 1_024, $"pooled projections allocated {allocated:N0} bytes");
|
Assert.True(
|
||||||
|
minimumAllocated <= 1_024,
|
||||||
|
$"best warmed pooled-projection batch allocated {minimumAllocated:N0} bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -529,20 +548,31 @@ public class PortalProjectionTests
|
||||||
Assert.Equal(expected, firstSnapshot);
|
Assert.Equal(expected, firstSnapshot);
|
||||||
Assert.Equal(expected, second);
|
Assert.Equal(expected, second);
|
||||||
|
|
||||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
for (int i = 0; i < 2_000; i++)
|
||||||
float checksum = 0f;
|
|
||||||
for (int i = 0; i < 1_000; i++)
|
|
||||||
{
|
{
|
||||||
store.ResetUsage();
|
store.ResetUsage();
|
||||||
Vector2[] reused = PortalProjection.ClipToRegion(
|
_ = PortalProjection.ClipToRegion(subject.AsSpan(), region, store);
|
||||||
subject.AsSpan(), region, store);
|
}
|
||||||
checksum += reused[0].X;
|
|
||||||
|
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(float.IsFinite(checksum));
|
||||||
Assert.True(allocated <= 4_096,
|
Assert.True(minimumAllocated <= 4_096,
|
||||||
$"frame-owned portal clipping allocated {allocated:N0} bytes");
|
$"best warmed frame-owned clip batch allocated {minimumAllocated:N0} bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -193,32 +193,57 @@ public sealed class LandblockBuildFactoryTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Build_UsesTheSuppliedSharedReaderGate()
|
public void Build_UsesTheSuppliedSharedReaderGate()
|
||||||
{
|
{
|
||||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||||
proxy.Add(LandblockId, new LandBlock { Id = LandblockId });
|
proxy.Add(LandblockId, new LandBlock { Id = LandblockId });
|
||||||
object gate = new();
|
object gate = new();
|
||||||
var factory = Factory(dat, gate);
|
var factory = Factory(dat, gate);
|
||||||
using var started = new ManualResetEventSlim();
|
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);
|
Monitor.Enter(gate);
|
||||||
Task<LandblockBuild?> build;
|
bool startedInTime;
|
||||||
|
bool blockedOnGate;
|
||||||
|
bool readWhileBlocked;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
build = Task.Run(() =>
|
worker.Start();
|
||||||
{
|
startedInTime = started.Wait(TimeSpan.FromSeconds(5));
|
||||||
started.Set();
|
blockedOnGate = startedInTime && SpinWait.SpinUntil(
|
||||||
return factory.Build(Request(LandblockStreamJobKind.LoadFar));
|
() => (worker.ThreadState & ThreadState.WaitSleepJoin) != 0,
|
||||||
});
|
TimeSpan.FromSeconds(5));
|
||||||
Assert.True(started.Wait(TimeSpan.FromSeconds(2)));
|
readWhileBlocked = proxy.ReadObserved.IsSet;
|
||||||
Assert.False(proxy.ReadObserved.Wait(TimeSpan.FromMilliseconds(100)));
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Monitor.Exit(gate);
|
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);
|
Assert.True(proxy.ReadObserved.IsSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -961,6 +961,8 @@ public sealed class StreamingWorkBudgetTests
|
||||||
nearRadius: 1,
|
nearRadius: 1,
|
||||||
farRadius: 1,
|
farRadius: 1,
|
||||||
presentationPipeline: presentation,
|
presentationPipeline: presentation,
|
||||||
|
workTimestamp: static () => 0,
|
||||||
|
workTimestampFrequency: 1_000,
|
||||||
workBudgetOptions: options);
|
workBudgetOptions: options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -759,7 +759,7 @@ public sealed class NakEmissionTests
|
||||||
/// retail-faithful steady state). The session survives the whole run.
|
/// retail-faithful steady state). The session survives the whole run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LossSoak_TwoPercentBidirectional_ZeroMessageLoss_LedgersConverge()
|
public async Task LossSoak_TwoPercentBidirectional_ZeroMessageLoss_LedgersConverge()
|
||||||
{
|
{
|
||||||
var transport = new FakeAceTransport();
|
var transport = new FakeAceTransport();
|
||||||
var session = new WorldSession(
|
var session = new WorldSession(
|
||||||
|
|
@ -808,7 +808,7 @@ public sealed class NakEmissionTests
|
||||||
session.Tick();
|
session.Tick();
|
||||||
|
|
||||||
if ((i & 15) == 0)
|
if ((i & 15) == 0)
|
||||||
Thread.Sleep(1);
|
await Task.Yield();
|
||||||
|
|
||||||
if (i % 500 == 0)
|
if (i % 500 == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -837,16 +837,18 @@ public sealed class NakEmissionTests
|
||||||
// recoverable once further sequenced C2S traffic arrives. Real
|
// recoverable once further sequenced C2S traffic arrives. Real
|
||||||
// clients keep talking; the trickle models that.
|
// clients keep talking; the trickle models that.
|
||||||
int trickle = 0;
|
int trickle = 0;
|
||||||
DateTime deadline = DateTime.UtcNow.AddSeconds(60);
|
const int MaxConvergenceSteps = 120; // 60 virtual seconds at 0.5 s/step
|
||||||
while (DateTime.UtcNow < deadline
|
for (int step = 0;
|
||||||
&& (s2cReceived != MessagesEachWay
|
step < MaxConvergenceSteps
|
||||||
|| c2sDispatched != MessagesEachWay))
|
&& (s2cReceived != MessagesEachWay
|
||||||
|
|| c2sDispatched != MessagesEachWay);
|
||||||
|
step++)
|
||||||
{
|
{
|
||||||
transport.Clock.Advance(TimeSpan.FromMilliseconds(500));
|
transport.Clock.Advance(TimeSpan.FromMilliseconds(500));
|
||||||
session.SendTalk($"trickle {trickle++}");
|
session.SendTalk($"trickle {trickle++}");
|
||||||
transport.PumpServer();
|
transport.PumpServer();
|
||||||
session.Tick();
|
session.Tick();
|
||||||
Thread.Sleep(1);
|
await Task.Yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convergence phase 2: quiet drain. Acks keep flowing on the
|
// Convergence phase 2: quiet drain. Acks keep flowing on the
|
||||||
|
|
@ -856,7 +858,7 @@ public sealed class NakEmissionTests
|
||||||
// traffic (two arrivals) before ACE's gap detection can fire —
|
// traffic (two arrivals) before ACE's gap detection can fire —
|
||||||
// an occasional healer send, only while the cache is stuck.
|
// an occasional healer send, only while the cache is stuck.
|
||||||
int quietIterations = 0;
|
int quietIterations = 0;
|
||||||
while (DateTime.UtcNow < deadline)
|
for (int step = 0; step < MaxConvergenceSteps; step++)
|
||||||
{
|
{
|
||||||
transport.Clock.Advance(TimeSpan.FromMilliseconds(500));
|
transport.Clock.Advance(TimeSpan.FromMilliseconds(500));
|
||||||
if (session.Transport!.Outbound.CacheDepth > 1
|
if (session.Transport!.Outbound.CacheDepth > 1
|
||||||
|
|
@ -867,7 +869,7 @@ public sealed class NakEmissionTests
|
||||||
|
|
||||||
transport.PumpServer();
|
transport.PumpServer();
|
||||||
session.Tick();
|
session.Tick();
|
||||||
Thread.Sleep(1);
|
await Task.Yield();
|
||||||
|
|
||||||
if (s2cReceived == MessagesEachWay
|
if (s2cReceived == MessagesEachWay
|
||||||
&& c2sDispatched == MessagesEachWay
|
&& c2sDispatched == MessagesEachWay
|
||||||
|
|
|
||||||
|
|
@ -139,8 +139,14 @@ public sealed class DatSoundCacheTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[Trait("Status", "KnownFailure")]
|
||||||
public void GetWave_ConcurrentSameId_PublishesOneCanonicalWaveAndDecodesOnce()
|
public void GetWave_ConcurrentSameId_PublishesOneCanonicalWaveAndDecodesOnce()
|
||||||
{
|
{
|
||||||
|
// #321: a stale caller can pass the resident-cache check, pause, and
|
||||||
|
// reach _inflight after the winning caller has decoded, admitted, and
|
||||||
|
// removed its Lazy. That second Lazy performs a duplicate decode.
|
||||||
|
// R3 preserves this product defect as an explicit known-failure lane;
|
||||||
|
// fixing the cache algorithm is outside the test-only cleanup scope.
|
||||||
var dats = new FakeDatObjectSource();
|
var dats = new FakeDatObjectSource();
|
||||||
dats.AddWave(1, MakePcmWave(1, dataBytes: 1000));
|
dats.AddWave(1, MakePcmWave(1, dataBytes: 1000));
|
||||||
var cache = new DatSoundCache(dats, maxWaveBytes: 10_000);
|
var cache = new DatSoundCache(dats, maxWaveBytes: 10_000);
|
||||||
|
|
|
||||||
|
|
@ -2372,13 +2372,24 @@ public sealed class RuntimeCollisionReportingStateTests
|
||||||
Collisions(target.Key!.Value.LocalEntityId);
|
Collisions(target.Key!.Value.LocalEntityId);
|
||||||
Assert.False(Handle(lifetime, owner, 1d, collision));
|
Assert.False(Handle(lifetime, owner, 1d, collision));
|
||||||
|
|
||||||
_ = GC.GetAllocatedBytesForCurrentThread();
|
// One call does not cross tiered-JIT/PGO thresholds for this stack.
|
||||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
// Warm the same steady-contact path at the measured batch size.
|
||||||
for (int index = 0; index < 10_000; index++)
|
for (int index = 0; index < 10_000; index++)
|
||||||
_ = Handle(lifetime, owner, 1.1d, collision);
|
_ = Handle(lifetime, owner, 1.1d, collision);
|
||||||
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
|
||||||
|
|
||||||
Assert.Equal(0, allocated);
|
long minimumAllocated = long.MaxValue;
|
||||||
|
for (int sample = 0; sample < 5; sample++)
|
||||||
|
{
|
||||||
|
long before = GC.GetAllocatedBytesForCurrentThread();
|
||||||
|
for (int index = 0; index < 10_000; index++)
|
||||||
|
_ = Handle(lifetime, owner, 1.1d, collision);
|
||||||
|
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
||||||
|
minimumAllocated = Math.Min(minimumAllocated, allocated);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A real per-refresh regression allocates in every batch; a one-time
|
||||||
|
// tier transition does not invalidate the warmed steady-state claim.
|
||||||
|
Assert.Equal(0, minimumAllocated);
|
||||||
Assert.Equal(1, lifetime.Physics.CollisionReports.CaptureOwnership()
|
Assert.Equal(1, lifetime.Physics.CollisionReports.CaptureOwnership()
|
||||||
.TrackedObjectCount);
|
.TrackedObjectCount);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue