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
@@ -311,6 +311,61 @@ public sealed class WorkerPipeSessionTests
await SendShutdownAndWaitAsync(pipePair, runTask, cancellation.Token);
}
/// <summary>
/// The event drain loop waits on the runtime's wake signal instead of sleeping a fixed tick,
/// so an event enqueued at an idle worker is framed as soon as it is enqueued rather than up
/// to <c>EventDrainInterval</c> later. The fake's wait honours only the signal here, so the
/// event reaching the pipe is proof the enqueue woke the loop — a poll-driven loop would
/// never run again, and the test would fail on its cancellation deadline instead of passing
/// on a fallback tick that happened to fire. The loop is left parked on that wait before the
/// enqueue, which also makes the recorded fallback ceiling assertable.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RunAsync_EventAfterIdle_DrainLoopWakesOnSignalNotOnPollTick()
{
using CancellationTokenSource cancellation = new(TimeSpan.FromSeconds(10));
using PipePair pipePair = await PipePair.CreateAsync(cancellation.Token);
FakeRuntimeSession runtime = new()
{
WaitForEventsOnSignalOnly = true,
};
// A far-off heartbeat interval keeps the drain loop the only thing that can produce a frame
// after the first beat, so nothing else can mask a drain loop that never woke.
WorkerPipeSession session = CreatePipeSession(
pipePair.WorkerStream,
runtime,
new WorkerPipeSessionOptions
{
HeartbeatInterval = TimeSpan.FromMinutes(5),
HeartbeatGrace = TimeSpan.FromSeconds(30),
});
Task runTask = session.RunAsync(cancellation.Token);
await CompleteGatewayHandshakeAsync(pipePair, cancellation.Token);
// Park the drain loop: it drains empty once and then waits. Enqueuing before it parks would
// let the first drain pass find the event, which proves nothing about the wake.
while (runtime.LastWaitForEventsTimeout is null)
{
await Task.Delay(TimeSpan.FromMilliseconds(5), cancellation.Token);
}
runtime.EnqueueEvent(CreateWorkerEvent(sequence: 7));
WorkerEnvelope workerEvent = await ReadUntilAsync(
pipePair.GatewayReader,
WorkerEnvelope.BodyOneofCase.WorkerEvent,
cancellation.Token);
Assert.Equal(7UL, workerEvent.WorkerEvent.Event.WorkerSequence);
// The 25 ms survives as the ceiling the loop passes to every wait, not as a poll period.
Assert.Equal(TimeSpan.FromMilliseconds(25), runtime.LastWaitForEventsTimeout);
await SendShutdownAndWaitAsync(pipePair, runTask, cancellation.Token);
}
/// <summary>
/// Verifies that a Ping control command is answered on the worker side
/// (not dispatched to the STA) with an OK reply that echoes the ping