Add XML documentation across gateway, worker, and .NET client
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user