64c92c63e5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
200 lines
6.6 KiB
C#
200 lines
6.6 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using ArchestrA.MxAccess;
|
|
using Serilog;
|
|
using ZB.MOM.WW.LmxProxy.Host.Domain;
|
|
|
|
namespace ZB.MOM.WW.LmxProxy.Host.MxAccess
|
|
{
|
|
public sealed partial class MxAccessClient
|
|
{
|
|
/// <summary>
|
|
/// Connects to MxAccess on the STA thread.
|
|
/// </summary>
|
|
public async Task ConnectAsync(CancellationToken ct = default)
|
|
{
|
|
if (_disposed) throw new ObjectDisposedException(nameof(MxAccessClient));
|
|
if (IsConnected) return;
|
|
|
|
SetState(ConnectionState.Connecting);
|
|
|
|
try
|
|
{
|
|
await _staThread.DispatchAsync(() =>
|
|
{
|
|
// Create COM object
|
|
_lmxProxy = new LMXProxyServer();
|
|
|
|
// Wire event handlers
|
|
_lmxProxy.OnDataChange += OnDataChange;
|
|
_lmxProxy.OnWriteComplete += OnWriteComplete;
|
|
|
|
// Register with MxAccess
|
|
_connectionHandle = _lmxProxy.Register("ZB.MOM.WW.LmxProxy.Host");
|
|
});
|
|
|
|
lock (_lock)
|
|
{
|
|
_connectedSince = DateTime.UtcNow;
|
|
}
|
|
|
|
SetState(ConnectionState.Connected);
|
|
Log.Information("Connected to MxAccess (handle={Handle})", _connectionHandle);
|
|
|
|
// Recreate any stored subscriptions from a previous connection
|
|
await RecreateStoredSubscriptionsAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Failed to connect to MxAccess");
|
|
await CleanupComObjectsAsync();
|
|
SetState(ConnectionState.Error, ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnects from MxAccess on the STA thread.
|
|
/// </summary>
|
|
public async Task DisconnectAsync(CancellationToken ct = default)
|
|
{
|
|
if (!IsConnected) return;
|
|
|
|
SetState(ConnectionState.Disconnecting);
|
|
|
|
try
|
|
{
|
|
await _staThread.DispatchAsync(() =>
|
|
{
|
|
if (_lmxProxy != null && _connectionHandle > 0)
|
|
{
|
|
try
|
|
{
|
|
// Remove event handlers first
|
|
_lmxProxy.OnDataChange -= OnDataChange;
|
|
_lmxProxy.OnWriteComplete -= OnWriteComplete;
|
|
|
|
// Unregister
|
|
_lmxProxy.Unregister(_connectionHandle);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warning(ex, "Error during MxAccess unregister");
|
|
}
|
|
finally
|
|
{
|
|
// Force-release COM object
|
|
Marshal.ReleaseComObject(_lmxProxy);
|
|
_lmxProxy = null;
|
|
_connectionHandle = 0;
|
|
}
|
|
}
|
|
});
|
|
|
|
SetState(ConnectionState.Disconnected);
|
|
Log.Information("Disconnected from MxAccess");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Error during disconnect");
|
|
SetState(ConnectionState.Error, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the auto-reconnect monitor loop.
|
|
/// Call this after initial ConnectAsync succeeds.
|
|
/// </summary>
|
|
public void StartMonitorLoop()
|
|
{
|
|
if (!_autoReconnect) return;
|
|
|
|
_reconnectCts = new CancellationTokenSource();
|
|
Task.Run(() => MonitorConnectionAsync(_reconnectCts.Token));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the auto-reconnect monitor loop.
|
|
/// </summary>
|
|
public void StopMonitorLoop()
|
|
{
|
|
_reconnectCts?.Cancel();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Auto-reconnect monitor loop. Checks connection every monitorInterval.
|
|
/// On disconnect, attempts reconnect. On failure, retries at next interval.
|
|
/// </summary>
|
|
private async Task MonitorConnectionAsync(CancellationToken ct)
|
|
{
|
|
Log.Information("Connection monitor loop started (interval={IntervalMs}ms)", _monitorIntervalMs);
|
|
|
|
while (!ct.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(_monitorIntervalMs, ct);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (IsConnected) continue;
|
|
|
|
Log.Information("MxAccess disconnected, attempting reconnect...");
|
|
SetState(ConnectionState.Reconnecting);
|
|
|
|
try
|
|
{
|
|
await ConnectAsync(ct);
|
|
Log.Information("Reconnected to MxAccess successfully");
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
break;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warning(ex, "Reconnect attempt failed, will retry in {IntervalMs}ms", _monitorIntervalMs);
|
|
}
|
|
}
|
|
|
|
Log.Information("Connection monitor loop exited");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up COM objects on the STA thread after a failed connection.
|
|
/// </summary>
|
|
private async Task CleanupComObjectsAsync()
|
|
{
|
|
try
|
|
{
|
|
await _staThread.DispatchAsync(() =>
|
|
{
|
|
if (_lmxProxy != null)
|
|
{
|
|
try { _lmxProxy.OnDataChange -= OnDataChange; } catch { }
|
|
try { _lmxProxy.OnWriteComplete -= OnWriteComplete; } catch { }
|
|
try { Marshal.ReleaseComObject(_lmxProxy); } catch { }
|
|
_lmxProxy = null;
|
|
}
|
|
_connectionHandle = 0;
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warning(ex, "Error during COM object cleanup");
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets the UTC time when the connection was established.</summary>
|
|
public DateTime ConnectedSince
|
|
{
|
|
get { lock (_lock) { return _connectedSince; } }
|
|
}
|
|
}
|
|
}
|