diff --git a/docs/Contracts.md b/docs/Contracts.md index cfb14a3..e071e6a 100644 --- a/docs/Contracts.md +++ b/docs/Contracts.md @@ -94,6 +94,15 @@ only and does not share types with `mxaccess_gateway.proto`. See [Galaxy Repository Browse](./GalaxyRepository.md) for the RPC catalog and behavior. +The retained proto remains the client-generation source of truth while the +gateway serves the wire-identical descriptor from the +`ZB.MOM.WW.GalaxyRepository` package. `GalaxyContractParityTests` converts both +loaded descriptors to `FileDescriptorProto`, removes source locations, and +normalizes the C# namespace and file name before comparing deterministic bytes. +Its mutation test also requires drift failures to identify the first differing +protobuf message. Any parity failure must be resolved before publishing either +Contracts or clients. + Generated C# output is written to `src/ZB.MOM.WW.MxGateway.Contracts/Generated/`. Do not hand-edit generated files. @@ -140,6 +149,13 @@ Run the focused contract tests after changing either `.proto` file: dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter ProtobufContractRoundTripTests ``` +When changing `galaxy_repository.proto` or the served Galaxy Repository +package, also run the retained-vs-served parity guard: + +```bash +dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter GalaxyContractParityTests +``` + The full solution build also regenerates the C# contracts before compiling gateway and test projects: diff --git a/docs/MxAccessWorkerInstanceDesign.md b/docs/MxAccessWorkerInstanceDesign.md index fab6491..72cf562 100644 --- a/docs/MxAccessWorkerInstanceDesign.md +++ b/docs/MxAccessWorkerInstanceDesign.md @@ -90,13 +90,22 @@ Expected command-line arguments: --protocol-version ``` -Expected protected environment values: +Expected launch environment values (the shared names are defined by +`WorkerLaunchEnvironment` in Contracts): ```text MXGATEWAY_WORKER_NONCE= -MXGATEWAY_WORKER_LOG_CONTEXT= +MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS= +MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS= +MXGATEWAY_EVENT_QUEUE_CAPACITY= +MXGATEWAY_ALARM_POLL_INTERVAL_MS= +MXGATEWAY_ALARM_MAX_ALARMS_PER_FETCH= ``` +The nonce is protected bootstrap material. The remaining values are derived +from the public `MxGateway:Worker:*` and `MxGateway:Alarms:*` configuration +keys documented in [Worker Process Launcher](./WorkerProcessLauncher.md). + Startup sequence: 1. Parse command-line arguments. @@ -312,7 +321,8 @@ write wait still yields the empty-`statuses` unconfirmed reply, and an expired per-tag `ReadBulk` wait still reports its own timeout. The write wait's budget is `MxGateway:Worker:WriteCompletionWaitMilliseconds` -(default 1500). It is a bounded hold on the STA per unary write, so deployments +(default 1500). It applies to `Write`, `Write2`, `WriteSecured`, and +`WriteSecured2` and is a bounded hold on the STA per single-item write, so deployments whose write workload is effectively fire-and-forget — no consumer reads the reply's `statuses` — can lower it, or set `0` to skip the wait entirely and reply on acceptance alone. diff --git a/docs/WorkerProcessLauncher.md b/docs/WorkerProcessLauncher.md index 235031d..aec577f 100644 --- a/docs/WorkerProcessLauncher.md +++ b/docs/WorkerProcessLauncher.md @@ -26,6 +26,23 @@ The launcher sets the nonce through the `MXGATEWAY_WORKER_NONCE` environment variable. The nonce is not included in `WorkerProcessCommandLine` so logs and diagnostics can report the launch command without exposing the secret. +All launch-variable names live in the shared Contracts +`WorkerLaunchEnvironment` class and are consumed by both the launcher and the +worker. The launcher always exports all six values: + +| Environment variable | Gateway source | Worker default when missing/invalid | +|---|---|---| +| `MXGATEWAY_WORKER_NONCE` | `WorkerProcessLaunchRequest.Nonce` | None; bootstrap fails because the nonce is required | +| `MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS` | `MxGateway:Worker:PipeConnectAttemptTimeoutMilliseconds` | `2000` ms | +| `MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS` | `MxGateway:Worker:WriteCompletionWaitMilliseconds` | `1500` ms | +| `MXGATEWAY_EVENT_QUEUE_CAPACITY` | `MxGateway:Worker:EventQueueCapacity` | `10000` events | +| `MXGATEWAY_ALARM_POLL_INTERVAL_MS` | `MxGateway:Alarms:PollIntervalMilliseconds` | `500` ms | +| `MXGATEWAY_ALARM_MAX_ALARMS_PER_FETCH` | `MxGateway:Alarms:MaxAlarmsPerFetch` | `1024` alarms | + +The write-completion wait applies to the four single-item write variants: +`Write`, `Write2`, `WriteSecured`, and `WriteSecured2`. Bulk writes report COM +acceptance per entry and do not wait for `OnWriteComplete` rows. + ## Validation And Cleanup Before starting the process, the launcher validates that the configured worker @@ -44,11 +61,8 @@ process remains alive. The policy is configured by `WorkerOptions.StartupProbeRetryDelayMilliseconds`; the retry counter is recorded as `mxgateway.retries.attempted` with `area=worker_startup`. -The launcher also passes -`MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS` to the worker process from -`WorkerOptions.PipeConnectAttemptTimeoutMilliseconds`. The worker uses that -value as the per-attempt named-pipe connect timeout inside its own bounded -Polly retry loop. +The worker uses the exported pipe-attempt value inside its own bounded Polly +retry loop. If startup fails or exceeds `WorkerOptions.StartupTimeoutSeconds`, the launcher kills the worker process tree, disposes the process handle, disposes the diff --git a/src/ZB.MOM.WW.MxGateway.Contracts/WorkerLaunchEnvironment.cs b/src/ZB.MOM.WW.MxGateway.Contracts/WorkerLaunchEnvironment.cs new file mode 100644 index 0000000..47a84e1 --- /dev/null +++ b/src/ZB.MOM.WW.MxGateway.Contracts/WorkerLaunchEnvironment.cs @@ -0,0 +1,25 @@ +namespace ZB.MOM.WW.MxGateway.Contracts; + +/// Names of environment variables shared by the gateway worker launcher and worker process. +public static class WorkerLaunchEnvironment +{ + /// Bootstrap nonce used to authenticate the worker handshake. + public const string WorkerNonce = "MXGATEWAY_WORKER_NONCE"; + + /// Per-attempt named-pipe connection timeout in milliseconds. + public const string PipeConnectAttemptTimeoutMilliseconds = + "MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS"; + + /// Bounded wait for a Write/Write2/WriteSecured/WriteSecured2 completion callback in milliseconds. + public const string WriteCompletionWaitMilliseconds = + "MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS"; + + /// Capacity of the worker's outbound MXAccess event queue. + public const string EventQueueCapacity = "MXGATEWAY_EVENT_QUEUE_CAPACITY"; + + /// Worker alarm polling cadence in milliseconds. + public const string AlarmPollIntervalMilliseconds = "MXGATEWAY_ALARM_POLL_INTERVAL_MS"; + + /// Maximum alarm records requested in one current-alarm fetch. + public const string MaxAlarmsPerFetch = "MXGATEWAY_ALARM_MAX_ALARMS_PER_FETCH"; +} diff --git a/src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj b/src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj index 99b99df..4c4f88b 100644 --- a/src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj +++ b/src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj @@ -37,7 +37,9 @@ the wire-identical Galaxy types from the ZB.MOM.WW.GalaxyRepository package. This proto is still the generation source of truth for the language clients (Go/Rust/Java/Python compile it by path; the .NET client consumes Contracts.Proto.Galaxy.* via this project reference) and for - clients/proto/proto-inputs.json. Removing it breaks all five clients — do not delete. --> + clients/proto/proto-inputs.json. GalaxyContractParityTests normalizes its descriptor against + the descriptor served by the package and fails on wire drift. Removing it breaks all five + clients — do not delete. --> diff --git a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerProcessLauncher.cs b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerProcessLauncher.cs index 96d07c6..de305b5 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerProcessLauncher.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerProcessLauncher.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using ZB.MOM.WW.MxGateway.Contracts; using ZB.MOM.WW.MxGateway.Server.Configuration; using ZB.MOM.WW.MxGateway.Server.Metrics; using Polly; @@ -15,41 +16,47 @@ namespace ZB.MOM.WW.MxGateway.Server.Workers; public sealed class WorkerProcessLauncher : IWorkerProcessLauncher { /// Environment variable for worker nonce. - public const string WorkerNonceEnvironmentVariableName = "MXGATEWAY_WORKER_NONCE"; + [Obsolete("Use WorkerLaunchEnvironment.WorkerNonce.")] + public const string WorkerNonceEnvironmentVariableName = WorkerLaunchEnvironment.WorkerNonce; /// Environment variable for worker pipe connect attempt timeout. + [Obsolete("Use WorkerLaunchEnvironment.PipeConnectAttemptTimeoutMilliseconds.")] public const string WorkerPipeConnectAttemptTimeoutEnvironmentVariableName = - "MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS"; + WorkerLaunchEnvironment.PipeConnectAttemptTimeoutMilliseconds; /// /// Conveys MxGateway:Worker:WriteCompletionWaitMilliseconds to the worker: - /// the bounded wait for the OnWriteComplete callback on - /// WriteSecured/WriteSecured2 replies. 0 disables the wait. + /// the bounded wait for the OnWriteComplete callback on Write, Write2, + /// WriteSecured, and WriteSecured2 replies. 0 disables the wait. /// + [Obsolete("Use WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds.")] public const string WorkerWriteCompletionWaitEnvironmentVariableName = - "MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS"; + WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds; /// /// Conveys MxGateway:Alarms:PollIntervalMilliseconds to the worker: the /// cadence at which the worker's STA polls the AVEVA alarm consumer. /// + [Obsolete("Use WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds.")] public const string WorkerAlarmPollIntervalEnvironmentVariableName = - "MXGATEWAY_ALARM_POLL_INTERVAL_MS"; + WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds; /// /// Conveys MxGateway:Alarms:MaxAlarmsPerFetch to the worker: the cap /// passed to GetXmlCurrentAlarms2, which is also the record count at /// which the worker treats a fetch as truncated. /// + [Obsolete("Use WorkerLaunchEnvironment.MaxAlarmsPerFetch.")] public const string WorkerMaxAlarmsPerFetchEnvironmentVariableName = - "MXGATEWAY_ALARM_MAX_ALARMS_PER_FETCH"; + WorkerLaunchEnvironment.MaxAlarmsPerFetch; /// /// Conveys MxGateway:Worker:EventQueueCapacity to the worker: the capacity /// of the outbound MXAccess event queue, whose overflow faults the session. /// + [Obsolete("Use WorkerLaunchEnvironment.EventQueueCapacity.")] public const string WorkerEventQueueCapacityEnvironmentVariableName = - "MXGATEWAY_EVENT_QUEUE_CAPACITY"; + WorkerLaunchEnvironment.EventQueueCapacity; private readonly IWorkerProcessFactory _processFactory; private readonly IWorkerStartupProbe _startupProbe; @@ -204,16 +211,16 @@ public sealed class WorkerProcessLauncher : IWorkerProcessLauncher startInfo.ArgumentList.Add(argument); } - startInfo.Environment[WorkerNonceEnvironmentVariableName] = request.Nonce; - startInfo.Environment[WorkerPipeConnectAttemptTimeoutEnvironmentVariableName] = + startInfo.Environment[WorkerLaunchEnvironment.WorkerNonce] = request.Nonce; + startInfo.Environment[WorkerLaunchEnvironment.PipeConnectAttemptTimeoutMilliseconds] = _workerOptions.PipeConnectAttemptTimeoutMilliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture); - startInfo.Environment[WorkerWriteCompletionWaitEnvironmentVariableName] = + startInfo.Environment[WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds] = _workerOptions.WriteCompletionWaitMilliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture); - startInfo.Environment[WorkerEventQueueCapacityEnvironmentVariableName] = + startInfo.Environment[WorkerLaunchEnvironment.EventQueueCapacity] = _workerOptions.EventQueueCapacity.ToString(System.Globalization.CultureInfo.InvariantCulture); - startInfo.Environment[WorkerAlarmPollIntervalEnvironmentVariableName] = + startInfo.Environment[WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds] = _alarmsOptions.PollIntervalMilliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture); - startInfo.Environment[WorkerMaxAlarmsPerFetchEnvironmentVariableName] = + startInfo.Environment[WorkerLaunchEnvironment.MaxAlarmsPerFetch] = _alarmsOptions.MaxAlarmsPerFetch.ToString(System.Globalization.CultureInfo.InvariantCulture); commandLine = new WorkerProcessCommandLine(executablePath, arguments); diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Contracts/GalaxyContractParityTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Contracts/GalaxyContractParityTests.cs new file mode 100644 index 0000000..8326f32 --- /dev/null +++ b/src/ZB.MOM.WW.MxGateway.Tests/Contracts/GalaxyContractParityTests.cs @@ -0,0 +1,100 @@ +using Google.Protobuf; +using Google.Protobuf.Reflection; +using RetainedGalaxyReflection = ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy.GalaxyRepositoryReflection; +using ServedGalaxyReflection = ZB.MOM.WW.GalaxyRepository.Grpc.GalaxyRepositoryReflection; + +namespace ZB.MOM.WW.MxGateway.Tests.Contracts; + +public sealed class GalaxyContractParityTests +{ + /// Guards the retained client proto against drift from the descriptor served by the gateway. + [Fact] + public void RetainedClientDescriptor_MatchesServedGatewayDescriptor() + { + FileDescriptorProto retained = Normalize(RetainedGalaxyReflection.Descriptor); + FileDescriptorProto served = Normalize(ServedGalaxyReflection.Descriptor); + + AssertEquivalentDescriptors(retained, served); + } + + /// Proves descriptor drift diagnostics name the first differing protobuf message. + [Fact] + public void DescriptorComparison_WhenMessageDiffers_ReportsDifferingMessage() + { + FileDescriptorProto retained = Normalize(RetainedGalaxyReflection.Descriptor); + FileDescriptorProto changed = retained.Clone(); + DescriptorProto changedMessage = changed.MessageType.First(message => message.Field.Count > 0); + changedMessage.Field[0].Name += "_changed"; + + Xunit.Sdk.XunitException exception = Assert.Throws( + () => AssertEquivalentDescriptors(retained, changed)); + + Assert.Contains(changedMessage.Name, exception.Message, StringComparison.Ordinal); + } + + private static FileDescriptorProto Normalize(FileDescriptor descriptor) + { + FileDescriptorProto proto = descriptor.ToProto(); + proto.SourceCodeInfo = null; + proto.Name = string.Empty; + if (proto.Options is not null) + { + proto.Options.CsharpNamespace = string.Empty; + } + + return proto; + } + + private static void AssertEquivalentDescriptors(FileDescriptorProto expected, FileDescriptorProto actual) + { + byte[] expectedBytes = SerializeDeterministically(expected); + byte[] actualBytes = SerializeDeterministically(actual); + if (expectedBytes.AsSpan().SequenceEqual(actualBytes)) + { + return; + } + + throw new Xunit.Sdk.XunitException( + $"Galaxy Repository descriptors differ at {DescribeFirstDifference(expected, actual)}."); + } + + private static string DescribeFirstDifference(FileDescriptorProto expected, FileDescriptorProto actual) + { + int comparableMessageCount = Math.Min(expected.MessageType.Count, actual.MessageType.Count); + for (int index = 0; index < comparableMessageCount; index++) + { + DescriptorProto expectedMessage = expected.MessageType[index]; + DescriptorProto actualMessage = actual.MessageType[index]; + if (!SerializeDeterministically(expectedMessage).AsSpan() + .SequenceEqual(SerializeDeterministically(actualMessage))) + { + string messageName = string.IsNullOrEmpty(expectedMessage.Name) + ? actualMessage.Name + : expectedMessage.Name; + return $"message '{Qualify(expected.Package, messageName)}'"; + } + } + + if (expected.MessageType.Count != actual.MessageType.Count) + { + return "the top-level message list"; + } + + return "the normalized file descriptor"; + } + + private static string Qualify(string package, string symbol) => + string.IsNullOrEmpty(package) ? symbol : package + "." + symbol; + + private static byte[] SerializeDeterministically(IMessage message) + { + using MemoryStream stream = new(); + using CodedOutputStream output = new(stream, leaveOpen: true) + { + Deterministic = true, + }; + message.WriteTo(output); + output.Flush(); + return stream.ToArray(); + } +} diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerProcessLauncherTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerProcessLauncherTests.cs index fb97c47..7fbbb3a 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerProcessLauncherTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerProcessLauncherTests.cs @@ -14,6 +14,16 @@ public sealed class WorkerProcessLauncherTests private const string PipeName = "mxaccess-gateway-123-session-1"; private const string Nonce = "super-secret-nonce"; + private static readonly string[] RequiredLaunchEnvironmentVariables = + [ + WorkerLaunchEnvironment.WorkerNonce, + WorkerLaunchEnvironment.PipeConnectAttemptTimeoutMilliseconds, + WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds, + WorkerLaunchEnvironment.EventQueueCapacity, + WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds, + WorkerLaunchEnvironment.MaxAlarmsPerFetch, + ]; + /// Verifies that a valid worker executable starts with correct bootstrap arguments and nonce environment variable. /// A task that represents the asynchronous operation. [Fact] @@ -38,33 +48,38 @@ public sealed class WorkerProcessLauncherTests Assert.Equal( ["--session-id", SessionId, "--pipe-name", PipeName, "--protocol-version", "1"], processFactory.LastStartInfo.ArgumentList); - Assert.Equal(Nonce, processFactory.LastStartInfo.Environment[WorkerProcessLauncher.WorkerNonceEnvironmentVariableName]); + Assert.Equal(Nonce, processFactory.LastStartInfo.Environment[WorkerLaunchEnvironment.WorkerNonce]); Assert.Equal( "2000", processFactory.LastStartInfo.Environment[ - WorkerProcessLauncher.WorkerPipeConnectAttemptTimeoutEnvironmentVariableName]); + WorkerLaunchEnvironment.PipeConnectAttemptTimeoutMilliseconds]); Assert.Equal( "1500", processFactory.LastStartInfo.Environment[ - WorkerProcessLauncher.WorkerWriteCompletionWaitEnvironmentVariableName]); + WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds]); // The worker sizes its outbound event queue from the launch environment; // the queue has no drop policy, so this capacity is the session's burst // headroom rather than a throttle. Assert.Equal( "10000", processFactory.LastStartInfo.Environment[ - WorkerProcessLauncher.WorkerEventQueueCapacityEnvironmentVariableName]); + WorkerLaunchEnvironment.EventQueueCapacity]); // MxGateway:Alarms defaults reach the worker's alarm poll loop and its // GetXmlCurrentAlarms2 cap (which is also its truncation threshold) // through the launch environment, not the command line. Assert.Equal( "500", processFactory.LastStartInfo.Environment[ - WorkerProcessLauncher.WorkerAlarmPollIntervalEnvironmentVariableName]); + WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds]); Assert.Equal( "1024", processFactory.LastStartInfo.Environment[ - WorkerProcessLauncher.WorkerMaxAlarmsPerFetchEnvironmentVariableName]); + WorkerLaunchEnvironment.MaxAlarmsPerFetch]); + Assert.All( + RequiredLaunchEnvironmentVariables, + variable => Assert.True( + processFactory.LastStartInfo.Environment.ContainsKey(variable), + $"Worker launch environment is missing required variable '{variable}'.")); Assert.DoesNotContain(Nonce, handle.CommandLine.ToString(), StringComparison.Ordinal); Assert.DoesNotContain(Nonce, string.Join(" ", handle.CommandLine.Arguments), StringComparison.Ordinal); Assert.False(pipeReservation.DisposeCalled); @@ -94,7 +109,7 @@ public sealed class WorkerProcessLauncherTests Assert.Equal( "65536", processFactory.LastStartInfo.Environment[ - WorkerProcessLauncher.WorkerEventQueueCapacityEnvironmentVariableName]); + WorkerLaunchEnvironment.EventQueueCapacity]); } /// Verifies that a failed startup probe kills and disposes the worker process. diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Bootstrap/WorkerApplicationTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Bootstrap/WorkerApplicationTests.cs index 5b28223..8742079 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Bootstrap/WorkerApplicationTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Bootstrap/WorkerApplicationTests.cs @@ -137,7 +137,7 @@ public sealed class WorkerApplicationTests private static MemoryWorkerEnvironment CreateEnvironment(string nonce) { MemoryWorkerEnvironment environment = new(); - environment.Set(WorkerOptions.NonceEnvironmentVariableName, nonce); + environment.Set(WorkerLaunchEnvironment.WorkerNonce, nonce); return environment; } diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Bootstrap/WorkerOptionsParserTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Bootstrap/WorkerOptionsParserTests.cs index 6fc0409..987f1fd 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Bootstrap/WorkerOptionsParserTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Bootstrap/WorkerOptionsParserTests.cs @@ -115,7 +115,7 @@ public sealed class WorkerOptionsParserTests private static MemoryWorkerEnvironment CreateEnvironment(string nonce) { MemoryWorkerEnvironment environment = new(); - environment.Set(WorkerOptions.NonceEnvironmentVariableName, nonce); + environment.Set(WorkerLaunchEnvironment.WorkerNonce, nonce); return environment; } } diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventQueueTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventQueueTests.cs index f2a5f1e..8d4681d 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventQueueTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventQueueTests.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Google.Protobuf.WellKnownTypes; +using ZB.MOM.WW.MxGateway.Contracts; using ZB.MOM.WW.MxGateway.Contracts.Proto; using ZB.MOM.WW.MxGateway.Worker.MxAccess; @@ -609,11 +610,11 @@ public sealed class MxAccessEventQueueTests int expected) { string? original = Environment.GetEnvironmentVariable( - MxAccessEventQueue.CapacityEnvironmentVariableName); + WorkerLaunchEnvironment.EventQueueCapacity); try { Environment.SetEnvironmentVariable( - MxAccessEventQueue.CapacityEnvironmentVariableName, + WorkerLaunchEnvironment.EventQueueCapacity, environmentValue); Assert.Equal(expected, MxAccessEventQueue.ResolveCapacity()); @@ -621,7 +622,7 @@ public sealed class MxAccessEventQueueTests finally { Environment.SetEnvironmentVariable( - MxAccessEventQueue.CapacityEnvironmentVariableName, + WorkerLaunchEnvironment.EventQueueCapacity, original); } } @@ -635,11 +636,11 @@ public sealed class MxAccessEventQueueTests public void Constructor_WithResolvedCapacity_UsesEnvironmentValue() { string? original = Environment.GetEnvironmentVariable( - MxAccessEventQueue.CapacityEnvironmentVariableName); + WorkerLaunchEnvironment.EventQueueCapacity); try { Environment.SetEnvironmentVariable( - MxAccessEventQueue.CapacityEnvironmentVariableName, + WorkerLaunchEnvironment.EventQueueCapacity, "2500"); MxAccessEventQueue queue = new(MxAccessEventQueue.ResolveCapacity()); @@ -649,7 +650,7 @@ public sealed class MxAccessEventQueueTests finally { Environment.SetEnvironmentVariable( - MxAccessEventQueue.CapacityEnvironmentVariableName, + WorkerLaunchEnvironment.EventQueueCapacity, original); } } diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs index 062fb0f..c4358a9 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using ZB.MOM.WW.MxGateway.Contracts; using ZB.MOM.WW.MxGateway.Contracts.Proto; using ZB.MOM.WW.MxGateway.Worker.MxAccess; using ZB.MOM.WW.MxGateway.Worker.Sta; @@ -35,11 +36,11 @@ public sealed class MxAccessStaSessionTests public void ResolveWriteCompletionTimeout_ParsesEnvironmentValue(string? rawValue, int? expectedMilliseconds) { string? original = Environment.GetEnvironmentVariable( - MxAccessStaSession.WriteCompletionWaitEnvironmentVariableName); + WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds); try { Environment.SetEnvironmentVariable( - MxAccessStaSession.WriteCompletionWaitEnvironmentVariableName, + WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds, rawValue); TimeSpan resolved = MxAccessStaSession.ResolveWriteCompletionTimeout(); @@ -52,7 +53,7 @@ public sealed class MxAccessStaSessionTests finally { Environment.SetEnvironmentVariable( - MxAccessStaSession.WriteCompletionWaitEnvironmentVariableName, + WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds, original); } } @@ -83,11 +84,11 @@ public sealed class MxAccessStaSessionTests int expectedMilliseconds) { string? original = Environment.GetEnvironmentVariable( - MxAccessStaSession.AlarmPollIntervalEnvironmentVariableName); + WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds); try { Environment.SetEnvironmentVariable( - MxAccessStaSession.AlarmPollIntervalEnvironmentVariableName, + WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds, environmentValue); Assert.Equal( @@ -97,7 +98,7 @@ public sealed class MxAccessStaSessionTests finally { Environment.SetEnvironmentVariable( - MxAccessStaSession.AlarmPollIntervalEnvironmentVariableName, + WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds, original); } } diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs index d1844f9..4ecac2a 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; +using ZB.MOM.WW.MxGateway.Contracts; using ZB.MOM.WW.MxGateway.Worker.MxAccess; namespace ZB.MOM.WW.MxGateway.Worker.Tests.MxAccess; @@ -506,11 +507,11 @@ public sealed class WnWrapAlarmConsumerXmlTests int expected) { string? original = Environment.GetEnvironmentVariable( - WnWrapAlarmConsumer.MaxAlarmsPerFetchEnvironmentVariableName); + WorkerLaunchEnvironment.MaxAlarmsPerFetch); try { Environment.SetEnvironmentVariable( - WnWrapAlarmConsumer.MaxAlarmsPerFetchEnvironmentVariableName, + WorkerLaunchEnvironment.MaxAlarmsPerFetch, environmentValue); Assert.Equal(expected, WnWrapAlarmConsumer.ResolveMaxAlarmsPerFetch()); @@ -518,7 +519,7 @@ public sealed class WnWrapAlarmConsumerXmlTests finally { Environment.SetEnvironmentVariable( - WnWrapAlarmConsumer.MaxAlarmsPerFetchEnvironmentVariableName, + WorkerLaunchEnvironment.MaxAlarmsPerFetch, original); } } diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerOptions.cs b/src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerOptions.cs index e527903..6f7ef3f 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerOptions.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerOptions.cs @@ -1,10 +1,13 @@ namespace ZB.MOM.WW.MxGateway.Worker.Bootstrap; +using ZB.MOM.WW.MxGateway.Contracts; + /// Worker bootstrap options passed via environment variables and named pipes. public sealed class WorkerOptions { /// Environment variable name for the worker nonce. - public const string NonceEnvironmentVariableName = "MXGATEWAY_WORKER_NONCE"; + [Obsolete("Use WorkerLaunchEnvironment.WorkerNonce.")] + public const string NonceEnvironmentVariableName = WorkerLaunchEnvironment.WorkerNonce; /// Initializes worker options from a bootstrap handshake. /// Identifier of the session. diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerOptionsParser.cs b/src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerOptionsParser.cs index 92f72eb..de3a2b0 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerOptionsParser.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerOptionsParser.cs @@ -75,7 +75,7 @@ public sealed class WorkerOptionsParser [$"Unsupported protocol version '{protocolVersionText}'."]); } - string? nonce = _environment.GetEnvironmentVariable(WorkerOptions.NonceEnvironmentVariableName); + string? nonce = _environment.GetEnvironmentVariable(WorkerLaunchEnvironment.WorkerNonce); if (string.IsNullOrWhiteSpace(nonce)) { diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeClient.cs b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeClient.cs index 03885cf..87c5156 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeClient.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeClient.cs @@ -4,6 +4,7 @@ using System.IO; using System.IO.Pipes; using System.Threading; using System.Threading.Tasks; +using ZB.MOM.WW.MxGateway.Contracts; using ZB.MOM.WW.MxGateway.Worker.Bootstrap; using Polly; using Polly.Retry; @@ -22,8 +23,9 @@ public sealed class WorkerPipeClient : IWorkerPipeClient public const int DefaultConnectAttemptTimeoutMilliseconds = 2000; /// Environment variable for overriding the per-attempt connection timeout. + [Obsolete("Use WorkerLaunchEnvironment.PipeConnectAttemptTimeoutMilliseconds.")] public const string ConnectAttemptTimeoutEnvironmentVariableName = - "MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS"; + WorkerLaunchEnvironment.PipeConnectAttemptTimeoutMilliseconds; private readonly int _connectTimeoutMilliseconds; private readonly int _connectAttemptTimeoutMilliseconds; @@ -255,7 +257,8 @@ public sealed class WorkerPipeClient : IWorkerPipeClient private static int ResolveDefaultConnectAttemptTimeoutMilliseconds() { - string? configuredValue = Environment.GetEnvironmentVariable(ConnectAttemptTimeoutEnvironmentVariableName); + string? configuredValue = Environment.GetEnvironmentVariable( + WorkerLaunchEnvironment.PipeConnectAttemptTimeoutMilliseconds); return int.TryParse(configuredValue, out int milliseconds) && milliseconds > 0 ? milliseconds : DefaultConnectAttemptTimeoutMilliseconds; diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessEventQueue.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessEventQueue.cs index eb4c471..53686c5 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessEventQueue.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessEventQueue.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Google.Protobuf; using Google.Protobuf.WellKnownTypes; +using ZB.MOM.WW.MxGateway.Contracts; using ZB.MOM.WW.MxGateway.Contracts.Proto; namespace ZB.MOM.WW.MxGateway.Worker.MxAccess; @@ -45,8 +46,6 @@ public sealed class MxAccessEventQueue /// — a bad environment value must never /// keep the session from starting. /// - internal const string CapacityEnvironmentVariableName = "MXGATEWAY_EVENT_QUEUE_CAPACITY"; - /// /// Floor on the resolved capacity. Mirrors the gateway-side /// MxGateway:Worker:EventQueueCapacity minimum so a value that @@ -140,7 +139,7 @@ public sealed class MxAccessEventQueue /// The capacity to construct the outbound event queue with. internal static int ResolveCapacity() { - string? value = Environment.GetEnvironmentVariable(CapacityEnvironmentVariableName); + string? value = Environment.GetEnvironmentVariable(WorkerLaunchEnvironment.EventQueueCapacity); return int.TryParse( value, NumberStyles.Integer, diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessStaSession.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessStaSession.cs index 6050993..ae1455c 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessStaSession.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessStaSession.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using ZB.MOM.WW.MxGateway.Contracts; using ZB.MOM.WW.MxGateway.Contracts.Proto; using ZB.MOM.WW.MxGateway.Worker.Conversion; using ZB.MOM.WW.MxGateway.Worker.Sta; @@ -16,17 +17,11 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession /// MxGateway:Worker:WriteCompletionWaitMilliseconds. 0 disables the /// write-completion wait (pure fire-and-forget replies). /// - internal const string WriteCompletionWaitEnvironmentVariableName = - "MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS"; - /// /// Environment variable the gateway's WorkerProcessLauncher sets from /// MxGateway:Alarms:PollIntervalMilliseconds. A missing or invalid /// value falls back to . /// - internal const string AlarmPollIntervalEnvironmentVariableName = - "MXGATEWAY_ALARM_POLL_INTERVAL_MS"; - /// /// Floor on the resolved alarm poll cadence. Mirrors the gateway-side /// MxGateway:Alarms:PollIntervalMilliseconds minimum so a value that @@ -211,7 +206,7 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession /// internal static TimeSpan ResolveWriteCompletionTimeout() { - string? value = Environment.GetEnvironmentVariable(WriteCompletionWaitEnvironmentVariableName); + string? value = Environment.GetEnvironmentVariable(WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds); return int.TryParse( value, System.Globalization.NumberStyles.Integer, @@ -233,7 +228,7 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession /// The cadence the alarm poll loop waits between polls. internal static TimeSpan ResolveAlarmPollInterval() { - string? value = Environment.GetEnvironmentVariable(AlarmPollIntervalEnvironmentVariableName); + string? value = Environment.GetEnvironmentVariable(WorkerLaunchEnvironment.AlarmPollIntervalMilliseconds); if (!int.TryParse( value, System.Globalization.NumberStyles.Integer, diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs index 837663d..ccb3b12 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Runtime.InteropServices; using System.Xml; using WNWRAPCONSUMERLib; +using ZB.MOM.WW.MxGateway.Contracts; namespace ZB.MOM.WW.MxGateway.Worker.MxAccess; @@ -75,9 +76,6 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer /// — a bad environment value /// must never keep the alarm consumer from starting. /// - internal const string MaxAlarmsPerFetchEnvironmentVariableName = - "MXGATEWAY_ALARM_MAX_ALARMS_PER_FETCH"; - /// /// Minimum gap between truncated-fetch warnings. A galaxy parked above /// the cap truncates on *every* poll, so an unthrottled warning would @@ -156,7 +154,7 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer /// The cap passed to GetXmlCurrentAlarms2. internal static int ResolveMaxAlarmsPerFetch() { - string? value = Environment.GetEnvironmentVariable(MaxAlarmsPerFetchEnvironmentVariableName); + string? value = Environment.GetEnvironmentVariable(WorkerLaunchEnvironment.MaxAlarmsPerFetch); return int.TryParse( value, NumberStyles.Integer,