70 lines
2.4 KiB
C#
70 lines
2.4 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor;
|
|
|
|
/// <summary>
|
|
/// Crash-loop circuit breaker for the FOCAS Host. Matches Galaxy Tier-C defaults:
|
|
/// 3 crashes within 5 minutes opens the breaker; cooldown escalates 1h → 4h → manual
|
|
/// reset. A sticky alert stays live until the operator explicitly clears it so
|
|
/// recurring crashes can't silently burn through the cooldown ladder overnight.
|
|
/// </summary>
|
|
public sealed class CircuitBreaker
|
|
{
|
|
public int CrashesAllowedPerWindow { get; init; } = 3;
|
|
public TimeSpan Window { get; init; } = TimeSpan.FromMinutes(5);
|
|
|
|
public TimeSpan[] CooldownEscalation { get; init; } =
|
|
[TimeSpan.FromHours(1), TimeSpan.FromHours(4), TimeSpan.MaxValue];
|
|
|
|
private readonly List<DateTime> _crashesUtc = [];
|
|
private DateTime? _openSinceUtc;
|
|
private int _escalationLevel;
|
|
public bool StickyAlertActive { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Records a crash + returns <c>true</c> if the supervisor may respawn. On
|
|
/// <c>false</c>, <paramref name="cooldownRemaining"/> is how long to wait before
|
|
/// trying again (<c>TimeSpan.MaxValue</c> means manual reset required).
|
|
/// </summary>
|
|
public bool TryRecordCrash(DateTime utcNow, out TimeSpan cooldownRemaining)
|
|
{
|
|
if (_openSinceUtc is { } openedAt)
|
|
{
|
|
var cooldown = CooldownEscalation[Math.Min(_escalationLevel, CooldownEscalation.Length - 1)];
|
|
if (cooldown == TimeSpan.MaxValue)
|
|
{
|
|
cooldownRemaining = TimeSpan.MaxValue;
|
|
return false;
|
|
}
|
|
if (utcNow - openedAt < cooldown)
|
|
{
|
|
cooldownRemaining = cooldown - (utcNow - openedAt);
|
|
return false;
|
|
}
|
|
|
|
_openSinceUtc = null;
|
|
_escalationLevel++;
|
|
}
|
|
|
|
_crashesUtc.RemoveAll(t => utcNow - t > Window);
|
|
_crashesUtc.Add(utcNow);
|
|
|
|
if (_crashesUtc.Count > CrashesAllowedPerWindow)
|
|
{
|
|
_openSinceUtc = utcNow;
|
|
StickyAlertActive = true;
|
|
cooldownRemaining = CooldownEscalation[Math.Min(_escalationLevel, CooldownEscalation.Length - 1)];
|
|
return false;
|
|
}
|
|
|
|
cooldownRemaining = TimeSpan.Zero;
|
|
return true;
|
|
}
|
|
|
|
public void ManualReset()
|
|
{
|
|
_crashesUtc.Clear();
|
|
_openSinceUtc = null;
|
|
_escalationLevel = 0;
|
|
StickyAlertActive = false;
|
|
}
|
|
}
|