diff --git a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.EventHandlers.cs b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.EventHandlers.cs index 2cba331..a3144d0 100644 --- a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.EventHandlers.cs +++ b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.EventHandlers.cs @@ -1,5 +1,5 @@ using System; -using System.Threading; +using System.Threading.Tasks; using ArchestrA.MxAccess; using Serilog; using ZB.MOM.WW.LmxProxy.Host.Domain; @@ -79,9 +79,8 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess /// /// COM event handler for MxAccess OnWriteComplete events. - /// Signature matches the ArchestrA.MxAccess ILMXProxyServerEvents interface. - /// Kept wired for diagnostic logging only — writes are resolved synchronously - /// when the Write() COM call returns without throwing. + /// Resolves the pending TaskCompletionSource so the caller gets + /// confirmation (or error) from the OnWriteComplete callback. /// private void OnWriteComplete( int hLMXServerHandle, @@ -90,22 +89,32 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess { try { + TaskCompletionSource tcs; + bool hasPending; + lock (_lock) + { + hasPending = _pendingWrites.TryGetValue(phItemHandle, out tcs); + } + if (ItemStatus != null && ItemStatus.Length > 0) { var status = ItemStatus[0]; if (status.success == 0) { - Log.Warning("OnWriteComplete callback: write failed for handle {Handle}: {Status}", - phItemHandle, MxStatusMapper.FormatStatus(status.detail, (int)status.category, (int)status.detectedBy)); + string errorMsg = MxStatusMapper.FormatStatus(status.detail, (int)status.category, (int)status.detectedBy); + Log.Warning("OnWriteComplete: write failed for handle {Handle}: {Status}", phItemHandle, errorMsg); + if (hasPending) tcs.TrySetException(new InvalidOperationException("Write failed: " + errorMsg)); } else { - Log.Debug("OnWriteComplete callback: write succeeded for handle {Handle}", phItemHandle); + Log.Debug("OnWriteComplete: write succeeded for handle {Handle}", phItemHandle); + if (hasPending) tcs.TrySetResult(true); } } else { - Log.Debug("OnWriteComplete callback: no status for handle {Handle}", phItemHandle); + Log.Debug("OnWriteComplete: no status for handle {Handle}", phItemHandle); + tcs?.TrySetResult(true); } } catch (Exception ex) diff --git a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.ReadWrite.cs b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.ReadWrite.cs index 474d970..4e1e8cd 100644 --- a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.ReadWrite.cs +++ b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.ReadWrite.cs @@ -184,12 +184,16 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess /// /// Internal write implementation dispatched on the STA thread. - /// MxAccess completes supervisory writes synchronously — the Write() call - /// succeeding (not throwing) confirms the write. The OnWriteComplete callback - /// is kept wired for diagnostic logging but is not awaited. + /// Registers a TaskCompletionSource, calls Write(), then awaits the + /// OnWriteComplete callback via the STA message pump. Falls back to + /// fire-and-forget if the callback doesn't arrive within the timeout. /// private async Task WriteInternalAsync(string address, object value, CancellationToken ct) { + var tcs = new TaskCompletionSource(); + int itemHandle = 0; + + // Step 1: Setup and write on the STA thread await _staThread.RunAsync(() => { lock (_lock) @@ -197,24 +201,23 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess if (!IsConnected || _lmxProxy == null) throw new InvalidOperationException("Not connected to MxAccess"); - int itemHandle = 0; try { - // Add the item itemHandle = _lmxProxy.AddItem(_connectionHandle, address); - - // Advise to enable writing _lmxProxy.AdviseSupervisory(_connectionHandle, itemHandle); + // Register for OnWriteComplete callback + _pendingWrites[itemHandle] = tcs; + // Write the value (-1 = no security classification) - // MxAccess completes simple/supervisory writes synchronously. - // If Write() returns without throwing, the write succeeded. _lmxProxy.Write(_connectionHandle, itemHandle, value, -1); - Log.Debug("Write completed synchronously for {Address} (handle={Handle})", address, itemHandle); + Log.Debug("Write dispatched for {Address} (handle={Handle}), awaiting OnWriteComplete", + address, itemHandle); } catch (System.Runtime.InteropServices.COMException comEx) { + _pendingWrites.Remove(itemHandle); string enriched = string.Format("Write failed for '{0}': COM error 0x{1:X8} — {2}", address, comEx.ErrorCode, comEx.Message); Log.Error(comEx, "COM write error for {Address}: HRESULT=0x{ErrorCode:X8}", @@ -223,27 +226,56 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess } catch (Exception ex) { + _pendingWrites.Remove(itemHandle); Log.Error(ex, "Failed to write value to {Address}", address); throw; } - finally - { - // Clean up: UnAdvise + RemoveItem after write (success or failure) - if (itemHandle > 0 && _lmxProxy != null) - { - try - { - _lmxProxy.UnAdvise(_connectionHandle, itemHandle); - _lmxProxy.RemoveItem(_connectionHandle, itemHandle); - } - catch (Exception ex) - { - Log.Debug(ex, "Error cleaning up write item for {Address} (handle={Handle})", address, itemHandle); - } - } - } } }); + + // Step 2: Wait for OnWriteComplete callback (delivered via STA message pump) + try + { + using (var cts = new CancellationTokenSource(_writeTimeoutMs)) + using (ct.Register(() => cts.Cancel())) + { + cts.Token.Register(() => tcs.TrySetResult(true)); // timeout = assume success (fire-and-forget fallback) + await tcs.Task; + } + } + finally + { + // Step 3: Clean up on the STA thread + if (itemHandle > 0) + { + try + { + await _staThread.RunAsync(() => + { + lock (_lock) + { + _pendingWrites.Remove(itemHandle); + if (_lmxProxy != null && _connectionHandle > 0) + { + try + { + _lmxProxy.UnAdvise(_connectionHandle, itemHandle); + _lmxProxy.RemoveItem(_connectionHandle, itemHandle); + } + catch (Exception ex) + { + Log.Debug(ex, "Error cleaning up write item for {Address} (handle={Handle})", address, itemHandle); + } + } + } + }); + } + catch (Exception ex) + { + Log.Debug(ex, "Error dispatching write cleanup for {Address}", address); + } + } + } } ///