Add XML documentation across gateway, worker, and .NET client

This commit is contained in:
Joseph Doherty
2026-04-30 11:49:58 -04:00
parent 4731ab535c
commit eed1e88a37
269 changed files with 4555 additions and 13 deletions
@@ -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; }
}