perf(worker): signal-driven event drain — removes the 25 ms latency floor and idle wakeups

This commit is contained in:
Joseph Doherty
2026-08-15 16:59:13 -04:00
parent dc9424d3bd
commit f4a6cb1db2
7 changed files with 383 additions and 24 deletions
@@ -15,6 +15,11 @@ namespace ZB.MOM.WW.MxGateway.Worker.Ipc;
public sealed class WorkerPipeSession
{
// Fallback ceiling for the event drain loop's wait — not a poll period. MxAccessEventQueue
// signals on enqueue (and on a recorded fault), so the loop wakes as soon as there is something
// to ship instead of paying up to this interval of latency on every burst from idle, and an
// idle worker parks instead of waking 40x/s. The interval survives only as the bound on how
// long the loop may sleep unsignalled, which keeps its DrainFault() poll on a known cadence.
private static readonly TimeSpan EventDrainInterval = TimeSpan.FromMilliseconds(25);
private static readonly TimeSpan BackgroundTaskStopTimeout = TimeSpan.FromSeconds(1);
private const uint EventDrainBatchSize = 128;
@@ -367,7 +372,15 @@ public sealed class WorkerPipeSession
IReadOnlyList<WorkerEvent> events = runtimeSession.DrainEvents(EventDrainBatchSize);
if (events.Count == 0)
{
await Task.Delay(EventDrainInterval, cancellationToken).ConfigureAwait(false);
// Wait on the queue's wake signal rather than sleeping a fixed tick: an event
// enqueued by the STA completes this immediately, so the first event of a burst is
// framed at signal latency instead of waiting out EventDrainInterval, and a session
// with no traffic stops waking at all. The wait's outcome is intentionally ignored —
// whether a signal or the fallback ended it, the next pass re-checks DrainFault()
// and re-drains, which is also why one coalesced wake for many enqueues is safe.
await runtimeSession
.WaitForEventsAsync(EventDrainInterval, cancellationToken)
.ConfigureAwait(false);
continue;
}