fix(communication)+fix(siteruntime): carry DetachedScriptThreads/ScriptRunShedCount/SiteStreamAlarmDropCount across the health wire; restore drained counters on failed send (arch-review 2026-08-19 P8-1)

This commit is contained in:
Joseph Doherty
2026-08-19 14:47:04 -04:00
parent eca0ab768d
commit d0a748e632
9 changed files with 228 additions and 45 deletions
@@ -554,6 +554,11 @@ public class CentralControlDtoMapperTests
LocalDbReplicationConnected = true,
LocalDbOplogBacklog = 250L,
StartupReconcilerActive = true,
// arch-review 2026-08-19 P8-1: these three never crossed the wire at all
// (mapper omitted them entirely, always reading back as 0) until this fix.
DetachedScriptThreads = 2,
ScriptRunShedCount = 13,
SiteStreamAlarmDropCount = 41L,
};
/// <summary>
@@ -603,6 +608,9 @@ public class CentralControlDtoMapperTests
Assert.Equal(expected.LocalDbReplicationConnected, actual.LocalDbReplicationConnected);
Assert.Equal(expected.LocalDbOplogBacklog, actual.LocalDbOplogBacklog);
Assert.Equal(expected.StartupReconcilerActive, actual.StartupReconcilerActive);
Assert.Equal(expected.DetachedScriptThreads, actual.DetachedScriptThreads);
Assert.Equal(expected.ScriptRunShedCount, actual.ScriptRunShedCount);
Assert.Equal(expected.SiteStreamAlarmDropCount, actual.SiteStreamAlarmDropCount);
}
/// <summary>
@@ -333,6 +333,16 @@ public class HealthReportSenderTests
/// un-sent report only, and the next successful report shipped with the
/// counters at zero.
/// </summary>
/// <remarks>
/// Extended for arch-review 2026-08-19 P8-1: <c>ScriptRunShedCount</c> is drained
/// by <c>CollectReport</c> the exact same way as the five counters above, but
/// <c>AddIntervalCounters</c> used to take only those five — a failed send
/// destructively lost the shed count forever. <c>DetachedScriptThreads</c> and
/// <c>SiteStreamAlarmDropCount</c> are deliberately NOT exercised here: they are
/// point-in-time gauges (set via <c>Interlocked.CompareExchange</c>/<c>Read</c>,
/// never zeroed by <c>CollectReport</c>), so a failed send loses nothing for them
/// to restore.
/// </remarks>
[Fact]
public async Task SendFailure_PreservesIntervalCountersForNextReport()
{
@@ -341,7 +351,7 @@ public class HealthReportSenderTests
collector.SetActiveNode(true);
// Pre-populate every per-interval counter so the restore path on each
// field is exercised — script error, alarm error, dead letter, site
// audit write failure, audit redaction failure.
// audit write failure, audit redaction failure, script run shed.
collector.IncrementScriptError();
collector.IncrementScriptError();
collector.IncrementAlarmError();
@@ -351,6 +361,10 @@ public class HealthReportSenderTests
collector.IncrementSiteAuditWriteFailures();
collector.IncrementAuditRedactionFailure();
collector.IncrementAuditRedactionFailure();
collector.IncrementScriptRunShed();
collector.IncrementScriptRunShed();
collector.IncrementScriptRunShed();
collector.IncrementScriptRunShed();
var options = Options.Create(new HealthMonitoringOptions
{
@@ -383,6 +397,7 @@ public class HealthReportSenderTests
Assert.Equal(3, firstSuccess.DeadLetterCount);
Assert.Equal(1, firstSuccess.SiteAuditWriteFailures);
Assert.Equal(2, firstSuccess.AuditRedactionFailure);
Assert.Equal(4, firstSuccess.ScriptRunShedCount);
}
/// <summary>