mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
Reserve icon loading capacity for live demand
Limit speculative icon work to workerCount - 1 consumers so one existing worker slot remains available for a newly realized IconBox. At two workers this deliberately trades half of speculative concurrency for the live-demand guarantee; a single-worker configuration still processes speculative work normally. Report the configured and retained worker capacity alongside reservation deferrals, and cover the two-worker, four-worker, and single-worker contracts.
This commit is contained in:
@@ -247,6 +247,22 @@ internal static class IconLoadDiagnostics
|
|||||||
return new DemandedIdleCapacityMeasurement(session, startedAt);
|
return new DemandedIdleCapacityMeasurement(session, startedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static SpeculativeDispatchDeferralMeasurement? BeginSpeculativeDispatchDeferral(
|
||||||
|
int speculativeQueueDepth,
|
||||||
|
int workerCount,
|
||||||
|
int reservedWorkerSlots)
|
||||||
|
{
|
||||||
|
var session = Volatile.Read(ref _activeSession);
|
||||||
|
if (session is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startedAt = Stopwatch.GetTimestamp();
|
||||||
|
session.RecordSpeculativeDispatchDeferralStarted(speculativeQueueDepth, workerCount, reservedWorkerSlots);
|
||||||
|
return new SpeculativeDispatchDeferralMeasurement(session, startedAt, workerCount);
|
||||||
|
}
|
||||||
|
|
||||||
private static IconLoadInputKind ClassifyInput(string? iconString, bool hasStream)
|
private static IconLoadInputKind ClassifyInput(string? iconString, bool hasStream)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(iconString))
|
if (!string.IsNullOrEmpty(iconString))
|
||||||
@@ -415,4 +431,40 @@ internal static class IconLoadDiagnostics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class SpeculativeDispatchDeferralMeasurement
|
||||||
|
{
|
||||||
|
private readonly IconLoadDiagnosticsSession _session;
|
||||||
|
private readonly long _startedAt;
|
||||||
|
private readonly int _workerCount;
|
||||||
|
private int _completed;
|
||||||
|
|
||||||
|
public SpeculativeDispatchDeferralMeasurement(
|
||||||
|
IconLoadDiagnosticsSession session,
|
||||||
|
long startedAt,
|
||||||
|
int workerCount)
|
||||||
|
{
|
||||||
|
_session = session;
|
||||||
|
_startedAt = startedAt;
|
||||||
|
_workerCount = workerCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsForActiveSession => ReferenceEquals(_session, Volatile.Read(ref _activeSession));
|
||||||
|
|
||||||
|
public void Observe(int speculativeQueueDepth, int reservedWorkerSlots)
|
||||||
|
{
|
||||||
|
_session.RecordSpeculativeDispatchDeferralObserved(
|
||||||
|
speculativeQueueDepth,
|
||||||
|
_workerCount,
|
||||||
|
reservedWorkerSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Complete()
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref _completed, 1) == 0)
|
||||||
|
{
|
||||||
|
_session.RecordSpeculativeDispatchDeferralCompleted(Stopwatch.GetTimestamp() - _startedAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ internal sealed class IconLoadDiagnosticsSession
|
|||||||
private readonly DiagnosticHistogram _workerReadyToDemandedDispatchLatency = new();
|
private readonly DiagnosticHistogram _workerReadyToDemandedDispatchLatency = new();
|
||||||
private readonly DiagnosticHistogram _workerReadyToSpeculativeDispatchLatency = new();
|
private readonly DiagnosticHistogram _workerReadyToSpeculativeDispatchLatency = new();
|
||||||
private readonly DiagnosticHistogram _demandedIdleCapacityDuration = new();
|
private readonly DiagnosticHistogram _demandedIdleCapacityDuration = new();
|
||||||
|
private readonly DiagnosticHistogram _speculativeDispatchDeferralDuration = new();
|
||||||
private readonly InputKindMeasurements[] _inputKindMeasurements = CreateInputKindMeasurements();
|
private readonly InputKindMeasurements[] _inputKindMeasurements = CreateInputKindMeasurements();
|
||||||
private readonly ElementKindMeasurements[] _elementKindMeasurements = CreateElementKindMeasurements();
|
private readonly ElementKindMeasurements[] _elementKindMeasurements = CreateElementKindMeasurements();
|
||||||
private readonly ConditionalWeakTable<Task<IconSource?>, IconLoadMeasurement> _loadsByTask = new();
|
private readonly ConditionalWeakTable<Task<IconSource?>, IconLoadMeasurement> _loadsByTask = new();
|
||||||
@@ -142,6 +143,11 @@ internal sealed class IconLoadDiagnosticsSession
|
|||||||
private long _currentDemandedIdleCapacityIntervals;
|
private long _currentDemandedIdleCapacityIntervals;
|
||||||
private long _maximumDemandedQueueDepthWithIdleCapacity;
|
private long _maximumDemandedQueueDepthWithIdleCapacity;
|
||||||
private long _maximumAvailableWorkerSlotsWithDemandedWork;
|
private long _maximumAvailableWorkerSlotsWithDemandedWork;
|
||||||
|
private long _speculativeDispatchDeferralIntervalsStarted;
|
||||||
|
private long _currentSpeculativeDispatchDeferralIntervals;
|
||||||
|
private long _maximumSpeculativeQueueDepthDuringDeferral;
|
||||||
|
private long _maximumWorkerCountDuringSpeculativeDispatchDeferral;
|
||||||
|
private long _maximumReservedWorkerSlotsDuringDeferral;
|
||||||
private long _activeWorkers;
|
private long _activeWorkers;
|
||||||
private long _maximumActiveWorkers;
|
private long _maximumActiveWorkers;
|
||||||
private long _dispatcherEnqueuedDemanded;
|
private long _dispatcherEnqueuedDemanded;
|
||||||
@@ -315,6 +321,34 @@ internal sealed class IconLoadDiagnosticsSession
|
|||||||
IconLoadEventSource.Log.DemandedIdleCapacityCompleted(Id, ToMicroseconds(elapsedTicks));
|
IconLoadEventSource.Log.DemandedIdleCapacityCompleted(Id, ToMicroseconds(elapsedTicks));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RecordSpeculativeDispatchDeferralStarted(
|
||||||
|
int speculativeQueueDepth,
|
||||||
|
int workerCount,
|
||||||
|
int reservedWorkerSlots)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _speculativeDispatchDeferralIntervalsStarted);
|
||||||
|
Interlocked.Increment(ref _currentSpeculativeDispatchDeferralIntervals);
|
||||||
|
RecordSpeculativeDispatchDeferralObserved(speculativeQueueDepth, workerCount, reservedWorkerSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecordSpeculativeDispatchDeferralObserved(
|
||||||
|
int speculativeQueueDepth,
|
||||||
|
int workerCount,
|
||||||
|
int reservedWorkerSlots)
|
||||||
|
{
|
||||||
|
UpdateMaximum(ref _maximumSpeculativeQueueDepthDuringDeferral, speculativeQueueDepth);
|
||||||
|
UpdateMaximum(ref _maximumWorkerCountDuringSpeculativeDispatchDeferral, workerCount);
|
||||||
|
UpdateMaximum(ref _maximumReservedWorkerSlotsDuringDeferral, reservedWorkerSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecordSpeculativeDispatchDeferralCompleted(long elapsedTicks)
|
||||||
|
{
|
||||||
|
var activeIntervals = Interlocked.Decrement(ref _currentSpeculativeDispatchDeferralIntervals);
|
||||||
|
Debug.Assert(activeIntervals >= 0, "A speculative-dispatch-deferral interval must start before it completes.");
|
||||||
|
_speculativeDispatchDeferralDuration.Record(elapsedTicks);
|
||||||
|
IconLoadEventSource.Log.SpeculativeDispatchDeferralCompleted(Id, ToMicroseconds(elapsedTicks));
|
||||||
|
}
|
||||||
|
|
||||||
public IconRequestMeasurement BeginRequest(IconRequestReason reason, double scale, IconRequestOrigin origin)
|
public IconRequestMeasurement BeginRequest(IconRequestReason reason, double scale, IconRequestOrigin origin)
|
||||||
{
|
{
|
||||||
origin = origin.Normalize();
|
origin = origin.Normalize();
|
||||||
@@ -1590,6 +1624,34 @@ internal sealed class IconLoadDiagnosticsSession
|
|||||||
Volatile.Read(ref _maximumAvailableWorkerSlotsWithDemandedWork),
|
Volatile.Read(ref _maximumAvailableWorkerSlotsWithDemandedWork),
|
||||||
" ");
|
" ");
|
||||||
_demandedIdleCapacityDuration.Append(builder, "Interval duration", " ");
|
_demandedIdleCapacityDuration.Append(builder, "Interval duration", " ");
|
||||||
|
builder.AppendLine(" Speculative dispatch deferred by the demand reserve");
|
||||||
|
builder.AppendLine(" Definition: a coordinator-state interval with speculative work queued, no demanded work queued, and a worker-ready slot deliberately retained for a future live request.");
|
||||||
|
AppendValue(
|
||||||
|
builder,
|
||||||
|
"Intervals started",
|
||||||
|
Volatile.Read(ref _speculativeDispatchDeferralIntervalsStarted),
|
||||||
|
" ");
|
||||||
|
AppendValue(
|
||||||
|
builder,
|
||||||
|
"Intervals active at stop",
|
||||||
|
Math.Max(0, Volatile.Read(ref _currentSpeculativeDispatchDeferralIntervals)),
|
||||||
|
" ");
|
||||||
|
AppendValue(
|
||||||
|
builder,
|
||||||
|
"Maximum speculative queue depth during an interval",
|
||||||
|
Volatile.Read(ref _maximumSpeculativeQueueDepthDuringDeferral),
|
||||||
|
" ");
|
||||||
|
AppendValue(
|
||||||
|
builder,
|
||||||
|
"Maximum configured worker count during an interval",
|
||||||
|
Volatile.Read(ref _maximumWorkerCountDuringSpeculativeDispatchDeferral),
|
||||||
|
" ");
|
||||||
|
AppendValue(
|
||||||
|
builder,
|
||||||
|
"Maximum worker-ready slots retained during an interval",
|
||||||
|
Volatile.Read(ref _maximumReservedWorkerSlotsDuringDeferral),
|
||||||
|
" ");
|
||||||
|
_speculativeDispatchDeferralDuration.Append(builder, "Interval duration", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AppendLoadDemandMeasurements(StringBuilder builder)
|
private void AppendLoadDemandMeasurements(StringBuilder builder)
|
||||||
|
|||||||
@@ -403,6 +403,17 @@ internal sealed partial class IconLoadEventSource : EventSource
|
|||||||
WriteEvent(37, sessionId, elapsedMicroseconds);
|
WriteEvent(37, sessionId, elapsedMicroseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Event(38, Level = EventLevel.Informational)]
|
||||||
|
public void SpeculativeDispatchDeferralCompleted(long sessionId, long elapsedMicroseconds)
|
||||||
|
{
|
||||||
|
if (!IsEnabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteEvent(38, sessionId, elapsedMicroseconds);
|
||||||
|
}
|
||||||
|
|
||||||
// These exact overloads intentionally shadow EventSource.WriteEvent(params object?[]).
|
// These exact overloads intentionally shadow EventSource.WriteEvent(params object?[]).
|
||||||
// The params overload allocates an array and boxes values while ETW is enabled, which
|
// The params overload allocates an array and boxes values while ETW is enabled, which
|
||||||
// would make the icon diagnostics measurably perturb the paths they are observing.
|
// would make the icon diagnostics measurably perturb the paths they are observing.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ internal sealed class IconLoadQueue
|
|||||||
private readonly Queue<IconLoadDiagnostics.SchedulerCommandMeasurement?> _availableWorkerMeasurements = new();
|
private readonly Queue<IconLoadDiagnostics.SchedulerCommandMeasurement?> _availableWorkerMeasurements = new();
|
||||||
private readonly TaskCompletionSource<bool> _schedulerCompletion = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
private readonly TaskCompletionSource<bool> _schedulerCompletion = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
private readonly Thread _schedulerThread;
|
private readonly Thread _schedulerThread;
|
||||||
|
private readonly int _workerSlotsReservedForDemand;
|
||||||
private readonly int _workerCount;
|
private readonly int _workerCount;
|
||||||
|
|
||||||
private TaskCompletionSource<bool>? _faultedEnqueuePublishersDrained;
|
private TaskCompletionSource<bool>? _faultedEnqueuePublishersDrained;
|
||||||
@@ -41,11 +42,18 @@ internal sealed class IconLoadQueue
|
|||||||
private int _availableWorkerSlots;
|
private int _availableWorkerSlots;
|
||||||
private bool _completionRequested;
|
private bool _completionRequested;
|
||||||
private IconLoadDiagnostics.DemandedIdleCapacityMeasurement? _demandedIdleCapacityMeasurement;
|
private IconLoadDiagnostics.DemandedIdleCapacityMeasurement? _demandedIdleCapacityMeasurement;
|
||||||
|
private IconLoadDiagnostics.SpeculativeDispatchDeferralMeasurement? _speculativeDispatchDeferralMeasurement;
|
||||||
|
|
||||||
public IconLoadQueue(int workerCount)
|
public IconLoadQueue(int workerCount)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfLessThan(workerCount, 1);
|
ArgumentOutOfRangeException.ThrowIfLessThan(workerCount, 1);
|
||||||
_workerCount = workerCount;
|
_workerCount = workerCount;
|
||||||
|
|
||||||
|
// Keep one consumer waiting for live demand while only speculative work is
|
||||||
|
// queued. At two workers this deliberately halves speculative concurrency:
|
||||||
|
// reserving no slot would let non-preemptible speculative loads occupy all
|
||||||
|
// capacity. A single-worker queue must still make progress.
|
||||||
|
_workerSlotsReservedForDemand = workerCount > 1 ? 1 : 0;
|
||||||
_readyWork = Channel.CreateUnbounded<Operation>(new UnboundedChannelOptions
|
_readyWork = Channel.CreateUnbounded<Operation>(new UnboundedChannelOptions
|
||||||
{
|
{
|
||||||
SingleReader = false,
|
SingleReader = false,
|
||||||
@@ -253,9 +261,12 @@ internal sealed class IconLoadQueue
|
|||||||
|
|
||||||
command.Measurement?.Processed();
|
command.Measurement?.Processed();
|
||||||
Apply(command);
|
Apply(command);
|
||||||
if (command.Measurement is not null || _demandedIdleCapacityMeasurement is not null)
|
if (command.Measurement is not null
|
||||||
|
|| _demandedIdleCapacityMeasurement is not null
|
||||||
|
|| _speculativeDispatchDeferralMeasurement is not null)
|
||||||
{
|
{
|
||||||
UpdateDemandedIdleCapacityMeasurement();
|
UpdateDemandedIdleCapacityMeasurement();
|
||||||
|
UpdateSpeculativeDispatchDeferralMeasurement();
|
||||||
}
|
}
|
||||||
|
|
||||||
commandBeingProcessed = null;
|
commandBeingProcessed = null;
|
||||||
@@ -268,6 +279,10 @@ internal sealed class IconLoadQueue
|
|||||||
UpdateDemandedIdleCapacityMeasurement();
|
UpdateDemandedIdleCapacityMeasurement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispatch can consume the last non-reserved slot and begin a
|
||||||
|
// deferral interval without another command changing state.
|
||||||
|
UpdateSpeculativeDispatchDeferralMeasurement();
|
||||||
|
|
||||||
if (measureBatch)
|
if (measureBatch)
|
||||||
{
|
{
|
||||||
wakeMeasurement!.BatchCompleted(
|
wakeMeasurement!.BatchCompleted(
|
||||||
@@ -293,6 +308,7 @@ internal sealed class IconLoadQueue
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
CompleteDemandedIdleCapacityMeasurement();
|
CompleteDemandedIdleCapacityMeasurement();
|
||||||
|
CompleteSpeculativeDispatchDeferralMeasurement();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -421,7 +437,7 @@ internal sealed class IconLoadQueue
|
|||||||
private int DispatchAvailableWorkers()
|
private int DispatchAvailableWorkers()
|
||||||
{
|
{
|
||||||
var dispatchedWorkItemCount = 0;
|
var dispatchedWorkItemCount = 0;
|
||||||
while (_availableWorkerSlots > 0 && RemoveNext() is { } item)
|
while (CanDispatchNextWorkItem() && RemoveNext() is { } item)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -448,6 +464,24 @@ internal sealed class IconLoadQueue
|
|||||||
return dispatchedWorkItemCount;
|
return dispatchedWorkItemCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool CanDispatchNextWorkItem()
|
||||||
|
{
|
||||||
|
if (_availableWorkerSlots == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_demandedHigh.Count != 0 || _demandedLow.Count != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speculative work uses at most workerCount - 1 consumers. Because a
|
||||||
|
// consumer publishes WorkerReady only after its previous load completes,
|
||||||
|
// retaining this slot also bounds the number of active speculative loads.
|
||||||
|
return _availableWorkerSlots > _workerSlotsReservedForDemand;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateDemandedIdleCapacityMeasurement()
|
private void UpdateDemandedIdleCapacityMeasurement()
|
||||||
{
|
{
|
||||||
var hasDemandedWorkWithIdleCapacity = _availableWorkerSlots > 0
|
var hasDemandedWorkWithIdleCapacity = _availableWorkerSlots > 0
|
||||||
@@ -479,6 +513,43 @@ internal sealed class IconLoadQueue
|
|||||||
_demandedIdleCapacityMeasurement = null;
|
_demandedIdleCapacityMeasurement = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateSpeculativeDispatchDeferralMeasurement()
|
||||||
|
{
|
||||||
|
var speculativeQueueDepth = _speculativeHigh.Count + _speculativeLow.Count;
|
||||||
|
var reserveIsDeferringWork = _workerSlotsReservedForDemand > 0
|
||||||
|
&& _availableWorkerSlots > 0
|
||||||
|
&& _availableWorkerSlots <= _workerSlotsReservedForDemand
|
||||||
|
&& _demandedHigh.Count == 0
|
||||||
|
&& _demandedLow.Count == 0
|
||||||
|
&& speculativeQueueDepth > 0;
|
||||||
|
if (!reserveIsDeferringWork)
|
||||||
|
{
|
||||||
|
CompleteSpeculativeDispatchDeferralMeasurement();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_speculativeDispatchDeferralMeasurement is null
|
||||||
|
|| !_speculativeDispatchDeferralMeasurement.IsForActiveSession)
|
||||||
|
{
|
||||||
|
CompleteSpeculativeDispatchDeferralMeasurement();
|
||||||
|
_speculativeDispatchDeferralMeasurement = IconLoadDiagnostics.BeginSpeculativeDispatchDeferral(
|
||||||
|
speculativeQueueDepth,
|
||||||
|
_workerCount,
|
||||||
|
_availableWorkerSlots);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_speculativeDispatchDeferralMeasurement.Observe(
|
||||||
|
speculativeQueueDepth,
|
||||||
|
_availableWorkerSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CompleteSpeculativeDispatchDeferralMeasurement()
|
||||||
|
{
|
||||||
|
_speculativeDispatchDeferralMeasurement?.Complete();
|
||||||
|
_speculativeDispatchDeferralMeasurement = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Strict order by design: speculative work may starve while demanded work keeps
|
// Strict order by design: speculative work may starve while demanded work keeps
|
||||||
// arriving, since no live request is waiting on it.
|
// arriving, since no live request is waiting on it.
|
||||||
private WorkItem? RemoveNext()
|
private WorkItem? RemoveNext()
|
||||||
|
|||||||
@@ -279,6 +279,58 @@ public class IconLoadDiagnosticsTests
|
|||||||
StringAssert.Contains(report.Text, " Interval duration: count=1");
|
StringAssert.Contains(report.Text, " Interval duration: count=1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[Timeout(5_000)]
|
||||||
|
public async Task SchedulerReportCapturesSpeculativeDemandReserve()
|
||||||
|
{
|
||||||
|
IconLoadDiagnostics.Start();
|
||||||
|
var queue = new IconLoadQueue(workerCount: 4);
|
||||||
|
var speculativeWork = new TestOperation();
|
||||||
|
var demandedWork = new TestOperation();
|
||||||
|
var speculativeDemand = IconLoadDemand.CreateDemanded();
|
||||||
|
speculativeDemand.RemoveRequester();
|
||||||
|
|
||||||
|
Assert.IsTrue(queue.TryEnqueue(
|
||||||
|
speculativeWork,
|
||||||
|
IconLoadPriority.Low,
|
||||||
|
speculativeDemand,
|
||||||
|
out _));
|
||||||
|
|
||||||
|
var reservedDequeue = queue.DequeueAsync().AsTask();
|
||||||
|
Assert.IsTrue(queue.TryEnqueue(
|
||||||
|
demandedWork,
|
||||||
|
IconLoadPriority.Low,
|
||||||
|
IconLoadDemand.CreateDemanded(),
|
||||||
|
out _));
|
||||||
|
Assert.AreSame(demandedWork, await reservedDequeue);
|
||||||
|
|
||||||
|
var firstReadyWorker = queue.DequeueAsync().AsTask();
|
||||||
|
var secondReadyWorker = queue.DequeueAsync().AsTask();
|
||||||
|
var speculativeDequeue = await Task.WhenAny(firstReadyWorker, secondReadyWorker);
|
||||||
|
Assert.AreSame(speculativeWork, await speculativeDequeue);
|
||||||
|
|
||||||
|
queue.Complete();
|
||||||
|
var remainingDequeue = ReferenceEquals(speculativeDequeue, firstReadyWorker)
|
||||||
|
? secondReadyWorker
|
||||||
|
: firstReadyWorker;
|
||||||
|
Assert.IsNull(await remainingDequeue);
|
||||||
|
await queue.Completion;
|
||||||
|
|
||||||
|
var report = IconLoadDiagnostics.StopAndCreateReport();
|
||||||
|
|
||||||
|
Assert.IsNotNull(report);
|
||||||
|
var reserveBlock =
|
||||||
|
$" Speculative dispatch deferred by the demand reserve{Environment.NewLine}" +
|
||||||
|
$" Definition: a coordinator-state interval with speculative work queued, no demanded work queued, and a worker-ready slot deliberately retained for a future live request.{Environment.NewLine}" +
|
||||||
|
$" Intervals started: 2{Environment.NewLine}" +
|
||||||
|
$" Intervals active at stop: 0{Environment.NewLine}" +
|
||||||
|
$" Maximum speculative queue depth during an interval: 1{Environment.NewLine}" +
|
||||||
|
$" Maximum configured worker count during an interval: 4{Environment.NewLine}" +
|
||||||
|
$" Maximum worker-ready slots retained during an interval: 1{Environment.NewLine}" +
|
||||||
|
$" Interval duration: count=2";
|
||||||
|
StringAssert.Contains(report.Text, reserveBlock);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SchedulerReportSeparatesEmptyCoalescedBatchWakeLatency()
|
public void SchedulerReportSeparatesEmptyCoalescedBatchWakeLatency()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ public sealed class IconLoadEventSourceTests
|
|||||||
log.DispatcherUiSliceCompleted(11, 14, 52, 53, isDemanded: true, 54);
|
log.DispatcherUiSliceCompleted(11, 14, 52, 53, isDemanded: true, 54);
|
||||||
log.DispatcherAsyncSuspensionCompleted(11, 14, 55, isDemanded: false, 56);
|
log.DispatcherAsyncSuspensionCompleted(11, 14, 55, isDemanded: false, 56);
|
||||||
log.UiResponsivenessProbeCompleted(11, 57);
|
log.UiResponsivenessProbeCompleted(11, 57);
|
||||||
|
log.SpeculativeDispatchDeferralCompleted(11, 69);
|
||||||
|
|
||||||
Assert.AreEqual(30, listener.Events.Count);
|
Assert.AreEqual(31, listener.Events.Count);
|
||||||
Assert.IsFalse(listener.Events.Any(e => e.EventId == 0), listener.GetEventSourceErrors());
|
Assert.IsFalse(listener.Events.Any(e => e.EventId == 0), listener.GetEventSourceErrors());
|
||||||
|
|
||||||
CollectionAssert.AreEqual(
|
CollectionAssert.AreEqual(
|
||||||
@@ -88,6 +89,9 @@ public sealed class IconLoadEventSourceTests
|
|||||||
CollectionAssert.AreEqual(
|
CollectionAssert.AreEqual(
|
||||||
new object?[] { 11L, 14L, 55, false, 56L },
|
new object?[] { 11L, 14L, 55, false, 56L },
|
||||||
listener.GetEvent(36).Payload!.ToArray());
|
listener.GetEvent(36).Payload!.ToArray());
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new object?[] { 11L, 69L },
|
||||||
|
listener.GetEvent(38).Payload!.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class CollectingEventListener : EventListener
|
private sealed class CollectingEventListener : EventListener
|
||||||
|
|||||||
@@ -145,6 +145,89 @@ public class IconLoadQueueTests
|
|||||||
await queue.Completion;
|
await queue.Completion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DataRow(2)]
|
||||||
|
[DataRow(4)]
|
||||||
|
[Timeout(5_000)]
|
||||||
|
public async Task SpeculativeWorkLeavesOneWorkerAvailableForDemand(int workerCount)
|
||||||
|
{
|
||||||
|
var queue = new IconLoadQueue(workerCount);
|
||||||
|
var releaseSpeculativeWork = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var speculativeCapacityFilled = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var demandedWorkStarted = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var speculativeStarts = 0;
|
||||||
|
var workers = new Task[workerCount];
|
||||||
|
|
||||||
|
for (var i = 0; i < workers.Length; i++)
|
||||||
|
{
|
||||||
|
workers[i] = RunWorkerAsync(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (var i = 0; i < workerCount; i++)
|
||||||
|
{
|
||||||
|
var speculativeDemand = IconLoadDemand.CreateDemanded();
|
||||||
|
speculativeDemand.RemoveRequester();
|
||||||
|
Assert.IsTrue(queue.TryEnqueue(
|
||||||
|
new TestOperation(async () =>
|
||||||
|
{
|
||||||
|
if (Interlocked.Increment(ref speculativeStarts) == workerCount - 1)
|
||||||
|
{
|
||||||
|
speculativeCapacityFilled.TrySetResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await releaseSpeculativeWork.Task;
|
||||||
|
}),
|
||||||
|
IconLoadPriority.Low,
|
||||||
|
speculativeDemand,
|
||||||
|
out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
await speculativeCapacityFilled.Task.WaitAsync(TimeSpan.FromSeconds(1));
|
||||||
|
|
||||||
|
Assert.IsTrue(queue.TryEnqueue(
|
||||||
|
new TestOperation(() =>
|
||||||
|
{
|
||||||
|
demandedWorkStarted.TrySetResult(true);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}),
|
||||||
|
IconLoadPriority.Low,
|
||||||
|
IconLoadDemand.CreateDemanded(),
|
||||||
|
out _));
|
||||||
|
|
||||||
|
await demandedWorkStarted.Task.WaitAsync(TimeSpan.FromSeconds(1));
|
||||||
|
Assert.AreEqual(workerCount - 1, Volatile.Read(ref speculativeStarts));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
queue.Complete();
|
||||||
|
releaseSpeculativeWork.TrySetResult(true);
|
||||||
|
await Task.WhenAll(workers);
|
||||||
|
await queue.Completion;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(workerCount, speculativeStarts);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[Timeout(5_000)]
|
||||||
|
public async Task SingleWorkerStillProcessesSpeculativeWork()
|
||||||
|
{
|
||||||
|
var queue = new IconLoadQueue(workerCount: 1);
|
||||||
|
var work = new TestOperation();
|
||||||
|
var speculativeDemand = IconLoadDemand.CreateDemanded();
|
||||||
|
speculativeDemand.RemoveRequester();
|
||||||
|
|
||||||
|
var dequeue = queue.DequeueAsync().AsTask();
|
||||||
|
Assert.IsTrue(queue.TryEnqueue(work, IconLoadPriority.Low, speculativeDemand, out _));
|
||||||
|
|
||||||
|
Assert.AreSame(work, await dequeue);
|
||||||
|
queue.Complete();
|
||||||
|
Assert.IsNull(await queue.DequeueAsync());
|
||||||
|
await queue.Completion;
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[Timeout(10_000)]
|
[Timeout(10_000)]
|
||||||
public async Task DemandChurnDuringDequeueRunsEveryWorkExactlyOnce()
|
public async Task DemandChurnDuringDequeueRunsEveryWorkExactlyOnce()
|
||||||
|
|||||||
Reference in New Issue
Block a user