From 59b72fe3857f6bdd73cb37b86e92728bd560e5e3 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 22 Oct 2026 10:15:01 -0400 Subject: [PATCH] fix(runtime): diagnostics Name stays the string instance id; order-tolerant write-cap test (A6 follow-through) DriverInstanceDiagnostics.Name carries the string driver-instance id (what /hosts, telemetry and the artifact key on) and DriverInstanceId the DB row Guid; the wire record has no slot for the authored display name. The write-cap test asserts entry order per slot pair instead of completion order of two concurrent writes. Refs #731 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT --- .../Components/Pages/FleetDiagnostics.razor | 2 +- .../Drivers/DriverHostActor.cs | 14 ++++++++------ .../Drivers/DriverHostActorDiagnosticsTests.cs | 4 ++-- .../DriverInstanceActorConcurrentWriteTests.cs | 10 +++++++++- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/FleetDiagnostics.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/FleetDiagnostics.razor index 60d2c993..b2171f3a 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/FleetDiagnostics.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/FleetDiagnostics.razor @@ -115,7 +115,7 @@ else - + diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs index 1e5be182..76c82ebf 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs @@ -2540,15 +2540,17 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS } /// - /// One child's diagnostics row. Device counts come from the driver's host statuses when it is an - /// IHostConnectivityProbe (Running = connected; Stopped/Faulted = faulted; Unknown = neither); - /// a single-host driver counts as one device, connected while it reports Healthy or Degraded and - /// faulted while it reports Faulted. A child that has not published yet reports "Connecting" (or - /// "Stubbed") with its spawn time as the last change. + /// One child's diagnostics row. Name carries the string driver-instance id — the identity every + /// other surface (/hosts, telemetry, the artifact) keys on — and DriverInstanceId the DB row Guid; + /// the wire record has no slot for the authored display name. Device counts come from the driver's host + /// statuses when it is an IHostConnectivityProbe (Running = connected; Stopped/Faulted = faulted; + /// Unknown = neither); a single-host driver counts as one device, connected while it reports Healthy or + /// Degraded and faulted while it reports Faulted. A child that has not published yet reports + /// "Connecting" (or "Stubbed") with its spawn time as the last change. /// private static DriverInstanceDiagnostics ToDiagnostics(string driverInstanceId, ChildEntry entry, ChildHealthRecord? health) { - var name = string.IsNullOrEmpty(entry.Spec.Name) ? driverInstanceId : entry.Spec.Name; + var name = driverInstanceId; if (entry.Stubbed) return new DriverInstanceDiagnostics(entry.Spec.DriverInstanceRowId, name, "Stubbed", 0, 0, entry.SpawnedUtc); if (health is null) diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDiagnosticsTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDiagnosticsTests.cs index 4bd833cb..86ca424b 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDiagnosticsTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDiagnosticsTests.cs @@ -11,7 +11,7 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers; /// /// Review 02 U1: GetDiagnostics answered a placeholder — Guid.Empty, "Spawned", zero devices and /// "now" for every child, in every behaviour — so the snapshot looked live and said nothing. It now carries -/// each child's row id, authored name, last-published health state, device counts and last change time. +/// each child's row id, instance id, last-published health state, device counts and last change time. /// public sealed class DriverHostActorDiagnosticsTests : RuntimeActorTestBase { @@ -39,7 +39,7 @@ public sealed class DriverHostActorDiagnosticsTests : RuntimeActorTestBase snapshot.RunningFromCache.ShouldBeFalse(); var driver = snapshot.Drivers.ShouldHaveSingleItem(); driver.DriverInstanceId.ShouldBe(rowId, "the real row id, not Guid.Empty"); - driver.Name.ShouldBe("Line1-Modbus", "the authored name, not the instance id"); + driver.Name.ShouldBe(DriverId, "the string instance id every other surface keys on"); driver.State.ShouldBe("Healthy"); driver.ConnectedDevices.ShouldBe(1); driver.FaultedDevices.ShouldBe(0); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs index f44b21f3..b4bf5683 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs @@ -96,7 +96,12 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa driver.ReleaseWrites(); var results = await Task.WhenAll(asks); results.ShouldAllBe(r => r.Success); - driver.Writes.Select(w => w.FullReference).ShouldBe(new[] { "tag-0", "tag-1", "tag-2", "tag-3" }); + // Entry order: the first two slots went to the first two writes (in either order — they are + // concurrent), the queued pair entered only after a slot freed, in queue order. + var entered = driver.EnteredRefs; + entered.Count.ShouldBe(4); + entered.Take(2).ShouldBe(new[] { "tag-0", "tag-1" }, ignoreOrder: true); + entered.Skip(2).ShouldBe(new[] { "tag-2", "tag-3" }); } [Fact] @@ -129,6 +134,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa { private readonly StubHandle _handle = new(); private readonly ConcurrentQueue _writes = new(); + private readonly ConcurrentQueue _enteredRefs = new(); private TaskCompletionSource _release = new(TaskCreationOptions.RunContinuationsAsynchronously); private int _entered; private int _inside; @@ -138,6 +144,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa public bool HoldWrites { get; init; } public IReadOnlyList Writes => _writes.ToArray(); + public IReadOnlyList EnteredRefs => _enteredRefs.ToArray(); public int WritesEntered => Volatile.Read(ref _entered); public int MaxConcurrentWrites => Volatile.Read(ref _maxInside); @@ -154,6 +161,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa public async Task> WriteAsync(IReadOnlyList writes, CancellationToken cancellationToken) { Interlocked.Increment(ref _entered); + foreach (var w in writes) _enteredRefs.Enqueue(w.FullReference); var inside = Interlocked.Increment(ref _inside); int seen; while (inside > (seen = Volatile.Read(ref _maxInside)))
DriverDriver instance Row id State Connected devices