953bd27533
- Writes: a second write to a tag already inside the driver waits in the queue behind it (_tagsInFlight) while other tags still run under the cap of 4; queued writes are stamped and failed BadTimeout at dequeue once older than the 8 s host Ask budget (Props queuedWriteBudget) instead of being written late. - Initialise attempts are bounded pessimistically (initializeTimeout, default 30 s = 2x Tier C) on top of the invoker's token-only timeout: an overrun is logged as abandoned, reported InitializeFailed so the retry loop continues, and its late result is dropped by generation. Generations come from a process-wide Interlocked counter so a prior incarnation's late result cannot collide (F6). - PostStop shares one shutdownTimeout budget across shutdown + dispose. - TelemetryDialSupervisor cancels the establishment deadline on StreamStopped; FleetDiagnosticsClient builds the ActorSelection inside the try (a malformed node degrades to "unavailable"); the diagnostics page labels the column "Last health publish". - Tests: same-tag serialisation with a distinct-tag positive control; stale queued write BadTimeout; token-ignoring hung init -> later attempt connects; stopwatch assertion demoted to a ceiling. Refs #731 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
68 lines
3.2 KiB
C#
68 lines
3.2 KiB
C#
using Akka.Actor;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.OtOpcUa.Cluster;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Interfaces;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Fleet;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Clients;
|
|
|
|
/// <summary>
|
|
/// <see cref="IFleetDiagnosticsClient"/> backed by an Akka <see cref="ActorSelection"/> against
|
|
/// the target node's <c>DriverHostActor</c> at <c>akka.tcp://otopcua@<host>:<port>/user/driver-host</c>.
|
|
/// Sends <see cref="GetDiagnostics"/>; expects a <see cref="NodeDiagnosticsSnapshot"/> reply.
|
|
///
|
|
/// On timeout or any other failure (peer down, GetDiagnostics handler missing) returns an
|
|
/// empty snapshot so the UI degrades to "no data" instead of throwing. The failure is logged at
|
|
/// Warning, since the snapshot shape cannot carry it: an empty snapshot reads the same as a node with
|
|
/// no drivers. Consumed by the <c>/fleet/diagnostics</c> page.
|
|
/// </summary>
|
|
public sealed class FleetDiagnosticsClient : IFleetDiagnosticsClient
|
|
{
|
|
private static readonly TimeSpan AskTimeout = TimeSpan.FromSeconds(3);
|
|
|
|
private readonly ActorSystem _system;
|
|
private readonly string _systemName;
|
|
private readonly ILogger<FleetDiagnosticsClient> _log;
|
|
|
|
/// <summary>Initializes a new FleetDiagnosticsClient with the given actor system and cluster options.</summary>
|
|
/// <param name="system">The Akka actor system.</param>
|
|
/// <param name="options">Cluster configuration options.</param>
|
|
/// <param name="log">Logger for the degraded-to-empty path; defaults to a no-op logger.</param>
|
|
public FleetDiagnosticsClient(
|
|
ActorSystem system, IOptions<AkkaClusterOptions> options, ILogger<FleetDiagnosticsClient>? log = null)
|
|
{
|
|
_system = system;
|
|
_systemName = options.Value.SystemName;
|
|
_log = log ?? NullLogger<FleetDiagnosticsClient>.Instance;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<NodeDiagnosticsSnapshot> GetDiagnosticsAsync(NodeId nodeId, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
// Inside the try: a malformed node value makes ActorSelection throw, and that must degrade to
|
|
// "unavailable" like a dead node does, not surface through the page's error boundary.
|
|
var selection = _system.ActorSelection($"akka.tcp://{_systemName}@{nodeId.Value}/user/driver-host");
|
|
using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
linked.CancelAfter(AskTimeout);
|
|
return await selection.Ask<NodeDiagnosticsSnapshot>(
|
|
new GetDiagnostics(CorrelationId.NewId()), AskTimeout, linked.Token);
|
|
}
|
|
catch (Exception ex) when (!ct.IsCancellationRequested)
|
|
{
|
|
_log.LogWarning(ex,
|
|
"Fleet diagnostics for node {NodeId} unavailable (no answer within {Timeout}s); returning an empty snapshot",
|
|
nodeId.Value, AskTimeout.TotalSeconds);
|
|
return new NodeDiagnosticsSnapshot(
|
|
nodeId,
|
|
CurrentRevision: null,
|
|
Drivers: Array.Empty<DriverInstanceDiagnostics>(),
|
|
AsOfUtc: DateTime.UtcNow);
|
|
}
|
|
}
|
|
}
|