64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
118 lines
5.6 KiB
C#
118 lines
5.6 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Serilog;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Ipc;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend
|
|
{
|
|
/// <summary>
|
|
/// IPC-side <see cref="IAlarmEventWriter"/> implementation that delegates to an
|
|
/// <see cref="IAlarmHistorianWriteBackend"/> (production: aahClientManaged-bound)
|
|
/// and maps the trinary <see cref="AlarmHistorianWriteOutcome"/> down to the
|
|
/// <c>bool[]</c> the IPC reply contract carries. Per-event outcomes:
|
|
/// <list type="bullet">
|
|
/// <item><description><see cref="AlarmHistorianWriteOutcome.Ack"/> → <c>true</c> (drop from sender's queue).</description></item>
|
|
/// <item><description><see cref="AlarmHistorianWriteOutcome.RetryPlease"/> → <c>false</c> (sender retries on next drain tick).</description></item>
|
|
/// <item><description><see cref="AlarmHistorianWriteOutcome.PermanentFail"/> → <c>false</c> (sender's B.4 widens the IPC bool back into the trinary outcome by inspecting structured diagnostics; this slot intentionally collapses to "not-ok" at the wire).</description></item>
|
|
/// </list>
|
|
/// </summary>
|
|
public sealed class AahClientManagedAlarmEventWriter : IAlarmEventWriter
|
|
{
|
|
private static readonly ILogger Log = Serilog.Log.ForContext<AahClientManagedAlarmEventWriter>();
|
|
|
|
private readonly IAlarmHistorianWriteBackend _backend;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the AahClientManagedAlarmEventWriter class.
|
|
/// </summary>
|
|
/// <param name="backend">The alarm historian write backend to delegate to.</param>
|
|
public AahClientManagedAlarmEventWriter(IAlarmHistorianWriteBackend backend)
|
|
{
|
|
_backend = backend ?? throw new ArgumentNullException(nameof(backend));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes an array of alarm historian events asynchronously.
|
|
/// </summary>
|
|
/// <param name="events">The alarm events to write.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
public async Task<bool[]> WriteAsync(AlarmHistorianEventDto[] events, CancellationToken cancellationToken)
|
|
{
|
|
if (events is null || events.Length == 0)
|
|
{
|
|
return new bool[0];
|
|
}
|
|
|
|
AlarmHistorianWriteOutcome[] outcomes;
|
|
try
|
|
{
|
|
outcomes = await _backend.WriteBatchAsync(events, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Backend-level failure (cluster unreachable, transport error). Treat the
|
|
// whole batch as RetryPlease so the sender's queue holds the rows for
|
|
// the next drain tick — preferable to dropping them on a transient.
|
|
Log.Warning(ex,
|
|
"Alarm historian backend WriteBatchAsync threw — marking entire {Count}-event batch RetryPlease.",
|
|
events.Length);
|
|
var fallback = new bool[events.Length];
|
|
return fallback;
|
|
}
|
|
|
|
if (outcomes.Length != events.Length)
|
|
{
|
|
// Backend contract violation — defensive degrade so a bug in the backend
|
|
// doesn't desync the sender's queue accounting. Treat as RetryPlease.
|
|
Log.Warning(
|
|
"Alarm historian backend returned {ReturnedCount} outcomes for a batch of {InputCount} events; degrading to RetryPlease for the whole batch.",
|
|
outcomes.Length, events.Length);
|
|
return new bool[events.Length];
|
|
}
|
|
|
|
var perEventOk = new bool[outcomes.Length];
|
|
for (var i = 0; i < outcomes.Length; i++)
|
|
{
|
|
perEventOk[i] = outcomes[i] == AlarmHistorianWriteOutcome.Ack;
|
|
}
|
|
return perEventOk;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Translate the outcome of a single SDK call (raw HRESULT + diagnostic) into the
|
|
/// trinary <see cref="AlarmHistorianWriteOutcome"/>. Exposed for the production
|
|
/// <see cref="SdkAlarmHistorianWriteBackend"/> to share the mapping with tests.
|
|
/// </summary>
|
|
/// <param name="hresult">The HRESULT code from the SDK call.</param>
|
|
/// <param name="isCommunicationError">Indicates whether the error is a communication-class error.</param>
|
|
/// <param name="isMalformedInput">Indicates whether the input was malformed.</param>
|
|
public static AlarmHistorianWriteOutcome MapOutcome(int hresult, bool isCommunicationError, bool isMalformedInput)
|
|
{
|
|
// Order matters: malformed input is permanent regardless of HRESULT pattern;
|
|
// communication-class errors are transient regardless of which specific
|
|
// HRESULT bit fired.
|
|
if (isMalformedInput)
|
|
{
|
|
return AlarmHistorianWriteOutcome.PermanentFail;
|
|
}
|
|
if (hresult == 0)
|
|
{
|
|
return AlarmHistorianWriteOutcome.Ack;
|
|
}
|
|
if (isCommunicationError)
|
|
{
|
|
return AlarmHistorianWriteOutcome.RetryPlease;
|
|
}
|
|
// Default: unknown HRESULT failure — be conservative and let the sender retry.
|
|
// The sender's drain worker has its own dead-letter cap so a permanently-broken
|
|
// event won't loop forever.
|
|
return AlarmHistorianWriteOutcome.RetryPlease;
|
|
}
|
|
}
|
|
}
|