diff --git a/lmxproxy/docs/deviations.md b/lmxproxy/docs/deviations.md index 7ff9149..c8e4321 100644 --- a/lmxproxy/docs/deviations.md +++ b/lmxproxy/docs/deviations.md @@ -50,3 +50,10 @@ Decisions made during implementation that differ from or extend the original pla **Actual**: Write is confirmed synchronously — if `_lmxProxy.Write()` returns without throwing, the write succeeded. The `OnWriteComplete` callback is kept wired for diagnostic logging only. **Why**: MxAccess completes supervisory writes synchronously. The `OnWriteComplete` COM callback never fires for simple supervisory writes, causing the original implementation to timeout waiting for a callback that would never arrive. This caused WriteAndReadBack and WriteBatchAndWait integration tests to fail. **How to apply**: Do not await `OnWriteComplete` for write confirmation. The `Write()` COM call succeeding (not throwing a COM exception) is the confirmation. Clean up (UnAdvise + RemoveItem) happens immediately after the write in a finally block. + +## 8. SubscriptionManager must create MxAccess COM subscriptions + +**Plan specified**: SubscriptionManager manages per-client channels and routes updates from MxAccess. +**Actual**: SubscriptionManager must also call `IScadaClient.SubscribeAsync()` to create the underlying COM subscriptions when a tag is first subscribed, and dispose them when the last client unsubscribes. +**Why**: The Phase 2 implementation tracked client-to-tag routing in internal dictionaries but never called `MxAccessClient.SubscribeAsync()` to create the actual MxAccess COM subscriptions (`AddItem` + `AdviseSupervisory`). Without the COM subscription, `OnDataChange` never fired and no updates were delivered to clients. This caused the `Subscribe_ReceivesUpdates` integration test to receive 0 updates over 30 seconds. +**How to apply**: `SubscriptionManager.Subscribe()` collects newly-seen tags (those without an existing `TagSubscription`) and calls `_scadaClient.SubscribeAsync()` for them, passing `OnTagValueChanged` as the callback. The returned `IAsyncDisposable` handles are tracked in `_mxAccessHandles` per address and disposed in `UnsubscribeClient()` when the last client for a tag leaves.