Add XML documentation across gateway, worker, and .NET client
This commit is contained in:
@@ -2,8 +2,12 @@ using System;
|
||||
|
||||
namespace MxGateway.Worker.Bootstrap;
|
||||
|
||||
/// <summary>
|
||||
/// Worker environment that reads from system environment variables.
|
||||
/// </summary>
|
||||
public sealed class EnvironmentVariableWorkerEnvironment : IWorkerEnvironment
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string? GetEnvironmentVariable(string name)
|
||||
{
|
||||
return Environment.GetEnvironmentVariable(name);
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
namespace MxGateway.Worker.Bootstrap;
|
||||
|
||||
/// <summary>
|
||||
/// Abstracts access to environment variables for the worker.
|
||||
/// </summary>
|
||||
public interface IWorkerEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an environment variable by name.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the environment variable.</param>
|
||||
/// <returns>The value of the environment variable, or null if not found.</returns>
|
||||
string? GetEnvironmentVariable(string name);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,17 @@ namespace MxGateway.Worker.Bootstrap;
|
||||
|
||||
public interface IWorkerLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Logs an informational event with fields.
|
||||
/// </summary>
|
||||
/// <param name="eventName">Event name.</param>
|
||||
/// <param name="fields">Event fields.</param>
|
||||
void Information(string eventName, IReadOnlyDictionary<string, object?> fields);
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error event with fields.
|
||||
/// </summary>
|
||||
/// <param name="eventName">Event name.</param>
|
||||
/// <param name="fields">Event fields.</param>
|
||||
void Error(string eventName, IReadOnlyDictionary<string, object?> fields);
|
||||
}
|
||||
|
||||
@@ -15,19 +15,42 @@ public sealed class WorkerBootstrapResult
|
||||
Errors = errors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the worker process exit code.
|
||||
/// </summary>
|
||||
public WorkerExitCode ExitCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bootstrap options if bootstrap succeeded.
|
||||
/// </summary>
|
||||
public WorkerOptions? Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of bootstrap errors if any.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Errors { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether bootstrap succeeded.
|
||||
/// </summary>
|
||||
public bool Succeeded => ExitCode == WorkerExitCode.Success;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful bootstrap result with the given options.
|
||||
/// </summary>
|
||||
/// <param name="options">Bootstrap options.</param>
|
||||
/// <returns>Successful bootstrap result.</returns>
|
||||
public static WorkerBootstrapResult Success(WorkerOptions options)
|
||||
{
|
||||
return new WorkerBootstrapResult(WorkerExitCode.Success, options, []);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a failed bootstrap result with the given exit code and errors.
|
||||
/// </summary>
|
||||
/// <param name="exitCode">Worker exit code.</param>
|
||||
/// <param name="errors">Bootstrap errors.</param>
|
||||
/// <returns>Failed bootstrap result.</returns>
|
||||
public static WorkerBootstrapResult Failure(WorkerExitCode exitCode, IEnumerable<string> errors)
|
||||
{
|
||||
return new WorkerBootstrapResult(exitCode, null, errors.ToArray());
|
||||
|
||||
@@ -9,16 +9,24 @@ public sealed class WorkerConsoleLogger : IWorkerLogger
|
||||
{
|
||||
private readonly TextWriter _writer;
|
||||
|
||||
/// <summary>Initializes a new worker console logger.</summary>
|
||||
/// <param name="writer">Text writer destination for log output.</param>
|
||||
public WorkerConsoleLogger(TextWriter writer)
|
||||
{
|
||||
_writer = writer ?? throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
/// <summary>Writes an informational log entry.</summary>
|
||||
/// <param name="eventName">Name of the event being logged.</param>
|
||||
/// <param name="fields">Event fields and values to log.</param>
|
||||
public void Information(string eventName, IReadOnlyDictionary<string, object?> fields)
|
||||
{
|
||||
Write("Information", eventName, fields);
|
||||
}
|
||||
|
||||
/// <summary>Writes an error log entry.</summary>
|
||||
/// <param name="eventName">Name of the event being logged.</param>
|
||||
/// <param name="fields">Event fields and values to log.</param>
|
||||
public void Error(string eventName, IReadOnlyDictionary<string, object?> fields)
|
||||
{
|
||||
Write("Error", eventName, fields);
|
||||
|
||||
@@ -3,8 +3,14 @@ using System.Collections.Generic;
|
||||
|
||||
namespace MxGateway.Worker.Bootstrap;
|
||||
|
||||
/// <summary>
|
||||
/// Redacts sensitive fields from worker log messages.
|
||||
/// </summary>
|
||||
public static class WorkerLogRedactor
|
||||
{
|
||||
/// <summary>
|
||||
/// Replacement text for redacted values.
|
||||
/// </summary>
|
||||
public const string RedactedValue = "[redacted]";
|
||||
|
||||
private static readonly string[] SensitiveFieldNameParts =
|
||||
@@ -18,6 +24,10 @@ public static class WorkerLogRedactor
|
||||
"api_key",
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Redacts sensitive field values from a log field dictionary.
|
||||
/// </summary>
|
||||
/// <param name="fields">Dictionary of field names and values.</param>
|
||||
public static Dictionary<string, object?> RedactFields(IReadOnlyDictionary<string, object?> fields)
|
||||
{
|
||||
Dictionary<string, object?> redactedFields = [];
|
||||
@@ -30,6 +40,11 @@ public static class WorkerLogRedactor
|
||||
return redactedFields;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redacts a single value if its field name contains sensitive keywords.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field to check.</param>
|
||||
/// <param name="value">Value to redact if sensitive.</param>
|
||||
public static object? RedactValue(string fieldName, object? value)
|
||||
{
|
||||
if (value is null)
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
namespace MxGateway.Worker.Bootstrap;
|
||||
|
||||
/// <summary>Worker bootstrap options passed via environment variables and named pipes.</summary>
|
||||
public sealed class WorkerOptions
|
||||
{
|
||||
/// <summary>Environment variable name for the worker nonce.</summary>
|
||||
public const string NonceEnvironmentVariableName = "MXGATEWAY_WORKER_NONCE";
|
||||
|
||||
/// <summary>Initializes worker options from a bootstrap handshake.</summary>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <param name="pipeName">Named pipe name for gateway communication.</param>
|
||||
/// <param name="protocolVersion">Protocol version agreed with the gateway.</param>
|
||||
/// <param name="nonce">Authentication nonce for the handshake.</param>
|
||||
public WorkerOptions(
|
||||
string sessionId,
|
||||
string pipeName,
|
||||
@@ -16,11 +23,15 @@ public sealed class WorkerOptions
|
||||
Nonce = nonce;
|
||||
}
|
||||
|
||||
/// <summary>Unique identifier for the gateway session this worker serves.</summary>
|
||||
public string SessionId { get; }
|
||||
|
||||
/// <summary>Named pipe name for communicating with the gateway.</summary>
|
||||
public string PipeName { get; }
|
||||
|
||||
/// <summary>Worker protocol version negotiated with the gateway.</summary>
|
||||
public uint ProtocolVersion { get; }
|
||||
|
||||
/// <summary>Nonce used to authenticate the handshake with the gateway.</summary>
|
||||
public string Nonce { get; }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ using MxGateway.Contracts;
|
||||
|
||||
namespace MxGateway.Worker.Bootstrap;
|
||||
|
||||
/// <summary>
|
||||
/// Parses worker command-line arguments and environment variables.
|
||||
/// </summary>
|
||||
public sealed class WorkerOptionsParser
|
||||
{
|
||||
private const string SessionIdOptionName = "--session-id";
|
||||
@@ -12,11 +15,20 @@ public sealed class WorkerOptionsParser
|
||||
|
||||
private readonly IWorkerEnvironment _environment;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the parser with a worker environment.
|
||||
/// </summary>
|
||||
/// <param name="environment">Worker environment for reading configuration.</param>
|
||||
public WorkerOptionsParser(IWorkerEnvironment environment)
|
||||
{
|
||||
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses command-line arguments and returns bootstrap configuration or errors.
|
||||
/// </summary>
|
||||
/// <param name="args">Command-line arguments to parse.</param>
|
||||
/// <returns>Bootstrap result containing configuration or error messages.</returns>
|
||||
public WorkerBootstrapResult Parse(string[] args)
|
||||
{
|
||||
if (args is null)
|
||||
|
||||
@@ -2,8 +2,13 @@ using MxGateway.Contracts.Proto;
|
||||
|
||||
namespace MxGateway.Worker.Conversion;
|
||||
|
||||
/// <summary>Result of converting an HResult to a protocol status and diagnostic message.</summary>
|
||||
public sealed class HResultConversion
|
||||
{
|
||||
/// <summary>Initializes the conversion result.</summary>
|
||||
/// <param name="hresult">The original HResult value.</param>
|
||||
/// <param name="protocolStatus">The converted protocol status.</param>
|
||||
/// <param name="diagnosticMessage">Diagnostic message describing the HResult.</param>
|
||||
public HResultConversion(
|
||||
int hresult,
|
||||
ProtocolStatus protocolStatus,
|
||||
@@ -14,9 +19,12 @@ public sealed class HResultConversion
|
||||
DiagnosticMessage = diagnosticMessage;
|
||||
}
|
||||
|
||||
/// <summary>The original HResult value.</summary>
|
||||
public int HResult { get; }
|
||||
|
||||
/// <summary>The converted protocol status.</summary>
|
||||
public ProtocolStatus ProtocolStatus { get; }
|
||||
|
||||
/// <summary>Diagnostic message describing the HResult.</summary>
|
||||
public string DiagnosticMessage { get; }
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace MxGateway.Worker.Conversion;
|
||||
|
||||
public sealed class HResultConverter
|
||||
{
|
||||
/// <summary>Converts an exception to an HResult conversion with protocol status and diagnostic message.</summary>
|
||||
/// <param name="exception">Exception to convert.</param>
|
||||
/// <returns>Conversion result with HResult, protocol status, and diagnostics.</returns>
|
||||
public HResultConversion Convert(Exception exception)
|
||||
{
|
||||
if (exception is null)
|
||||
@@ -23,6 +26,10 @@ public sealed class HResultConverter
|
||||
CreateSafeDiagnosticMessage(exception));
|
||||
}
|
||||
|
||||
/// <summary>Creates a protocol status from an HResult code and optional exception.</summary>
|
||||
/// <param name="hresult">HResult error code.</param>
|
||||
/// <param name="exception">Exception providing additional context.</param>
|
||||
/// <returns>Protocol status with mapped code and message.</returns>
|
||||
public ProtocolStatus CreateProtocolStatus(
|
||||
int hresult,
|
||||
Exception? exception = null)
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace MxGateway.Worker.Conversion;
|
||||
|
||||
public sealed class MxStatusConversionException : Exception
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="MxStatusConversionException"/> class.</summary>
|
||||
/// <param name="message">The exception message.</param>
|
||||
public MxStatusConversionException(string message)
|
||||
: base(message)
|
||||
{
|
||||
|
||||
@@ -47,6 +47,9 @@ internal static class MxStatusDetailText
|
||||
[8017] = "Object must be offscan to modify attributes that have an MxSecurityConfigure security classification",
|
||||
};
|
||||
|
||||
/// <summary>Looks up the text description for an MXAccess status detail code.</summary>
|
||||
/// <param name="detail">Status detail code.</param>
|
||||
/// <returns>Description text if found; otherwise empty string.</returns>
|
||||
public static string Lookup(int detail)
|
||||
{
|
||||
return KnownDetails.TryGetValue(detail, out string text) ? text : string.Empty;
|
||||
|
||||
@@ -6,8 +6,11 @@ using MxGateway.Contracts.Proto;
|
||||
|
||||
namespace MxGateway.Worker.Conversion;
|
||||
|
||||
/// <summary>Converts MXAccess MXSTATUS_PROXY COM objects to protobuf MxStatusProxy messages.</summary>
|
||||
public sealed class MxStatusProxyConverter
|
||||
{
|
||||
/// <summary>Converts a single status object to a protobuf message, reflecting all fields and diagnostics.</summary>
|
||||
/// <param name="status">COM status object to convert.</param>
|
||||
public MxStatusProxy Convert(object status)
|
||||
{
|
||||
if (status is null)
|
||||
@@ -33,6 +36,8 @@ public sealed class MxStatusProxyConverter
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Converts an array of status objects, handling nulls gracefully.</summary>
|
||||
/// <param name="statuses">Array of COM status objects; null returns empty list.</param>
|
||||
public IReadOnlyList<MxStatusProxy> ConvertMany(Array? statuses)
|
||||
{
|
||||
if (statuses is null)
|
||||
@@ -60,6 +65,8 @@ public sealed class MxStatusProxyConverter
|
||||
return converted;
|
||||
}
|
||||
|
||||
/// <summary>Preserves completion-only status bytes as a diagnostic hex string since they cannot be unpacked.</summary>
|
||||
/// <param name="statusBytes">Status bytes to encode as hex string.</param>
|
||||
public string PreserveCompletionOnlyStatusBytes(byte[] statusBytes)
|
||||
{
|
||||
if (statusBytes is null)
|
||||
|
||||
@@ -8,11 +8,22 @@ namespace MxGateway.Worker.Conversion;
|
||||
|
||||
public sealed class VariantConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an object value to an MxValue without a specified data type.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to convert.</param>
|
||||
/// <returns>Converted MxValue.</returns>
|
||||
public MxValue Convert(object? value)
|
||||
{
|
||||
return Convert(value, MxDataType.Unspecified);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an object value to an MxValue with an expected data type.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to convert.</param>
|
||||
/// <param name="expectedDataType">Expected MXAccess data type.</param>
|
||||
/// <returns>Converted MxValue.</returns>
|
||||
public MxValue Convert(
|
||||
object? value,
|
||||
MxDataType expectedDataType)
|
||||
@@ -35,6 +46,12 @@ public sealed class VariantConverter
|
||||
return ConvertScalar(value, expectedDataType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a .NET array to an MxArray.
|
||||
/// </summary>
|
||||
/// <param name="array">Array to convert.</param>
|
||||
/// <param name="expectedElementDataType">Expected data type for array elements.</param>
|
||||
/// <returns>Converted MxArray.</returns>
|
||||
public MxArray ConvertArray(
|
||||
Array array,
|
||||
MxDataType expectedElementDataType = MxDataType.Unspecified)
|
||||
|
||||
@@ -4,8 +4,12 @@ using MxGateway.Worker.Bootstrap;
|
||||
|
||||
namespace MxGateway.Worker.Ipc;
|
||||
|
||||
/// <summary>Manages the worker's named pipe connection to the gateway.</summary>
|
||||
public interface IWorkerPipeClient
|
||||
{
|
||||
/// <summary>Connects to the gateway and runs the worker until the session ends or is cancelled.</summary>
|
||||
/// <param name="options">Configuration options.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
Task RunAsync(
|
||||
WorkerOptions options,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -5,7 +5,9 @@ namespace MxGateway.Worker.Ipc;
|
||||
|
||||
public static class WorkerContractInfo
|
||||
{
|
||||
/// <summary>The worker protocol version supported by this contract.</summary>
|
||||
public static uint SupportedProtocolVersion => GatewayContractInfo.WorkerProtocolVersion;
|
||||
|
||||
/// <summary>The fully qualified name of the WorkerEnvelope message descriptor.</summary>
|
||||
public static string WorkerEnvelopeDescriptorName => WorkerEnvelope.Descriptor.FullName;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,12 @@ using MxGateway.Contracts.Proto;
|
||||
|
||||
namespace MxGateway.Worker.Ipc;
|
||||
|
||||
/// <summary>Validates worker envelope frames against protocol options.</summary>
|
||||
internal static class WorkerEnvelopeValidator
|
||||
{
|
||||
/// <summary>Validates a worker envelope for protocol compliance.</summary>
|
||||
/// <param name="envelope">The envelope to validate.</param>
|
||||
/// <param name="options">The frame protocol configuration.</param>
|
||||
public static void Validate(
|
||||
WorkerEnvelope envelope,
|
||||
WorkerFrameProtocolOptions options)
|
||||
|
||||
@@ -2,8 +2,16 @@ using System;
|
||||
|
||||
namespace MxGateway.Worker.Ipc;
|
||||
|
||||
/// <summary>
|
||||
/// Exception raised when the named-pipe frame protocol encounters an error.
|
||||
/// </summary>
|
||||
public sealed class WorkerFrameProtocolException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes with an error code and message.
|
||||
/// </summary>
|
||||
/// <param name="errorCode">Protocol error classification.</param>
|
||||
/// <param name="message">Exception message.</param>
|
||||
public WorkerFrameProtocolException(
|
||||
WorkerFrameProtocolErrorCode errorCode,
|
||||
string message)
|
||||
@@ -12,6 +20,12 @@ public sealed class WorkerFrameProtocolException : Exception
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes with an error code, message, and inner exception.
|
||||
/// </summary>
|
||||
/// <param name="errorCode">Protocol error classification.</param>
|
||||
/// <param name="message">Exception message.</param>
|
||||
/// <param name="innerException">Underlying cause.</param>
|
||||
public WorkerFrameProtocolException(
|
||||
WorkerFrameProtocolErrorCode errorCode,
|
||||
string message,
|
||||
@@ -21,5 +35,8 @@ public sealed class WorkerFrameProtocolException : Exception
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The protocol error code classifying the failure.
|
||||
/// </summary>
|
||||
public WorkerFrameProtocolErrorCode ErrorCode { get; }
|
||||
}
|
||||
|
||||
@@ -4,10 +4,14 @@ using MxGateway.Worker.Bootstrap;
|
||||
|
||||
namespace MxGateway.Worker.Ipc;
|
||||
|
||||
/// <summary>Configuration options for the worker frame protocol.</summary>
|
||||
public sealed class WorkerFrameProtocolOptions
|
||||
{
|
||||
/// <summary>Default maximum message size in bytes (16 MB).</summary>
|
||||
public const int DefaultMaxMessageBytes = 16 * 1024 * 1024;
|
||||
|
||||
/// <summary>Initializes a new instance of the WorkerFrameProtocolOptions class from WorkerOptions.</summary>
|
||||
/// <param name="options">Worker initialization options.</param>
|
||||
public WorkerFrameProtocolOptions(WorkerOptions options)
|
||||
: this(
|
||||
options?.SessionId ?? throw new ArgumentNullException(nameof(options)),
|
||||
@@ -17,6 +21,10 @@ public sealed class WorkerFrameProtocolOptions
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the WorkerFrameProtocolOptions class with default max message bytes.</summary>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <param name="protocolVersion">Protocol version.</param>
|
||||
/// <param name="nonce">Nonce for startup validation.</param>
|
||||
public WorkerFrameProtocolOptions(
|
||||
string sessionId,
|
||||
uint protocolVersion,
|
||||
@@ -29,6 +37,11 @@ public sealed class WorkerFrameProtocolOptions
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the WorkerFrameProtocolOptions class with all parameters.</summary>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <param name="protocolVersion">Protocol version.</param>
|
||||
/// <param name="nonce">Nonce for startup validation.</param>
|
||||
/// <param name="maxMessageBytes">Maximum message size in bytes.</param>
|
||||
public WorkerFrameProtocolOptions(
|
||||
string sessionId,
|
||||
uint protocolVersion,
|
||||
@@ -76,11 +89,15 @@ public sealed class WorkerFrameProtocolOptions
|
||||
MaxMessageBytes = maxMessageBytes;
|
||||
}
|
||||
|
||||
/// <summary>Gets the session ID for the worker protocol.</summary>
|
||||
public string SessionId { get; }
|
||||
|
||||
/// <summary>Gets the protocol version.</summary>
|
||||
public uint ProtocolVersion { get; }
|
||||
|
||||
/// <summary>Gets the nonce for startup validation.</summary>
|
||||
public string Nonce { get; }
|
||||
|
||||
/// <summary>Gets the maximum message size in bytes.</summary>
|
||||
public int MaxMessageBytes { get; }
|
||||
}
|
||||
|
||||
@@ -7,11 +7,15 @@ using MxGateway.Contracts.Proto;
|
||||
|
||||
namespace MxGateway.Worker.Ipc;
|
||||
|
||||
/// <summary>Reads length-prefixed WorkerEnvelope protobuf frames from a stream.</summary>
|
||||
public sealed class WorkerFrameReader
|
||||
{
|
||||
private readonly WorkerFrameProtocolOptions _options;
|
||||
private readonly Stream _stream;
|
||||
|
||||
/// <summary>Initializes the reader with a stream and protocol options.</summary>
|
||||
/// <param name="stream">Stream to read frames from.</param>
|
||||
/// <param name="options">Protocol options for frame validation.</param>
|
||||
public WorkerFrameReader(
|
||||
Stream stream,
|
||||
WorkerFrameProtocolOptions options)
|
||||
@@ -20,6 +24,8 @@ public sealed class WorkerFrameReader
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <summary>Reads and validates a single length-prefixed frame from the stream.</summary>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
public async Task<WorkerEnvelope> ReadAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
byte[] lengthPrefix = new byte[sizeof(uint)];
|
||||
|
||||
@@ -7,12 +7,16 @@ using MxGateway.Contracts.Proto;
|
||||
|
||||
namespace MxGateway.Worker.Ipc;
|
||||
|
||||
/// <summary>Writes worker frames to a stream with length-prefixed protobuf serialization.</summary>
|
||||
public sealed class WorkerFrameWriter
|
||||
{
|
||||
private readonly WorkerFrameProtocolOptions _options;
|
||||
private readonly SemaphoreSlim _writeLock = new(1, 1);
|
||||
private readonly Stream _stream;
|
||||
|
||||
/// <summary>Initializes a new instance of the WorkerFrameWriter class.</summary>
|
||||
/// <param name="stream">Stream to write frames to.</param>
|
||||
/// <param name="options">Protocol options for frame encoding.</param>
|
||||
public WorkerFrameWriter(
|
||||
Stream stream,
|
||||
WorkerFrameProtocolOptions options)
|
||||
@@ -21,6 +25,9 @@ public sealed class WorkerFrameWriter
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <summary>Writes a worker envelope frame to the stream with length prefix.</summary>
|
||||
/// <param name="envelope">Worker envelope to write.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
public async Task WriteAsync(
|
||||
WorkerEnvelope envelope,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -10,10 +10,18 @@ using Polly.Retry;
|
||||
|
||||
namespace MxGateway.Worker.Ipc;
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the gateway via a named pipe and runs the worker frame protocol session.
|
||||
/// </summary>
|
||||
public sealed class WorkerPipeClient : IWorkerPipeClient
|
||||
{
|
||||
/// <summary>Default overall connection timeout in milliseconds.</summary>
|
||||
public const int DefaultConnectTimeoutMilliseconds = 30000;
|
||||
|
||||
/// <summary>Default per-attempt connection timeout in milliseconds.</summary>
|
||||
public const int DefaultConnectAttemptTimeoutMilliseconds = 2000;
|
||||
|
||||
/// <summary>Environment variable for overriding the per-attempt connection timeout.</summary>
|
||||
public const string ConnectAttemptTimeoutEnvironmentVariableName =
|
||||
"MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS";
|
||||
|
||||
@@ -22,21 +30,37 @@ public sealed class WorkerPipeClient : IWorkerPipeClient
|
||||
private readonly Func<Stream, WorkerFrameProtocolOptions, IWorkerLogger?, WorkerPipeSession> _sessionFactory;
|
||||
private readonly IWorkerLogger? _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a worker pipe client with default timeouts.
|
||||
/// </summary>
|
||||
public WorkerPipeClient()
|
||||
: this(null, DefaultConnectTimeoutMilliseconds)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a worker pipe client with a logger and default timeouts.
|
||||
/// </summary>
|
||||
/// <param name="logger">Optional logger for diagnostic output.</param>
|
||||
public WorkerPipeClient(IWorkerLogger? logger)
|
||||
: this(logger, DefaultConnectTimeoutMilliseconds)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a worker pipe client with a custom overall connect timeout.
|
||||
/// </summary>
|
||||
/// <param name="connectTimeoutMilliseconds">Overall connection timeout in milliseconds.</param>
|
||||
public WorkerPipeClient(int connectTimeoutMilliseconds)
|
||||
: this(null, connectTimeoutMilliseconds)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a worker pipe client with custom timeouts and a session factory.
|
||||
/// </summary>
|
||||
/// <param name="connectTimeoutMilliseconds">Overall connection timeout in milliseconds.</param>
|
||||
/// <param name="sessionFactory">Factory creating the worker pipe session.</param>
|
||||
public WorkerPipeClient(
|
||||
int connectTimeoutMilliseconds,
|
||||
Func<Stream, WorkerFrameProtocolOptions, WorkerPipeSession> sessionFactory)
|
||||
@@ -48,6 +72,11 @@ public sealed class WorkerPipeClient : IWorkerPipeClient
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a worker pipe client with a logger and custom overall timeout.
|
||||
/// </summary>
|
||||
/// <param name="logger">Optional logger for diagnostic output.</param>
|
||||
/// <param name="connectTimeoutMilliseconds">Overall connection timeout in milliseconds.</param>
|
||||
public WorkerPipeClient(
|
||||
IWorkerLogger? logger,
|
||||
int connectTimeoutMilliseconds)
|
||||
@@ -59,6 +88,12 @@ public sealed class WorkerPipeClient : IWorkerPipeClient
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a worker pipe client with logger, timeouts, and a session factory.
|
||||
/// </summary>
|
||||
/// <param name="logger">Optional logger for diagnostic output.</param>
|
||||
/// <param name="connectTimeoutMilliseconds">Overall connection timeout in milliseconds.</param>
|
||||
/// <param name="sessionFactory">Factory creating the worker pipe session.</param>
|
||||
public WorkerPipeClient(
|
||||
IWorkerLogger? logger,
|
||||
int connectTimeoutMilliseconds,
|
||||
@@ -71,6 +106,13 @@ public sealed class WorkerPipeClient : IWorkerPipeClient
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a worker pipe client with full configuration.
|
||||
/// </summary>
|
||||
/// <param name="logger">Optional logger for diagnostic output.</param>
|
||||
/// <param name="connectTimeoutMilliseconds">Overall connection timeout in milliseconds.</param>
|
||||
/// <param name="connectAttemptTimeoutMilliseconds">Per-attempt connection timeout in milliseconds.</param>
|
||||
/// <param name="sessionFactory">Factory creating the worker pipe session.</param>
|
||||
public WorkerPipeClient(
|
||||
IWorkerLogger? logger,
|
||||
int connectTimeoutMilliseconds,
|
||||
@@ -97,6 +139,11 @@ public sealed class WorkerPipeClient : IWorkerPipeClient
|
||||
_connectAttemptTimeoutMilliseconds = connectAttemptTimeoutMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the worker by connecting to the gateway and executing the frame protocol.
|
||||
/// </summary>
|
||||
/// <param name="options">Worker configuration options.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
public async Task RunAsync(
|
||||
WorkerOptions options,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -34,6 +34,10 @@ public sealed class WorkerPipeSession
|
||||
private bool _watchdogFaultSent;
|
||||
private bool _shutdownTimedOut;
|
||||
|
||||
/// <summary>Initializes a new worker pipe session over the provided stream.</summary>
|
||||
/// <param name="stream">Network stream for reading and writing frames.</param>
|
||||
/// <param name="options">Frame protocol configuration.</param>
|
||||
/// <param name="logger">Optional logger for diagnostic output.</param>
|
||||
public WorkerPipeSession(
|
||||
Stream stream,
|
||||
WorkerFrameProtocolOptions options,
|
||||
@@ -49,6 +53,11 @@ public sealed class WorkerPipeSession
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new worker pipe session with custom frame reader and writer.</summary>
|
||||
/// <param name="reader">Frame reader for incoming messages.</param>
|
||||
/// <param name="writer">Frame writer for outgoing messages.</param>
|
||||
/// <param name="options">Frame protocol configuration.</param>
|
||||
/// <param name="processIdProvider">Function returning the current worker process ID.</param>
|
||||
public WorkerPipeSession(
|
||||
WorkerFrameReader reader,
|
||||
WorkerFrameWriter writer,
|
||||
@@ -65,6 +74,14 @@ public sealed class WorkerPipeSession
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new worker pipe session with full configuration and dependencies.</summary>
|
||||
/// <param name="reader">Frame reader for incoming messages.</param>
|
||||
/// <param name="writer">Frame writer for outgoing messages.</param>
|
||||
/// <param name="options">Frame protocol configuration.</param>
|
||||
/// <param name="processIdProvider">Function returning the current worker process ID.</param>
|
||||
/// <param name="sessionOptions">Session-specific options.</param>
|
||||
/// <param name="runtimeSessionFactory">Factory creating the MXAccess runtime session.</param>
|
||||
/// <param name="logger">Optional logger for diagnostic output.</param>
|
||||
public WorkerPipeSession(
|
||||
WorkerFrameReader reader,
|
||||
WorkerFrameWriter writer,
|
||||
@@ -84,6 +101,8 @@ public sealed class WorkerPipeSession
|
||||
_sessionOptions.Validate();
|
||||
}
|
||||
|
||||
/// <summary>Runs the worker session, completing the handshake and processing messages until cancellation.</summary>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
public async Task RunAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_runtimeSession = _runtimeSessionFactory();
|
||||
@@ -106,11 +125,16 @@ public sealed class WorkerPipeSession
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Completes the gateway startup handshake using default MXAccess initialization.</summary>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
public Task CompleteStartupHandshakeAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return CompleteStartupHandshakeAsync(InitializeMxAccessAsync, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Completes the gateway startup handshake with custom MXAccess initialization that returns void.</summary>
|
||||
/// <param name="initializeMxAccessAsync">Async function to initialize MXAccess.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
public async Task CompleteStartupHandshakeAsync(
|
||||
Func<CancellationToken, Task> initializeMxAccessAsync,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -129,6 +153,9 @@ public sealed class WorkerPipeSession
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>Completes the gateway startup handshake with custom MXAccess initialization that returns WorkerReady.</summary>
|
||||
/// <param name="initializeMxAccessAsync">Async function to initialize MXAccess and return ready state.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
public async Task CompleteStartupHandshakeAsync(
|
||||
Func<CancellationToken, Task<WorkerReady>> initializeMxAccessAsync,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -2,21 +2,28 @@ using System;
|
||||
|
||||
namespace MxGateway.Worker.Ipc;
|
||||
|
||||
/// <summary>Configuration options for worker pipe sessions including heartbeat parameters.</summary>
|
||||
public sealed class WorkerPipeSessionOptions
|
||||
{
|
||||
/// <summary>Default heartbeat interval (5 seconds).</summary>
|
||||
public static readonly TimeSpan DefaultHeartbeatInterval = TimeSpan.FromSeconds(5);
|
||||
/// <summary>Default heartbeat grace period (15 seconds).</summary>
|
||||
public static readonly TimeSpan DefaultHeartbeatGrace = TimeSpan.FromSeconds(15);
|
||||
|
||||
/// <summary>Initializes a new instance of the WorkerPipeSessionOptions class with default values.</summary>
|
||||
public WorkerPipeSessionOptions()
|
||||
{
|
||||
HeartbeatInterval = DefaultHeartbeatInterval;
|
||||
HeartbeatGrace = DefaultHeartbeatGrace;
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the heartbeat interval.</summary>
|
||||
public TimeSpan HeartbeatInterval { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the heartbeat grace period.</summary>
|
||||
public TimeSpan HeartbeatGrace { get; set; }
|
||||
|
||||
/// <summary>Validates the session options.</summary>
|
||||
public void Validate()
|
||||
{
|
||||
if (HeartbeatInterval <= TimeSpan.Zero)
|
||||
|
||||
@@ -2,5 +2,7 @@ namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
public interface IMxAccessComObjectFactory
|
||||
{
|
||||
/// <summary>Creates an MXAccess COM object instance.</summary>
|
||||
/// <returns>The created COM object.</returns>
|
||||
object Create();
|
||||
}
|
||||
|
||||
@@ -2,9 +2,13 @@ namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
public interface IMxAccessEventSink
|
||||
{
|
||||
/// <summary>Attaches the event sink to an MXAccess COM object.</summary>
|
||||
/// <param name="mxAccessComObject">The MXAccess COM object.</param>
|
||||
/// <param name="sessionId">The session ID.</param>
|
||||
void Attach(
|
||||
object mxAccessComObject,
|
||||
string sessionId);
|
||||
|
||||
/// <summary>Detaches the event sink from the COM object.</summary>
|
||||
void Detach();
|
||||
}
|
||||
|
||||
@@ -2,31 +2,57 @@ namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
public interface IMxAccessServer
|
||||
{
|
||||
/// <summary>Registers a client and returns a server handle.</summary>
|
||||
/// <param name="clientName">Name of the client requesting registration.</param>
|
||||
/// <returns>Server handle for subsequent operations.</returns>
|
||||
int Register(string clientName);
|
||||
|
||||
/// <summary>Unregisters a server handle.</summary>
|
||||
/// <param name="serverHandle">Server handle to unregister.</param>
|
||||
void Unregister(int serverHandle);
|
||||
|
||||
/// <summary>Adds an item to a server and returns an item handle.</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemDefinition">Item definition string.</param>
|
||||
/// <returns>Item handle for the added item.</returns>
|
||||
int AddItem(
|
||||
int serverHandle,
|
||||
string itemDefinition);
|
||||
|
||||
/// <summary>Adds an item with context to a server and returns an item handle.</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemDefinition">Item definition string.</param>
|
||||
/// <param name="itemContext">Item context string.</param>
|
||||
/// <returns>Item handle for the added item.</returns>
|
||||
int AddItem2(
|
||||
int serverHandle,
|
||||
string itemDefinition,
|
||||
string itemContext);
|
||||
|
||||
/// <summary>Removes an item from a server.</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemHandle">Item handle to remove.</param>
|
||||
void RemoveItem(
|
||||
int serverHandle,
|
||||
int itemHandle);
|
||||
|
||||
/// <summary>Subscribes to change notifications for an item.</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemHandle">Item handle to subscribe to.</param>
|
||||
void Advise(
|
||||
int serverHandle,
|
||||
int itemHandle);
|
||||
|
||||
/// <summary>Unsubscribes from change notifications for an item.</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemHandle">Item handle to unsubscribe from.</param>
|
||||
void UnAdvise(
|
||||
int serverHandle,
|
||||
int itemHandle);
|
||||
|
||||
/// <summary>Subscribes to supervisory change notifications for an item.</summary>
|
||||
/// <param name="serverHandle">Server handle identifying the registration.</param>
|
||||
/// <param name="itemHandle">Item handle to subscribe to.</param>
|
||||
void AdviseSupervisory(
|
||||
int serverHandle,
|
||||
int itemHandle);
|
||||
|
||||
@@ -7,25 +7,65 @@ using MxGateway.Worker.Sta;
|
||||
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Manages the runtime session between the worker and the MXAccess COM instance on an STA thread.
|
||||
/// </summary>
|
||||
public interface IWorkerRuntimeSession : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts the session, creates the MXAccess COM object, and returns ready metadata.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <param name="workerProcessId">ID of the worker process.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
/// <returns>Asynchronous task returning worker readiness metadata.</returns>
|
||||
Task<WorkerReady> StartAsync(
|
||||
string sessionId,
|
||||
int workerProcessId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches an STA command to the MXAccess runtime and returns the reply.
|
||||
/// </summary>
|
||||
/// <param name="command">STA command to execute on the STA thread.</param>
|
||||
/// <returns>Asynchronous task returning the command reply.</returns>
|
||||
Task<MxCommandReply> DispatchAsync(StaCommand command);
|
||||
|
||||
/// <summary>
|
||||
/// Captures a heartbeat snapshot of the runtime state.
|
||||
/// </summary>
|
||||
WorkerRuntimeHeartbeatSnapshot CaptureHeartbeat();
|
||||
|
||||
/// <summary>
|
||||
/// Drains up to the specified number of pending events from the queue.
|
||||
/// </summary>
|
||||
/// <param name="maxEvents">Maximum number of events to drain.</param>
|
||||
/// <returns>List of drained events.</returns>
|
||||
IReadOnlyList<WorkerEvent> DrainEvents(uint maxEvents);
|
||||
|
||||
/// <summary>
|
||||
/// Drains a pending fault from the queue, if any.
|
||||
/// </summary>
|
||||
WorkerFault? DrainFault();
|
||||
|
||||
/// <summary>
|
||||
/// Cancels a pending command by correlation ID.
|
||||
/// </summary>
|
||||
/// <param name="correlationId">Correlation ID of the command to cancel.</param>
|
||||
/// <returns>True if the command was found and cancelled; otherwise, false.</returns>
|
||||
bool CancelCommand(string correlationId);
|
||||
|
||||
/// <summary>
|
||||
/// Requests a graceful shutdown of the session.
|
||||
/// </summary>
|
||||
void RequestShutdown();
|
||||
|
||||
/// <summary>
|
||||
/// Shuts down the session gracefully within the specified timeout.
|
||||
/// </summary>
|
||||
/// <param name="timeout">Maximum time to allow for graceful shutdown.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
/// <returns>Asynchronous task returning the shutdown result.</returns>
|
||||
Task<MxAccessShutdownResult> ShutdownGracefullyAsync(
|
||||
TimeSpan timeout,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -4,6 +4,7 @@ using Proto = MxGateway.Contracts.Proto;
|
||||
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>Sink for MXAccess COM events that converts them to protobuf format.</summary>
|
||||
public sealed class MxAccessBaseEventSink : IMxAccessEventSink
|
||||
{
|
||||
private readonly MxAccessEventMapper eventMapper;
|
||||
@@ -11,16 +12,22 @@ public sealed class MxAccessBaseEventSink : IMxAccessEventSink
|
||||
private LMXProxyServerClass? server;
|
||||
private string sessionId = string.Empty;
|
||||
|
||||
/// <summary>Initializes a new instance of the MxAccessBaseEventSink class with a default queue.</summary>
|
||||
public MxAccessBaseEventSink()
|
||||
: this(new MxAccessEventQueue())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the MxAccessBaseEventSink class with a provided queue.</summary>
|
||||
/// <param name="eventQueue">Queue for buffering converted MXAccess events.</param>
|
||||
public MxAccessBaseEventSink(MxAccessEventQueue eventQueue)
|
||||
: this(eventQueue, new MxAccessEventMapper())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the MxAccessBaseEventSink class with provided queue and mapper.</summary>
|
||||
/// <param name="eventQueue">Queue for buffering converted MXAccess events.</param>
|
||||
/// <param name="eventMapper">Converter for MXAccess events to protobuf format.</param>
|
||||
public MxAccessBaseEventSink(
|
||||
MxAccessEventQueue eventQueue,
|
||||
MxAccessEventMapper eventMapper)
|
||||
@@ -29,6 +36,7 @@ public sealed class MxAccessBaseEventSink : IMxAccessEventSink
|
||||
this.eventMapper = eventMapper ?? throw new ArgumentNullException(nameof(eventMapper));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Attach(
|
||||
object mxAccessComObject,
|
||||
string sessionId)
|
||||
@@ -41,6 +49,7 @@ public sealed class MxAccessBaseEventSink : IMxAccessEventSink
|
||||
server.OnBufferedDataChange += OnBufferedDataChange;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Detach()
|
||||
{
|
||||
if (server is null)
|
||||
|
||||
@@ -2,8 +2,10 @@ using ArchestrA.MxAccess;
|
||||
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>Factory for creating MXAccess COM objects on the STA thread.</summary>
|
||||
public sealed class MxAccessComObjectFactory : IMxAccessComObjectFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Create()
|
||||
{
|
||||
return new LMXProxyServerClass();
|
||||
|
||||
@@ -5,15 +5,23 @@ using ArchestrA.MxAccess;
|
||||
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Adapter exposing MXAccess COM object methods through the IMxAccessServer interface.
|
||||
/// </summary>
|
||||
public sealed class MxAccessComServer : IMxAccessServer
|
||||
{
|
||||
private readonly object mxAccessComObject;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the adapter with the MXAccess COM object.
|
||||
/// </summary>
|
||||
/// <param name="mxAccessComObject">MXAccess COM object instance.</param>
|
||||
public MxAccessComServer(object mxAccessComObject)
|
||||
{
|
||||
this.mxAccessComObject = mxAccessComObject ?? throw new ArgumentNullException(nameof(mxAccessComObject));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Register(string clientName)
|
||||
{
|
||||
if (mxAccessComObject is ILMXProxyServer mxAccessServer)
|
||||
@@ -24,6 +32,7 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
return (int)Invoke(nameof(Register), clientName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Unregister(int serverHandle)
|
||||
{
|
||||
if (mxAccessComObject is ILMXProxyServer mxAccessServer)
|
||||
@@ -35,6 +44,7 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
Invoke(nameof(Unregister), serverHandle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AddItem(
|
||||
int serverHandle,
|
||||
string itemDefinition)
|
||||
@@ -47,6 +57,7 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
return (int)Invoke(nameof(AddItem), serverHandle, itemDefinition);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AddItem2(
|
||||
int serverHandle,
|
||||
string itemDefinition,
|
||||
@@ -60,6 +71,7 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
return (int)Invoke(nameof(AddItem2), serverHandle, itemDefinition, itemContext);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveItem(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -73,6 +85,7 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
Invoke(nameof(RemoveItem), serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Advise(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -86,6 +99,7 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
Invoke(nameof(Advise), serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UnAdvise(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -99,6 +113,7 @@ public sealed class MxAccessComServer : IMxAccessServer
|
||||
Invoke(nameof(UnAdvise), serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AdviseSupervisory(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
|
||||
@@ -6,16 +6,28 @@ using MxGateway.Worker.Sta;
|
||||
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Executes MXAccess commands on an STA session.
|
||||
/// </summary>
|
||||
public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
{
|
||||
private readonly MxAccessSession session;
|
||||
private readonly VariantConverter variantConverter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a command executor with an MXAccess session.
|
||||
/// </summary>
|
||||
/// <param name="session">MXAccess session on the STA thread.</param>
|
||||
public MxAccessCommandExecutor(MxAccessSession session)
|
||||
: this(session, new VariantConverter())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a command executor with an MXAccess session and a variant converter.
|
||||
/// </summary>
|
||||
/// <param name="session">MXAccess session on the STA thread.</param>
|
||||
/// <param name="variantConverter">Converter for MXAccess variant values to MxValue protobuf messages.</param>
|
||||
public MxAccessCommandExecutor(
|
||||
MxAccessSession session,
|
||||
VariantConverter variantConverter)
|
||||
@@ -24,6 +36,11 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
this.variantConverter = variantConverter ?? throw new ArgumentNullException(nameof(variantConverter));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an MXAccess command and returns the reply.
|
||||
/// </summary>
|
||||
/// <param name="command">STA command to execute.</param>
|
||||
/// <returns>Command reply with result or error details.</returns>
|
||||
public MxCommandReply Execute(StaCommand command)
|
||||
{
|
||||
if (command is null)
|
||||
|
||||
@@ -3,8 +3,11 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>Thrown when the worker fails to instantiate the MXAccess COM object.</summary>
|
||||
public sealed class MxAccessCreationException : Exception
|
||||
{
|
||||
/// <summary>Initializes a new instance with diagnostic info from the inner exception.</summary>
|
||||
/// <param name="innerException">The exception that caused the creation failure.</param>
|
||||
public MxAccessCreationException(Exception innerException)
|
||||
: base(
|
||||
$"Failed to create MXAccess COM object {MxAccessInteropInfo.ComClassName} ({MxAccessInteropInfo.ProgId}).",
|
||||
@@ -16,14 +19,21 @@ public sealed class MxAccessCreationException : Exception
|
||||
HResult = innerException.HResult;
|
||||
}
|
||||
|
||||
/// <summary>The ProgID that was attempted during COM instantiation.</summary>
|
||||
public string AttemptedProgId { get; }
|
||||
|
||||
/// <summary>The CLSID that was attempted during COM instantiation.</summary>
|
||||
public string AttemptedClsid { get; }
|
||||
|
||||
/// <summary>The COM class name that was attempted during instantiation.</summary>
|
||||
public string AttemptedComClassName { get; }
|
||||
|
||||
/// <summary>The captured HResult from the instantiation failure, or null if zero.</summary>
|
||||
public int? CapturedHResult => HResult == 0 ? null : HResult;
|
||||
|
||||
/// <summary>Wraps an exception in MxAccessCreationException if it is not already.</summary>
|
||||
/// <param name="exception">The exception to wrap.</param>
|
||||
/// <returns>An MxAccessCreationException wrapping the input exception.</returns>
|
||||
public static MxAccessCreationException From(Exception exception)
|
||||
{
|
||||
return exception is MxAccessCreationException creationException
|
||||
@@ -31,6 +41,9 @@ public sealed class MxAccessCreationException : Exception
|
||||
: new MxAccessCreationException(exception);
|
||||
}
|
||||
|
||||
/// <summary>Extracts the HResult from an exception, handling MXAccess and COM exceptions specially.</summary>
|
||||
/// <param name="exception">The exception to extract the HResult from.</param>
|
||||
/// <returns>The HResult value, or null if zero.</returns>
|
||||
public static int? ExtractHResult(Exception exception)
|
||||
{
|
||||
if (exception is MxAccessCreationException creationException)
|
||||
|
||||
@@ -4,16 +4,21 @@ using MxGateway.Worker.Conversion;
|
||||
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>Maps MXAccess COM events to protobuf MxEvent messages.</summary>
|
||||
public sealed class MxAccessEventMapper
|
||||
{
|
||||
private readonly VariantConverter variantConverter;
|
||||
private readonly MxStatusProxyConverter statusProxyConverter;
|
||||
|
||||
/// <summary>Initializes a new instance of the MxAccessEventMapper class with default converters.</summary>
|
||||
public MxAccessEventMapper()
|
||||
: this(new VariantConverter(), new MxStatusProxyConverter())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the MxAccessEventMapper class with provided converters.</summary>
|
||||
/// <param name="variantConverter">Converter for MXAccess variant values to MxValue protobuf messages.</param>
|
||||
/// <param name="statusProxyConverter">Converter for MXAccess status arrays to MxStatusProxy protobuf messages.</param>
|
||||
public MxAccessEventMapper(
|
||||
VariantConverter variantConverter,
|
||||
MxStatusProxyConverter statusProxyConverter)
|
||||
@@ -22,6 +27,14 @@ public sealed class MxAccessEventMapper
|
||||
this.statusProxyConverter = statusProxyConverter ?? throw new ArgumentNullException(nameof(statusProxyConverter));
|
||||
}
|
||||
|
||||
/// <summary>Creates an OnDataChange event from MXAccess COM event arguments.</summary>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="value">Item value received from MXAccess.</param>
|
||||
/// <param name="quality">Item quality code from MXAccess.</param>
|
||||
/// <param name="timestamp">Item timestamp from MXAccess.</param>
|
||||
/// <param name="statuses">Array of MxStatusProxy values from MXAccess.</param>
|
||||
public MxEvent CreateOnDataChange(
|
||||
string sessionId,
|
||||
int serverHandle,
|
||||
@@ -45,6 +58,11 @@ public sealed class MxAccessEventMapper
|
||||
return mxEvent;
|
||||
}
|
||||
|
||||
/// <summary>Creates an OnWriteComplete event from MXAccess COM event arguments.</summary>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="statuses">Array of MxStatusProxy values from MXAccess.</param>
|
||||
public MxEvent CreateOnWriteComplete(
|
||||
string sessionId,
|
||||
int serverHandle,
|
||||
@@ -62,6 +80,11 @@ public sealed class MxAccessEventMapper
|
||||
return mxEvent;
|
||||
}
|
||||
|
||||
/// <summary>Creates an OperationComplete event from MXAccess COM event arguments.</summary>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="statuses">Array of MxStatusProxy values from MXAccess.</param>
|
||||
public MxEvent CreateOperationComplete(
|
||||
string sessionId,
|
||||
int serverHandle,
|
||||
@@ -79,6 +102,15 @@ public sealed class MxAccessEventMapper
|
||||
return mxEvent;
|
||||
}
|
||||
|
||||
/// <summary>Creates an OnBufferedDataChange event from MXAccess COM event arguments.</summary>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="rawDataType">Raw MXAccess data type code for the buffered value.</param>
|
||||
/// <param name="value">Item value received from MXAccess.</param>
|
||||
/// <param name="quality">Array of quality values from MXAccess.</param>
|
||||
/// <param name="timestamp">Array of timestamp values from MXAccess.</param>
|
||||
/// <param name="statuses">Array of MxStatusProxy values from MXAccess.</param>
|
||||
public MxEvent CreateOnBufferedDataChange(
|
||||
string sessionId,
|
||||
int serverHandle,
|
||||
@@ -108,6 +140,9 @@ public sealed class MxAccessEventMapper
|
||||
return mxEvent;
|
||||
}
|
||||
|
||||
/// <summary>Maps a raw MXAccess data type code to the MxDataType enum.</summary>
|
||||
/// <param name="rawDataType">Raw MXAccess data type value to map.</param>
|
||||
/// <returns>The corresponding MxDataType enum value.</returns>
|
||||
public static MxDataType MapMxDataType(int rawDataType)
|
||||
{
|
||||
return rawDataType switch
|
||||
|
||||
@@ -5,8 +5,14 @@ using MxGateway.Contracts.Proto;
|
||||
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe queue for MxAccess events with capacity overflow and fault tracking.
|
||||
/// </summary>
|
||||
public sealed class MxAccessEventQueue
|
||||
{
|
||||
/// <summary>
|
||||
/// Default queue capacity (10,000 events).
|
||||
/// </summary>
|
||||
public const int DefaultCapacity = 10000;
|
||||
|
||||
private readonly int capacity;
|
||||
@@ -16,11 +22,18 @@ public sealed class MxAccessEventQueue
|
||||
private WorkerFault? fault;
|
||||
private bool faultDrained;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the queue with the default capacity.
|
||||
/// </summary>
|
||||
public MxAccessEventQueue()
|
||||
: this(DefaultCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the queue with the specified capacity.
|
||||
/// </summary>
|
||||
/// <param name="capacity">Maximum number of events the queue can hold.</param>
|
||||
public MxAccessEventQueue(int capacity)
|
||||
{
|
||||
if (capacity <= 0)
|
||||
@@ -34,8 +47,14 @@ public sealed class MxAccessEventQueue
|
||||
events = new Queue<WorkerEvent>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The queue's maximum capacity.
|
||||
/// </summary>
|
||||
public int Capacity => capacity;
|
||||
|
||||
/// <summary>
|
||||
/// The current number of events in the queue.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
@@ -47,6 +66,9 @@ public sealed class MxAccessEventQueue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The highest event sequence number assigned.
|
||||
/// </summary>
|
||||
public ulong LastEventSequence
|
||||
{
|
||||
get
|
||||
@@ -58,6 +80,9 @@ public sealed class MxAccessEventQueue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the queue is in a faulted state.
|
||||
/// </summary>
|
||||
public bool IsFaulted
|
||||
{
|
||||
get
|
||||
@@ -69,6 +94,9 @@ public sealed class MxAccessEventQueue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current fault if the queue is faulted, or null.
|
||||
/// </summary>
|
||||
public WorkerFault? Fault
|
||||
{
|
||||
get
|
||||
@@ -80,6 +108,10 @@ public sealed class MxAccessEventQueue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues an MxAccess event, assigning a sequence number and timestamp.
|
||||
/// </summary>
|
||||
/// <param name="mxEvent">MXAccess event to enqueue.</param>
|
||||
public void Enqueue(MxEvent mxEvent)
|
||||
{
|
||||
if (mxEvent is null)
|
||||
@@ -112,6 +144,10 @@ public sealed class MxAccessEventQueue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to dequeue the next event without removing it if empty.
|
||||
/// </summary>
|
||||
/// <param name="workerEvent">The dequeued event if successful; null if queue is empty.</param>
|
||||
public bool TryDequeue(out WorkerEvent? workerEvent)
|
||||
{
|
||||
lock (syncRoot)
|
||||
@@ -127,6 +163,10 @@ public sealed class MxAccessEventQueue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drains up to maxEvents from the queue; if maxEvents is 0, drains all events.
|
||||
/// </summary>
|
||||
/// <param name="maxEvents">Maximum number of events to drain; 0 means drain all.</param>
|
||||
public IReadOnlyList<WorkerEvent> Drain(uint maxEvents)
|
||||
{
|
||||
lock (syncRoot)
|
||||
@@ -149,6 +189,10 @@ public sealed class MxAccessEventQueue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records a fault if one has not already been recorded.
|
||||
/// </summary>
|
||||
/// <param name="workerFault">Worker fault to record.</param>
|
||||
public void RecordFault(WorkerFault workerFault)
|
||||
{
|
||||
if (workerFault is null)
|
||||
@@ -162,6 +206,9 @@ public sealed class MxAccessEventQueue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns and clears the fault so it is not reported twice.
|
||||
/// </summary>
|
||||
public WorkerFault? DrainFault()
|
||||
{
|
||||
lock (syncRoot)
|
||||
|
||||
@@ -4,11 +4,18 @@ namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
public sealed class MxAccessEventQueueOverflowException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MxAccessEventQueueOverflowException"/>.
|
||||
/// </summary>
|
||||
/// <param name="capacity">Queue capacity.</param>
|
||||
public MxAccessEventQueueOverflowException(int capacity)
|
||||
: base($"MXAccess outbound event queue reached its configured capacity of {capacity}.")
|
||||
{
|
||||
Capacity = capacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the queue capacity.
|
||||
/// </summary>
|
||||
public int Capacity { get; }
|
||||
}
|
||||
|
||||
@@ -10,17 +10,20 @@ public sealed class MxAccessHandleRegistry
|
||||
private readonly Dictionary<long, RegisteredItemHandle> itemHandles = new();
|
||||
private readonly Dictionary<AdviceHandleKey, RegisteredAdviceHandle> adviceHandles = new();
|
||||
|
||||
/// <summary>Gets a read-only list of registered server handles ordered by handle value.</summary>
|
||||
public IReadOnlyList<RegisteredServerHandle> ServerHandles => serverHandles
|
||||
.Values
|
||||
.OrderBy(handle => handle.ServerHandle)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>Gets a read-only list of registered item handles ordered by server handle then item handle.</summary>
|
||||
public IReadOnlyList<RegisteredItemHandle> ItemHandles => itemHandles
|
||||
.Values
|
||||
.OrderBy(handle => handle.ServerHandle)
|
||||
.ThenBy(handle => handle.ItemHandle)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>Gets a read-only list of registered advice handles ordered by server handle, item handle, and advice kind.</summary>
|
||||
public IReadOnlyList<RegisteredAdviceHandle> AdviceHandles => adviceHandles
|
||||
.Values
|
||||
.OrderBy(handle => handle.ServerHandle)
|
||||
@@ -28,6 +31,9 @@ public sealed class MxAccessHandleRegistry
|
||||
.ThenBy(handle => handle.AdviceKind)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>Registers a server handle with the registry.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="clientName">Display name of the client that owns the server handle.</param>
|
||||
public void RegisterServerHandle(
|
||||
int serverHandle,
|
||||
string clientName)
|
||||
@@ -35,6 +41,8 @@ public sealed class MxAccessHandleRegistry
|
||||
serverHandles[serverHandle] = new RegisteredServerHandle(serverHandle, clientName);
|
||||
}
|
||||
|
||||
/// <summary>Unregisters a server handle and all associated item and advice handles from the registry.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
public void UnregisterServerHandle(int serverHandle)
|
||||
{
|
||||
serverHandles.Remove(serverHandle);
|
||||
@@ -56,11 +64,19 @@ public sealed class MxAccessHandleRegistry
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Checks if the registry contains the specified server handle.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
public bool ContainsServerHandle(int serverHandle)
|
||||
{
|
||||
return serverHandles.ContainsKey(serverHandle);
|
||||
}
|
||||
|
||||
/// <summary>Registers an item handle with the registry.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemDefinition">Item definition name from MXAccess.</param>
|
||||
/// <param name="itemContext">Item context from MXAccess, or empty string if none.</param>
|
||||
/// <param name="hasItemContext">True if the item has a context; false otherwise.</param>
|
||||
public void RegisterItemHandle(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
@@ -76,6 +92,9 @@ public sealed class MxAccessHandleRegistry
|
||||
hasItemContext);
|
||||
}
|
||||
|
||||
/// <summary>Removes an item handle and all associated advice handles from the registry.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
public void RemoveItemHandle(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -84,6 +103,9 @@ public sealed class MxAccessHandleRegistry
|
||||
RemoveAdviceHandles(serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
/// <summary>Checks if the registry contains the specified item handle.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
public bool ContainsItemHandle(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -91,6 +113,10 @@ public sealed class MxAccessHandleRegistry
|
||||
return itemHandles.ContainsKey(CreateItemKey(serverHandle, itemHandle));
|
||||
}
|
||||
|
||||
/// <summary>Registers an advice handle with the registry.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="adviceKind">Type of advice to register.</param>
|
||||
public void RegisterAdviceHandle(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
@@ -103,6 +129,9 @@ public sealed class MxAccessHandleRegistry
|
||||
adviceKind);
|
||||
}
|
||||
|
||||
/// <summary>Removes all advice handles for the specified server and item handles from the registry.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
public void RemoveAdviceHandles(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -116,6 +145,10 @@ public sealed class MxAccessHandleRegistry
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Checks if the registry contains the specified advice handle.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="adviceKind">Type of advice to check.</param>
|
||||
public bool ContainsAdviceHandle(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
@@ -137,6 +170,10 @@ public sealed class MxAccessHandleRegistry
|
||||
private readonly int itemHandle;
|
||||
private readonly MxAccessAdviceKind adviceKind;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="AdviceHandleKey"/> struct.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="adviceKind">Type of advice.</param>
|
||||
public AdviceHandleKey(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
@@ -147,6 +184,7 @@ public sealed class MxAccessHandleRegistry
|
||||
this.adviceKind = adviceKind;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(AdviceHandleKey other)
|
||||
{
|
||||
return serverHandle == other.serverHandle
|
||||
@@ -154,11 +192,13 @@ public sealed class MxAccessHandleRegistry
|
||||
&& adviceKind == other.adviceKind;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is AdviceHandleKey other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
|
||||
@@ -3,25 +3,52 @@ using ArchestrA.MxAccess;
|
||||
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Constants and metadata for MXAccess COM interop.
|
||||
/// </summary>
|
||||
public static class MxAccessInteropInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Versioned ProgID for the MXAccess COM server.
|
||||
/// </summary>
|
||||
public const string ProgId = "LMXProxy.LMXProxyServer.1";
|
||||
|
||||
/// <summary>
|
||||
/// Version-independent ProgID for the MXAccess COM server.
|
||||
/// </summary>
|
||||
public const string VersionIndependentProgId = "LMXProxy.LMXProxyServer";
|
||||
|
||||
/// <summary>
|
||||
/// Class ID (CLSID) of the MXAccess COM server.
|
||||
/// </summary>
|
||||
public const string Clsid = "{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}";
|
||||
|
||||
/// <summary>
|
||||
/// Path to the ArchestrA.MxAccess.dll interop assembly.
|
||||
/// </summary>
|
||||
public const string InteropAssemblyPath =
|
||||
@"C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll";
|
||||
|
||||
/// <summary>
|
||||
/// Path to the installed MXAccess COM server DLL.
|
||||
/// </summary>
|
||||
public const string RegisteredServerPath =
|
||||
@"C:\Program Files (x86)\ArchestrA\Framework\Bin\LmxProxy.dll";
|
||||
|
||||
/// <summary>
|
||||
/// Full qualified name of the COM class.
|
||||
/// </summary>
|
||||
public const string ComClassName = "ArchestrA.MxAccess.LMXProxyServerClass";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the MXAccess interop assembly.
|
||||
/// </summary>
|
||||
public static string InteropAssemblyName =>
|
||||
typeof(LMXProxyServerClass).Assembly.GetName().Name ?? string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the MXAccess interop assembly.
|
||||
/// </summary>
|
||||
public static Version InteropAssemblyVersion =>
|
||||
typeof(LMXProxyServerClass).Assembly.GetName().Version ?? new Version(0, 0);
|
||||
}
|
||||
|
||||
@@ -28,10 +28,14 @@ public sealed class MxAccessSession : IDisposable
|
||||
CreationThreadId = creationThreadId;
|
||||
}
|
||||
|
||||
/// <summary>The thread ID where this session was created.</summary>
|
||||
public int CreationThreadId { get; }
|
||||
|
||||
/// <summary>The registry for tracking opened handles.</summary>
|
||||
public MxAccessHandleRegistry HandleRegistry => handleRegistry;
|
||||
|
||||
/// <summary>Creates a WorkerReady message with session metadata.</summary>
|
||||
/// <param name="workerProcessId">Process ID of the worker.</param>
|
||||
public WorkerReady CreateWorkerReady(int workerProcessId)
|
||||
{
|
||||
return new WorkerReady
|
||||
@@ -43,6 +47,10 @@ public sealed class MxAccessSession : IDisposable
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Creates and initializes an MXAccess COM session.</summary>
|
||||
/// <param name="factory">Factory to create the MXAccess COM object.</param>
|
||||
/// <param name="eventSink">Event sink to attach to the COM object.</param>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
public static MxAccessSession Create(
|
||||
IMxAccessComObjectFactory factory,
|
||||
IMxAccessEventSink eventSink,
|
||||
@@ -97,6 +105,8 @@ public sealed class MxAccessSession : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Registers a client with MXAccess and returns the server handle.</summary>
|
||||
/// <param name="clientName">Name of the client to register.</param>
|
||||
public int Register(string clientName)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -107,6 +117,8 @@ public sealed class MxAccessSession : IDisposable
|
||||
return serverHandle;
|
||||
}
|
||||
|
||||
/// <summary>Unregisters a client from MXAccess.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
public void Unregister(int serverHandle)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -115,6 +127,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
handleRegistry.UnregisterServerHandle(serverHandle);
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to an MXAccess server and returns the item handle.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemDefinition">Definition or address of the item to add.</param>
|
||||
public int AddItem(
|
||||
int serverHandle,
|
||||
string itemDefinition)
|
||||
@@ -132,6 +147,10 @@ public sealed class MxAccessSession : IDisposable
|
||||
return itemHandle;
|
||||
}
|
||||
|
||||
/// <summary>Adds an item with context to an MXAccess server and returns the item handle.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemDefinition">Definition or address of the item to add.</param>
|
||||
/// <param name="itemContext">Context string for the item.</param>
|
||||
public int AddItem2(
|
||||
int serverHandle,
|
||||
string itemDefinition,
|
||||
@@ -150,6 +169,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
return itemHandle;
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from an MXAccess server.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
public void RemoveItem(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -160,6 +182,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
handleRegistry.RemoveItemHandle(serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
/// <summary>Advises on item changes with plain subscription.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
public void Advise(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -173,6 +198,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
MxAccessAdviceKind.Plain);
|
||||
}
|
||||
|
||||
/// <summary>Removes plain advice subscription from an item.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
public void UnAdvise(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -183,6 +211,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
handleRegistry.RemoveAdviceHandles(serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
/// <summary>Advises on item changes with supervisory subscription.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
public void AdviseSupervisory(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -196,6 +227,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
MxAccessAdviceKind.Supervisory);
|
||||
}
|
||||
|
||||
/// <summary>Adds multiple items in bulk, returning success/failure results.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="tagAddresses">Enumerable of item definitions to add.</param>
|
||||
public IReadOnlyList<SubscribeResult> AddItemBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<string> tagAddresses)
|
||||
@@ -229,6 +263,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>Advises on multiple items in bulk, returning success/failure results.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandles">Enumerable of item handles to advise on.</param>
|
||||
public IReadOnlyList<SubscribeResult> AdviseItemBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<int> itemHandles)
|
||||
@@ -256,6 +293,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>Removes multiple items in bulk, returning success/failure results.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandles">Enumerable of item handles to remove.</param>
|
||||
public IReadOnlyList<SubscribeResult> RemoveItemBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<int> itemHandles)
|
||||
@@ -283,6 +323,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>Removes advice subscriptions from multiple items in bulk, returning success/failure results.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandles">Enumerable of item handles to unadvise.</param>
|
||||
public IReadOnlyList<SubscribeResult> UnAdviseItemBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<int> itemHandles)
|
||||
@@ -310,6 +353,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>Adds multiple items and subscribes to them in bulk, returning success/failure results.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="tagAddresses">Enumerable of item definitions to add and subscribe to.</param>
|
||||
public IReadOnlyList<SubscribeResult> SubscribeBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<string> tagAddresses)
|
||||
@@ -351,6 +397,9 @@ public sealed class MxAccessSession : IDisposable
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>Unsubscribes from multiple items in bulk, returning success/failure results.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandles">Enumerable of item handles to unsubscribe from.</param>
|
||||
public IReadOnlyList<SubscribeResult> UnsubscribeBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<int> itemHandles)
|
||||
@@ -392,6 +441,7 @@ public sealed class MxAccessSession : IDisposable
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>Gracefully shuts down the session, cleaning up all handles.</summary>
|
||||
public MxAccessShutdownResult ShutdownGracefully()
|
||||
{
|
||||
if (disposed)
|
||||
@@ -409,6 +459,7 @@ public sealed class MxAccessSession : IDisposable
|
||||
return new MxAccessShutdownResult(failures);
|
||||
}
|
||||
|
||||
/// <summary>Releases the MXAccess COM object and resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
|
||||
@@ -2,8 +2,18 @@ using System;
|
||||
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Captures details about an MXAccess operation that failed during shutdown.
|
||||
/// </summary>
|
||||
public sealed class MxAccessShutdownFailure
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the shutdown failure record.
|
||||
/// </summary>
|
||||
/// <param name="operation">Name of the operation that failed.</param>
|
||||
/// <param name="serverHandle">Server handle if applicable.</param>
|
||||
/// <param name="itemHandle">Item handle if applicable.</param>
|
||||
/// <param name="exception">Exception that was raised.</param>
|
||||
public MxAccessShutdownFailure(
|
||||
string operation,
|
||||
int? serverHandle,
|
||||
@@ -22,13 +32,28 @@ public sealed class MxAccessShutdownFailure
|
||||
HResult = exception?.HResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The operation that failed (e.g., Unregister, RemoveItem).
|
||||
/// </summary>
|
||||
public string Operation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Server handle if applicable; otherwise null.
|
||||
/// </summary>
|
||||
public int? ServerHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Item handle if applicable; otherwise null.
|
||||
/// </summary>
|
||||
public int? ItemHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Full type name of the exception, or empty string if no exception.
|
||||
/// </summary>
|
||||
public string ExceptionType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// HResult code if the exception has one; otherwise null.
|
||||
/// </summary>
|
||||
public int? HResult { get; }
|
||||
}
|
||||
|
||||
@@ -5,12 +5,16 @@ namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
public sealed class MxAccessShutdownResult
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="MxAccessShutdownResult"/> class.</summary>
|
||||
/// <param name="failures">List of failures encountered during graceful shutdown.</param>
|
||||
public MxAccessShutdownResult(IReadOnlyList<MxAccessShutdownFailure> failures)
|
||||
{
|
||||
Failures = failures ?? throw new ArgumentNullException(nameof(failures));
|
||||
}
|
||||
|
||||
/// <summary>Gets the list of shutdown failures.</summary>
|
||||
public IReadOnlyList<MxAccessShutdownFailure> Failures { get; }
|
||||
|
||||
/// <summary>Gets a value indicating whether the shutdown succeeded with no failures.</summary>
|
||||
public bool Succeeded => Failures.Count == 0;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
private MxAccessSession? session;
|
||||
private bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MxAccessStaSession"/> with default dependencies.
|
||||
/// </summary>
|
||||
public MxAccessStaSession()
|
||||
: this(
|
||||
new StaRuntime(),
|
||||
@@ -26,6 +29,12 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MxAccessStaSession"/> with custom STA runtime and factory.
|
||||
/// </summary>
|
||||
/// <param name="staRuntime">STA thread runtime.</param>
|
||||
/// <param name="factory">MXAccess COM object factory.</param>
|
||||
/// <param name="eventSink">Event sink for MXAccess events.</param>
|
||||
public MxAccessStaSession(
|
||||
StaRuntime staRuntime,
|
||||
IMxAccessComObjectFactory factory,
|
||||
@@ -34,6 +43,12 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MxAccessStaSession"/> with custom event queue.
|
||||
/// </summary>
|
||||
/// <param name="staRuntime">STA thread runtime.</param>
|
||||
/// <param name="factory">MXAccess COM object factory.</param>
|
||||
/// <param name="eventQueue">Event queue for buffering MXAccess events.</param>
|
||||
public MxAccessStaSession(
|
||||
StaRuntime staRuntime,
|
||||
IMxAccessComObjectFactory factory,
|
||||
@@ -42,6 +57,13 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MxAccessStaSession"/> with all dependencies.
|
||||
/// </summary>
|
||||
/// <param name="staRuntime">STA thread runtime.</param>
|
||||
/// <param name="factory">MXAccess COM object factory.</param>
|
||||
/// <param name="eventSink">Event sink for MXAccess events.</param>
|
||||
/// <param name="eventQueue">Event queue for buffering MXAccess events.</param>
|
||||
public MxAccessStaSession(
|
||||
StaRuntime staRuntime,
|
||||
IMxAccessComObjectFactory factory,
|
||||
@@ -54,8 +76,17 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
this.eventQueue = eventQueue ?? throw new ArgumentNullException(nameof(eventQueue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event queue for this session.
|
||||
/// </summary>
|
||||
public MxAccessEventQueue EventQueue => eventQueue;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the MXAccess COM session asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="workerProcessId">Worker process identifier.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Worker ready message.</returns>
|
||||
public Task<WorkerReady> StartAsync(
|
||||
int workerProcessId,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -63,6 +94,13 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
return StartAsync(string.Empty, workerProcessId, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the MXAccess COM session with a session ID asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session identifier.</param>
|
||||
/// <param name="workerProcessId">Worker process identifier.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Worker ready message.</returns>
|
||||
public Task<WorkerReady> StartAsync(
|
||||
string sessionId,
|
||||
int workerProcessId,
|
||||
@@ -88,6 +126,11 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches a command to the STA thread for execution asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to dispatch.</param>
|
||||
/// <returns>Command reply.</returns>
|
||||
public Task<MxCommandReply> DispatchAsync(StaCommand command)
|
||||
{
|
||||
if (commandDispatcher is null)
|
||||
@@ -98,6 +141,10 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
return commandDispatcher.DispatchAsync(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures a heartbeat snapshot of the session's runtime state.
|
||||
/// </summary>
|
||||
/// <returns>Heartbeat snapshot.</returns>
|
||||
public WorkerRuntimeHeartbeatSnapshot CaptureHeartbeat()
|
||||
{
|
||||
uint pendingCommandCount = 0;
|
||||
@@ -117,26 +164,48 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
currentCommandCorrelationId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests graceful shutdown of the command dispatcher.
|
||||
/// </summary>
|
||||
public void RequestShutdown()
|
||||
{
|
||||
commandDispatcher?.RequestShutdown();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drains up to the specified number of events from the queue.
|
||||
/// </summary>
|
||||
/// <param name="maxEvents">Maximum events to drain.</param>
|
||||
/// <returns>Drained events.</returns>
|
||||
public IReadOnlyList<WorkerEvent> DrainEvents(uint maxEvents)
|
||||
{
|
||||
return eventQueue.Drain(maxEvents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drains a fault from the queue if present.
|
||||
/// </summary>
|
||||
/// <returns>Drained fault or null.</returns>
|
||||
public WorkerFault? DrainFault()
|
||||
{
|
||||
return eventQueue.DrainFault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels a queued command by correlation ID.
|
||||
/// </summary>
|
||||
/// <param name="correlationId">Correlation ID of the command to cancel.</param>
|
||||
/// <returns>True if cancelled; otherwise false.</returns>
|
||||
public bool CancelCommand(string correlationId)
|
||||
{
|
||||
return commandDispatcher?.CancelQueuedCommand(correlationId) ?? false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registered server handles asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Registered server handles.</returns>
|
||||
public Task<IReadOnlyList<RegisteredServerHandle>> GetRegisteredServerHandlesAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -150,6 +219,11 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registered item handles asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Registered item handles.</returns>
|
||||
public Task<IReadOnlyList<RegisteredItemHandle>> GetRegisteredItemHandlesAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -163,6 +237,11 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registered advice handles asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Registered advice handles.</returns>
|
||||
public Task<IReadOnlyList<RegisteredAdviceHandle>> GetRegisteredAdviceHandlesAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -176,6 +255,12 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs graceful shutdown of the MXAccess session within a timeout.
|
||||
/// </summary>
|
||||
/// <param name="timeout">Maximum time allowed for shutdown.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Shutdown result with any cleanup failures.</returns>
|
||||
public async Task<MxAccessShutdownResult> ShutdownGracefullyAsync(
|
||||
TimeSpan timeout,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -238,6 +323,7 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Releases resources and shuts down the session.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
|
||||
@@ -2,6 +2,10 @@ namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
public sealed class RegisteredAdviceHandle
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="RegisteredAdviceHandle"/> class.</summary>
|
||||
/// <param name="serverHandle">Handle returned by the worker.</param>
|
||||
/// <param name="itemHandle">Handle returned by the worker.</param>
|
||||
/// <param name="adviceKind">Type of advice.</param>
|
||||
public RegisteredAdviceHandle(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
@@ -12,9 +16,12 @@ public sealed class RegisteredAdviceHandle
|
||||
AdviceKind = adviceKind;
|
||||
}
|
||||
|
||||
/// <summary>Gets the server handle.</summary>
|
||||
public int ServerHandle { get; }
|
||||
|
||||
/// <summary>Gets the item handle.</summary>
|
||||
public int ItemHandle { get; }
|
||||
|
||||
/// <summary>Gets the advice kind.</summary>
|
||||
public MxAccessAdviceKind AdviceKind { get; }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Metadata for an item handle registered in an MXAccess session.
|
||||
/// </summary>
|
||||
public sealed class RegisteredItemHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a registered item handle with complete metadata.
|
||||
/// </summary>
|
||||
/// <param name="serverHandle">Handle returned by Register.</param>
|
||||
/// <param name="itemHandle">Handle returned by AddItem.</param>
|
||||
/// <param name="itemDefinition">Item definition (tag address).</param>
|
||||
/// <param name="itemContext">Item context string.</param>
|
||||
/// <param name="hasItemContext">Whether this item has an associated context.</param>
|
||||
public RegisteredItemHandle(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
@@ -16,13 +27,28 @@ public sealed class RegisteredItemHandle
|
||||
HasItemContext = hasItemContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server handle that owns this item.
|
||||
/// </summary>
|
||||
public int ServerHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item handle within the server.
|
||||
/// </summary>
|
||||
public int ItemHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item definition (tag address).
|
||||
/// </summary>
|
||||
public string ItemDefinition { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item context.
|
||||
/// </summary>
|
||||
public string ItemContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this item has an associated context.
|
||||
/// </summary>
|
||||
public bool HasItemContext { get; }
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
public sealed class RegisteredServerHandle
|
||||
{
|
||||
/// <summary>Initializes a new registered server handle.</summary>
|
||||
/// <param name="serverHandle">MXAccess server handle.</param>
|
||||
/// <param name="clientName">Client name associated with the handle.</param>
|
||||
public RegisteredServerHandle(
|
||||
int serverHandle,
|
||||
string clientName)
|
||||
@@ -10,7 +13,9 @@ public sealed class RegisteredServerHandle
|
||||
ClientName = clientName;
|
||||
}
|
||||
|
||||
/// <summary>The MXAccess server handle.</summary>
|
||||
public int ServerHandle { get; }
|
||||
|
||||
/// <summary>The client name associated with this server handle.</summary>
|
||||
public string ClientName { get; }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,12 @@ namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
public sealed class WorkerRuntimeHeartbeatSnapshot
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="WorkerRuntimeHeartbeatSnapshot"/> class.</summary>
|
||||
/// <param name="lastStaActivityUtc">Timestamp of the last STA thread activity in UTC.</param>
|
||||
/// <param name="pendingCommandCount">Number of commands awaiting processing.</param>
|
||||
/// <param name="outboundEventQueueDepth">Current depth of the worker event queue.</param>
|
||||
/// <param name="lastEventSequence">Sequence number of the most recent event.</param>
|
||||
/// <param name="currentCommandCorrelationId">Correlation ID of the in-flight command.</param>
|
||||
public WorkerRuntimeHeartbeatSnapshot(
|
||||
DateTimeOffset lastStaActivityUtc,
|
||||
uint pendingCommandCount,
|
||||
@@ -18,13 +24,18 @@ public sealed class WorkerRuntimeHeartbeatSnapshot
|
||||
CurrentCommandCorrelationId = currentCommandCorrelationId ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>Gets the last STA activity timestamp in UTC.</summary>
|
||||
public DateTimeOffset LastStaActivityUtc { get; }
|
||||
|
||||
/// <summary>Gets the pending command count.</summary>
|
||||
public uint PendingCommandCount { get; }
|
||||
|
||||
/// <summary>Gets the current depth of the worker event queue.</summary>
|
||||
public uint OutboundEventQueueDepth { get; }
|
||||
|
||||
/// <summary>Gets the sequence number of the most recent event.</summary>
|
||||
public ulong LastEventSequence { get; }
|
||||
|
||||
/// <summary>Gets the correlation ID of the in-flight command.</summary>
|
||||
public string CurrentCommandCorrelationId { get; }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
namespace MxGateway.Worker.Sta;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and uninitializes the COM apartment for the STA thread.
|
||||
/// </summary>
|
||||
public interface IStaComApartmentInitializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the COM apartment on the STA thread.
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Uninitializes the COM apartment on the STA thread.
|
||||
/// </summary>
|
||||
void Uninitialize();
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@ namespace MxGateway.Worker.Sta;
|
||||
|
||||
public interface IStaCommandExecutor
|
||||
{
|
||||
/// <summary>Executes a command on the STA thread.</summary>
|
||||
/// <param name="command">The command to execute.</param>
|
||||
/// <returns>The command reply.</returns>
|
||||
MxCommandReply Execute(StaCommand command);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ namespace MxGateway.Worker.Sta;
|
||||
|
||||
internal interface IStaWorkItem
|
||||
{
|
||||
/// <summary>Cancels the work item before it executes on the STA thread.</summary>
|
||||
void CancelBeforeExecution();
|
||||
|
||||
/// <summary>Executes the work item on the STA thread.</summary>
|
||||
void Execute();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ public sealed class StaComApartmentInitializer : IStaComApartmentInitializer
|
||||
private const int SOk = 0;
|
||||
private const int SFalse = 1;
|
||||
|
||||
/// <summary>Initializes the COM apartment in single-threaded mode.</summary>
|
||||
public void Initialize()
|
||||
{
|
||||
int hresult = CoInitializeEx(IntPtr.Zero, CoInitializeApartmentThreaded);
|
||||
@@ -18,6 +19,7 @@ public sealed class StaComApartmentInitializer : IStaComApartmentInitializer
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Uninitializes the COM apartment.</summary>
|
||||
public void Uninitialize()
|
||||
{
|
||||
CoUninitialize();
|
||||
|
||||
@@ -7,6 +7,12 @@ namespace MxGateway.Worker.Sta;
|
||||
|
||||
public sealed class StaCommand
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="StaCommand"/> class.</summary>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <param name="correlationId">Correlation identifier for the command.</param>
|
||||
/// <param name="command">The MXAccess command to execute.</param>
|
||||
/// <param name="enqueueTimestamp">Timestamp when the command was enqueued.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
public StaCommand(
|
||||
string sessionId,
|
||||
string correlationId,
|
||||
@@ -31,17 +37,24 @@ public sealed class StaCommand
|
||||
CancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
/// <summary>Gets the session ID for the STA command.</summary>
|
||||
public string SessionId { get; }
|
||||
|
||||
/// <summary>Gets the correlation ID for the STA command.</summary>
|
||||
public string CorrelationId { get; }
|
||||
|
||||
/// <summary>Gets the MXAccess command to execute.</summary>
|
||||
public MxCommand Command { get; }
|
||||
|
||||
/// <summary>Gets the timestamp when the command was enqueued.</summary>
|
||||
public Timestamp EnqueueTimestamp { get; }
|
||||
|
||||
/// <summary>Gets the token to cancel the asynchronous operation.</summary>
|
||||
public CancellationToken CancellationToken { get; }
|
||||
|
||||
/// <summary>Gets the kind of the MXAccess command.</summary>
|
||||
public MxCommandKind Kind => Command.Kind;
|
||||
|
||||
/// <summary>Gets the method name of the command.</summary>
|
||||
public string MethodName => Kind.ToString();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ public sealed class StaCommandDispatcher
|
||||
private bool shutdownRequested;
|
||||
private string currentCommandCorrelationId = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="StaCommandDispatcher"/> with default converter.
|
||||
/// </summary>
|
||||
/// <param name="staRuntime">STA thread runtime.</param>
|
||||
/// <param name="commandExecutor">Command executor.</param>
|
||||
public StaCommandDispatcher(
|
||||
StaRuntime staRuntime,
|
||||
IStaCommandExecutor commandExecutor)
|
||||
@@ -27,6 +32,12 @@ public sealed class StaCommandDispatcher
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="StaCommandDispatcher"/> with custom converter.
|
||||
/// </summary>
|
||||
/// <param name="staRuntime">STA thread runtime.</param>
|
||||
/// <param name="commandExecutor">Command executor.</param>
|
||||
/// <param name="hresultConverter">HResult converter.</param>
|
||||
public StaCommandDispatcher(
|
||||
StaRuntime staRuntime,
|
||||
IStaCommandExecutor commandExecutor,
|
||||
@@ -35,6 +46,13 @@ public sealed class StaCommandDispatcher
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="StaCommandDispatcher"/> with all parameters.
|
||||
/// </summary>
|
||||
/// <param name="staRuntime">STA thread runtime.</param>
|
||||
/// <param name="commandExecutor">Command executor.</param>
|
||||
/// <param name="hresultConverter">HResult converter.</param>
|
||||
/// <param name="maxPendingCommands">Maximum pending commands allowed.</param>
|
||||
public StaCommandDispatcher(
|
||||
StaRuntime staRuntime,
|
||||
IStaCommandExecutor commandExecutor,
|
||||
@@ -54,6 +72,9 @@ public sealed class StaCommandDispatcher
|
||||
this.maxPendingCommands = maxPendingCommands;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of pending commands in the queue.
|
||||
/// </summary>
|
||||
public int PendingCommandCount
|
||||
{
|
||||
get
|
||||
@@ -65,6 +86,9 @@ public sealed class StaCommandDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the correlation ID of the currently executing command.
|
||||
/// </summary>
|
||||
public string CurrentCommandCorrelationId
|
||||
{
|
||||
get
|
||||
@@ -76,6 +100,11 @@ public sealed class StaCommandDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches a command to the queue for asynchronous STA execution.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to dispatch.</param>
|
||||
/// <returns>Task for the command reply.</returns>
|
||||
public Task<MxCommandReply> DispatchAsync(StaCommand command)
|
||||
{
|
||||
if (command is null)
|
||||
@@ -114,6 +143,11 @@ public sealed class StaCommandDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels a queued command by its correlation ID.
|
||||
/// </summary>
|
||||
/// <param name="correlationId">Correlation ID of the command to cancel.</param>
|
||||
/// <returns>True if the command was canceled; otherwise false.</returns>
|
||||
public bool CancelQueuedCommand(string correlationId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(correlationId))
|
||||
@@ -159,6 +193,9 @@ public sealed class StaCommandDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests graceful shutdown, rejecting all queued commands.
|
||||
/// </summary>
|
||||
public void RequestShutdown()
|
||||
{
|
||||
lock (gate)
|
||||
@@ -175,6 +212,10 @@ public sealed class StaCommandDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the given heartbeat with current dispatcher state.
|
||||
/// </summary>
|
||||
/// <param name="heartbeat">Heartbeat to populate.</param>
|
||||
public void PopulateHeartbeat(WorkerHeartbeat heartbeat)
|
||||
{
|
||||
if (heartbeat is null)
|
||||
@@ -331,15 +372,29 @@ public sealed class StaCommandDispatcher
|
||||
private readonly TaskCompletionSource<MxCommandReply> completion = new(
|
||||
TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="QueuedStaCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="command">The STA command to queue.</param>
|
||||
public QueuedStaCommand(StaCommand command)
|
||||
{
|
||||
Command = command;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the queued STA command.
|
||||
/// </summary>
|
||||
public StaCommand Command { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task representing the command's completion.
|
||||
/// </summary>
|
||||
public Task<MxCommandReply> Task => completion.Task;
|
||||
|
||||
/// <summary>
|
||||
/// Completes the command with the given reply.
|
||||
/// </summary>
|
||||
/// <param name="reply">The command reply.</param>
|
||||
public void Complete(MxCommandReply reply)
|
||||
{
|
||||
completion.TrySetResult(reply);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace MxGateway.Worker.Sta;
|
||||
|
||||
/// <summary>Pumps Windows messages on the STA thread to allow MXAccess COM events to deliver.</summary>
|
||||
public sealed class StaMessagePump
|
||||
{
|
||||
private const uint Infinite = 0xFFFFFFFF;
|
||||
@@ -13,6 +14,9 @@ public sealed class StaMessagePump
|
||||
private const uint PmRemove = 0x0001;
|
||||
private const uint QsAllInput = 0x04FF;
|
||||
|
||||
/// <summary>Waits for a command wake event or Windows messages, pumping any pending messages.</summary>
|
||||
/// <param name="commandWakeEvent">Event to signal when work is available.</param>
|
||||
/// <param name="timeout">Maximum time to wait; InfiniteTimeSpan waits indefinitely.</param>
|
||||
public void WaitForWorkOrMessages(WaitHandle commandWakeEvent, TimeSpan timeout)
|
||||
{
|
||||
if (commandWakeEvent is null)
|
||||
@@ -38,6 +42,7 @@ public sealed class StaMessagePump
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Pumps and dispatches all pending Windows messages, returning the count processed.</summary>
|
||||
public int PumpPendingMessages()
|
||||
{
|
||||
int pumpedMessages = 0;
|
||||
|
||||
@@ -23,11 +23,20 @@ public sealed class StaRuntime : IDisposable
|
||||
private long lastActivityUtcTicks;
|
||||
private bool comInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="StaRuntime"/> with default dependencies.
|
||||
/// </summary>
|
||||
public StaRuntime()
|
||||
: this(new StaComApartmentInitializer(), new StaMessagePump(), TimeSpan.FromMilliseconds(50))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="StaRuntime"/> with custom dependencies.
|
||||
/// </summary>
|
||||
/// <param name="comApartmentInitializer">COM apartment initializer.</param>
|
||||
/// <param name="messagePump">Message pump for the STA thread.</param>
|
||||
/// <param name="idlePumpInterval">Interval for idle message pump waits.</param>
|
||||
public StaRuntime(
|
||||
IStaComApartmentInitializer comApartmentInitializer,
|
||||
StaMessagePump messagePump,
|
||||
@@ -54,13 +63,25 @@ public sealed class StaRuntime : IDisposable
|
||||
staThread.SetApartmentState(ApartmentState.STA);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the managed thread ID of the STA thread.
|
||||
/// </summary>
|
||||
public int? StaThreadId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timestamp of the last STA thread activity.
|
||||
/// </summary>
|
||||
public DateTimeOffset LastActivityUtc =>
|
||||
new(new DateTime(Volatile.Read(ref lastActivityUtcTicks), DateTimeKind.Utc));
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the STA runtime is currently running.
|
||||
/// </summary>
|
||||
public bool IsRunning => startedEvent.IsSet && !stoppedEvent.IsSet;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the STA thread.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -88,6 +109,12 @@ public sealed class StaRuntime : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes an action on the STA thread asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="command">Action to invoke on the STA thread.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Task that completes when the action executes.</returns>
|
||||
public Task InvokeAsync(Action command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (command is null)
|
||||
@@ -104,6 +131,13 @@ public sealed class StaRuntime : IDisposable
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes a function on the STA thread asynchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type of the function.</typeparam>
|
||||
/// <param name="command">Function to invoke on the STA thread.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Task that returns the function result.</returns>
|
||||
public Task<T> InvokeAsync<T>(Func<T> command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (command is null)
|
||||
@@ -135,6 +169,11 @@ public sealed class StaRuntime : IDisposable
|
||||
return workItem.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests graceful shutdown of the STA runtime within a timeout.
|
||||
/// </summary>
|
||||
/// <param name="timeout">Maximum time to wait for shutdown.</param>
|
||||
/// <returns>True if shutdown completed; otherwise false.</returns>
|
||||
public bool Shutdown(TimeSpan timeout)
|
||||
{
|
||||
if (timeout < TimeSpan.Zero && timeout != Timeout.InfiniteTimeSpan)
|
||||
@@ -165,6 +204,9 @@ public sealed class StaRuntime : IDisposable
|
||||
return stopped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases resources used by the STA runtime.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
|
||||
@@ -4,6 +4,9 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MxGateway.Worker.Sta;
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates a work item to be executed on an STA thread with cancellation support.
|
||||
/// </summary>
|
||||
internal sealed class StaWorkItem<T> : IStaWorkItem
|
||||
{
|
||||
private readonly Func<T> command;
|
||||
@@ -11,6 +14,9 @@ internal sealed class StaWorkItem<T> : IStaWorkItem
|
||||
private readonly CancellationTokenRegistration cancellationRegistration;
|
||||
private int started;
|
||||
|
||||
/// <summary>Initializes a work item with a command and cancellation token.</summary>
|
||||
/// <param name="command">Function to execute on the STA thread.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the work item.</param>
|
||||
public StaWorkItem(Func<T> command, CancellationToken cancellationToken)
|
||||
{
|
||||
this.command = command ?? throw new ArgumentNullException(nameof(command));
|
||||
@@ -30,10 +36,12 @@ internal sealed class StaWorkItem<T> : IStaWorkItem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the task that completes when work completes.</summary>
|
||||
public Task<T> Task => Completion.Task;
|
||||
|
||||
private TaskCompletionSource<T> Completion { get; }
|
||||
|
||||
/// <summary>Cancels the work item before execution begins.</summary>
|
||||
public void CancelBeforeExecution()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref started, 1, 0) == 0)
|
||||
@@ -43,6 +51,7 @@ internal sealed class StaWorkItem<T> : IStaWorkItem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Executes the work item command.</summary>
|
||||
public void Execute()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref started, 1, 0) != 0)
|
||||
|
||||
@@ -6,8 +6,11 @@ using MxGateway.Worker.Ipc;
|
||||
|
||||
namespace MxGateway.Worker;
|
||||
|
||||
/// <summary>Entry point for the worker process.</summary>
|
||||
public static class WorkerApplication
|
||||
{
|
||||
/// <summary>Initializes and runs the worker with default environment and logging.</summary>
|
||||
/// <param name="args">Command-line arguments.</param>
|
||||
public static int Run(string[] args)
|
||||
{
|
||||
return Run(
|
||||
@@ -16,6 +19,10 @@ public static class WorkerApplication
|
||||
new WorkerConsoleLogger(Console.Error));
|
||||
}
|
||||
|
||||
/// <summary>Initializes and runs the worker with custom environment and logging.</summary>
|
||||
/// <param name="args">Command-line arguments.</param>
|
||||
/// <param name="environment">Worker environment for resolving configuration.</param>
|
||||
/// <param name="logger">Worker logger for diagnostics.</param>
|
||||
public static int Run(
|
||||
string[] args,
|
||||
IWorkerEnvironment environment,
|
||||
@@ -28,6 +35,11 @@ public static class WorkerApplication
|
||||
new WorkerPipeClient(logger));
|
||||
}
|
||||
|
||||
/// <summary>Parses arguments, bootstraps the handshake, and runs the worker until shutdown.</summary>
|
||||
/// <param name="args">Command-line arguments.</param>
|
||||
/// <param name="environment">Worker environment for resolving configuration.</param>
|
||||
/// <param name="logger">Worker logger for diagnostics.</param>
|
||||
/// <param name="pipeClient">Named pipe client for gateway communication.</param>
|
||||
public static int Run(
|
||||
string[] args,
|
||||
IWorkerEnvironment environment,
|
||||
|
||||
Reference in New Issue
Block a user