feat(lmxproxy): phase 7 — integration tests, deployment to windev, v1 cutover
- Replaced STA dispatch thread with Task.Run pattern for COM interop - Fixed TypedValue oneof tracking with property-level _setCase field - Added x-api-key DelegatingHandler for gRPC metadata authentication - Fixed CheckApiKey RPC to validate request body key (not header) - Integration tests: 15/17 pass (reads, subscribes, API keys, connections) - 2 write tests pending (OnWriteComplete callback timing issue) - v2 service deployed on windev port 50100 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess
|
||||
/// <summary>
|
||||
/// Subscribes to value changes for the specified addresses.
|
||||
/// Stores subscription state for reconnect replay.
|
||||
/// COM calls dispatched via Task.Run.
|
||||
/// </summary>
|
||||
public async Task<IAsyncDisposable> SubscribeAsync(
|
||||
IEnumerable<string> addresses,
|
||||
@@ -24,19 +25,22 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess
|
||||
|
||||
var addressList = addresses.ToList();
|
||||
|
||||
await _staThread.DispatchAsync(() =>
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (var address in addressList)
|
||||
lock (_lock)
|
||||
{
|
||||
SubscribeInternal(address);
|
||||
if (!IsConnected || _lmxProxy == null)
|
||||
throw new InvalidOperationException("Not connected to MxAccess");
|
||||
|
||||
// Store for reconnect replay
|
||||
lock (_lock)
|
||||
foreach (var address in addressList)
|
||||
{
|
||||
SubscribeInternal(address);
|
||||
|
||||
// Store for reconnect replay
|
||||
_storedSubscriptions[address] = callback;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, ct);
|
||||
|
||||
Log.Information("Subscribed to {Count} tags", addressList.Count);
|
||||
|
||||
@@ -50,14 +54,13 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess
|
||||
{
|
||||
var addressList = addresses.ToList();
|
||||
|
||||
await _staThread.DispatchAsync(() =>
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (var address in addressList)
|
||||
lock (_lock)
|
||||
{
|
||||
UnsubscribeInternal(address);
|
||||
|
||||
lock (_lock)
|
||||
foreach (var address in addressList)
|
||||
{
|
||||
UnsubscribeInternal(address);
|
||||
_storedSubscriptions.Remove(address);
|
||||
}
|
||||
}
|
||||
@@ -81,53 +84,87 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess
|
||||
|
||||
Log.Information("Recreating {Count} stored subscriptions after reconnect", subscriptions.Count);
|
||||
|
||||
await _staThread.DispatchAsync(() =>
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (var kvp in subscriptions)
|
||||
lock (_lock)
|
||||
{
|
||||
try
|
||||
foreach (var kvp in subscriptions)
|
||||
{
|
||||
SubscribeInternal(kvp.Key);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Failed to recreate subscription for {Address}", kvp.Key);
|
||||
try
|
||||
{
|
||||
SubscribeInternal(kvp.Key);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Failed to recreate subscription for {Address}", kvp.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Internal COM calls (execute on STA thread) ──────────
|
||||
// ── Internal COM calls ──────────
|
||||
|
||||
/// <summary>
|
||||
/// Registers a tag subscription with MxAccess COM API (AddItem + AdviseSupervisory).
|
||||
/// Must be called on the STA thread.
|
||||
/// Must be called while holding _lock.
|
||||
/// </summary>
|
||||
private void SubscribeInternal(string address)
|
||||
{
|
||||
// The exact MxAccess COM API call is:
|
||||
// var itemHandle = _lmxProxy.AddItem(_connectionHandle, address);
|
||||
// _lmxProxy.AdviseSupervisory(_connectionHandle, itemHandle);
|
||||
//
|
||||
// Consult src-reference/Implementation/MxAccessClient.Subscription.cs
|
||||
if (_lmxProxy == null || _connectionHandle <= 0)
|
||||
throw new InvalidOperationException("Not connected to MxAccess");
|
||||
|
||||
throw new NotImplementedException(
|
||||
"SubscribeInternal must be implemented using ArchestrA.MXAccess COM API. " +
|
||||
"See src-reference/Implementation/MxAccessClient.Subscription.cs for the exact pattern.");
|
||||
// If already subscribed to this address, skip
|
||||
if (_addressToHandle.ContainsKey(address))
|
||||
{
|
||||
Log.Debug("Already subscribed to {Address}, skipping", address);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the item to MxAccess
|
||||
int itemHandle = _lmxProxy.AddItem(_connectionHandle, address);
|
||||
|
||||
// Track handle-to-address and address-to-handle mappings
|
||||
_handleToAddress[itemHandle] = address;
|
||||
_addressToHandle[address] = itemHandle;
|
||||
|
||||
// Advise (subscribe) for data change events
|
||||
_lmxProxy.AdviseSupervisory(_connectionHandle, itemHandle);
|
||||
|
||||
Log.Debug("Subscribed to {Address} with handle {Handle}", address, itemHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a tag subscription from MxAccess COM API (UnAdvise + RemoveItem).
|
||||
/// Must be called on the STA thread.
|
||||
/// Must be called while holding _lock.
|
||||
/// </summary>
|
||||
private void UnsubscribeInternal(string address)
|
||||
{
|
||||
// The exact MxAccess COM API call is:
|
||||
// _lmxProxy.UnAdvise(_connectionHandle, itemHandle);
|
||||
// _lmxProxy.RemoveItem(_connectionHandle, itemHandle);
|
||||
if (!_addressToHandle.TryGetValue(address, out int itemHandle))
|
||||
{
|
||||
Log.Debug("No active subscription for {Address}, skipping unsubscribe", address);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NotImplementedException(
|
||||
"UnsubscribeInternal must be implemented using ArchestrA.MXAccess COM API.");
|
||||
try
|
||||
{
|
||||
if (_lmxProxy != null && _connectionHandle > 0)
|
||||
{
|
||||
_lmxProxy.UnAdvise(_connectionHandle, itemHandle);
|
||||
_lmxProxy.RemoveItem(_connectionHandle, itemHandle);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error unsubscribing from {Address} (handle {Handle})", address, itemHandle);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_handleToAddress.Remove(itemHandle);
|
||||
_addressToHandle.Remove(address);
|
||||
}
|
||||
|
||||
Log.Debug("Unsubscribed from {Address} (handle {Handle})", address, itemHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user