fix(worker): handle AckMsg add/advise/write COM failures and server death
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
This commit is contained in:
@@ -111,17 +111,48 @@ public sealed class AlarmAckWriter : IAlarmAckWriter
|
||||
$"Operator authentication failed for '{ackOperator.VerifyUser}' (no user id returned).");
|
||||
}
|
||||
|
||||
if (!itemHandles.TryGetValue(itemAddress, out int itemHandle))
|
||||
ulong baselineVersion = 0;
|
||||
int itemHandle = 0;
|
||||
bool cached = itemHandles.TryGetValue(itemAddress, out itemHandle);
|
||||
string step = "AddItem";
|
||||
try
|
||||
{
|
||||
itemHandle = mx.AddItem(serverHandle, itemAddress);
|
||||
// MXAccess rejects writes to added-but-unadvised items with E_INVALIDARG.
|
||||
mx.Advise(serverHandle, itemHandle);
|
||||
itemHandles[itemAddress] = itemHandle;
|
||||
if (!cached)
|
||||
{
|
||||
itemHandle = mx.AddItem(serverHandle, itemAddress);
|
||||
itemHandles[itemAddress] = itemHandle;
|
||||
step = "Advise";
|
||||
// MXAccess rejects writes to added-but-unadvised items with E_INVALIDARG.
|
||||
mx.Advise(serverHandle, itemHandle);
|
||||
}
|
||||
|
||||
pumpStep();
|
||||
step = "Write";
|
||||
baselineVersion = completionCache.CurrentVersion(serverHandle, itemHandle);
|
||||
mx.Write(serverHandle, itemHandle, comment ?? string.Empty, userId);
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
int hresult = ex.HResult;
|
||||
if (itemHandles.Remove(itemAddress))
|
||||
{
|
||||
try { mx.UnAdvise(serverHandle, itemHandle); } catch { /* best effort */ }
|
||||
try { mx.RemoveItem(serverHandle, itemHandle); } catch { /* best effort */ }
|
||||
}
|
||||
|
||||
if (IsServerGone(hresult))
|
||||
{
|
||||
TearDownServer();
|
||||
}
|
||||
|
||||
// Diagnostic names the step and HRESULT only: exception text can echo the item or operator.
|
||||
return AlarmAckResult.Create(
|
||||
AlarmAckOutcome.WriteRejected,
|
||||
hresult,
|
||||
$"AckMsg {step} failed (HRESULT 0x{hresult:X8}).");
|
||||
}
|
||||
|
||||
pumpStep();
|
||||
ulong baseline = completionCache.CurrentVersion(serverHandle, itemHandle);
|
||||
mx.Write(serverHandle, itemHandle, comment ?? string.Empty, userId);
|
||||
ulong baseline = baselineVersion;
|
||||
|
||||
if (!completionCache.TryWaitForCompletion(
|
||||
serverHandle,
|
||||
@@ -158,6 +189,17 @@ public sealed class AlarmAckWriter : IAlarmAckWriter
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
|
||||
TearDownServer();
|
||||
}
|
||||
|
||||
private static bool IsServerGone(int hresult) =>
|
||||
hresult == unchecked((int)0x80010108) // RPC_E_DISCONNECTED
|
||||
|| hresult == unchecked((int)0x800706BA) // RPC_S_SERVER_UNAVAILABLE
|
||||
|| hresult == unchecked((int)0x80010105); // RPC_E_SERVERFAULT
|
||||
|
||||
/// <summary>Releases the MXAccess registration so the next call re-registers (also used by Dispose).</summary>
|
||||
private void TearDownServer()
|
||||
{
|
||||
if (comEventSource is not null)
|
||||
{
|
||||
try { comEventSource.OnWriteComplete -= OnWriteComplete; } catch { /* best effort */ }
|
||||
@@ -190,7 +232,12 @@ public sealed class AlarmAckWriter : IAlarmAckWriter
|
||||
{
|
||||
if (server is not null) return server;
|
||||
|
||||
object created = factory!.Create() ?? throw new InvalidOperationException("MXAccess COM factory returned null.");
|
||||
if (factory is null)
|
||||
{
|
||||
throw new InvalidOperationException("MXAccess server is gone and no COM factory is available to re-register.");
|
||||
}
|
||||
|
||||
object created = factory.Create() ?? throw new InvalidOperationException("MXAccess COM factory returned null.");
|
||||
try
|
||||
{
|
||||
comObject = created;
|
||||
|
||||
Reference in New Issue
Block a user