perf(worker): launcher-configurable event queue capacity

This commit is contained in:
Joseph Doherty
2026-08-15 17:10:54 -04:00
parent f3e1de5f37
commit 13583322b5
10 changed files with 253 additions and 5 deletions
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
@@ -28,6 +29,31 @@ public sealed class MxAccessEventQueue
/// </summary>
public const int DefaultCapacity = 10000;
/// <summary>
/// Environment variable the gateway's <c>WorkerProcessLauncher</c> sets
/// from <c>MxGateway:Worker:EventQueueCapacity</c>. A missing,
/// unparseable, or out-of-range value falls back to
/// <see cref="DefaultCapacity"/> — a bad environment value must never
/// keep the session from starting.
/// </summary>
internal const string CapacityEnvironmentVariableName = "MXGATEWAY_EVENT_QUEUE_CAPACITY";
/// <summary>
/// Floor on the resolved capacity. Mirrors the gateway-side
/// <c>MxGateway:Worker:EventQueueCapacity</c> minimum so a value that
/// slipped past startup validation still leaves the headroom the queue
/// needs: it has no drop policy, so an overflow faults the session.
/// </summary>
internal const int MinimumCapacity = 1000;
/// <summary>
/// Ceiling on the resolved capacity, mirroring the gateway-side maximum.
/// The backing queue pre-allocates its slots, so an absurd environment
/// value would otherwise cost the 32-bit worker that allocation at
/// startup.
/// </summary>
internal const int MaximumCapacity = 1_000_000;
// Extra per-event slack added to WorkerEvent.CalculateSize() when charging the byte budget in
// Drain(maxEvents, maxTotalBytes). CalculateSize() already accounts for the event's own tag and
// length-delimiter (the WorkerEvent wrapper serializes MxEvent as field 1, and the reply packs
@@ -80,6 +106,28 @@ public sealed class MxAccessEventQueue
events = new Queue<WorkerEvent>(capacity);
}
/// <summary>
/// Resolves the queue capacity from the launcher-provided environment
/// variable. A missing, unparseable, or out-of-range value falls back to
/// <see cref="DefaultCapacity"/> rather than throwing: running on the
/// default capacity is always safe, and a worker that refuses to start
/// over a mistyped environment variable is not.
/// </summary>
/// <returns>The capacity to construct the outbound event queue with.</returns>
internal static int ResolveCapacity()
{
string? value = Environment.GetEnvironmentVariable(CapacityEnvironmentVariableName);
return int.TryParse(
value,
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out int capacity)
&& capacity >= MinimumCapacity
&& capacity <= MaximumCapacity
? capacity
: DefaultCapacity;
}
/// <summary>
/// The queue's maximum capacity.
/// </summary>
@@ -63,12 +63,15 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
/// <summary>
/// Initializes a new instance of <see cref="MxAccessStaSession"/> with default dependencies.
/// The outbound event queue is sized from the launcher-provided
/// <c>MXGATEWAY_EVENT_QUEUE_CAPACITY</c> (see <see cref="MxAccessEventQueue.ResolveCapacity"/>);
/// callers that pass their own queue keep full control of its capacity.
/// </summary>
public MxAccessStaSession()
: this(
new StaRuntime(),
new MxAccessComObjectFactory(),
new MxAccessEventQueue())
new MxAccessEventQueue(MxAccessEventQueue.ResolveCapacity()))
{
}
@@ -84,7 +87,7 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
: this(
new StaRuntime(),
new MxAccessComObjectFactory(),
new MxAccessEventQueue(),
new MxAccessEventQueue(MxAccessEventQueue.ResolveCapacity()),
alarmCommandHandlerFactory)
{
}
@@ -99,7 +102,7 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
StaRuntime staRuntime,
IMxAccessComObjectFactory factory,
IMxAccessEventSink eventSink)
: this(staRuntime, factory, eventSink, new MxAccessEventQueue())
: this(staRuntime, factory, eventSink, new MxAccessEventQueue(MxAccessEventQueue.ResolveCapacity()))
{
}