feat(GWC-37,WRK-32): refresh live alarm watch lists
This commit is contained in:
@@ -45,6 +45,7 @@ public sealed class AlarmCommandHandler : IAlarmCommandHandler
|
||||
private FailoverAlarmConsumer? failoverConsumer;
|
||||
private EventHandler<AlarmProviderModeChange>? providerModeChangedHandler;
|
||||
private string subscribeSessionId = string.Empty;
|
||||
private string subscriptionExpression = string.Empty;
|
||||
private bool disposed;
|
||||
|
||||
/// <summary>Initializes a new alarm command handler with the given event queue.</summary>
|
||||
@@ -153,6 +154,7 @@ public sealed class AlarmCommandHandler : IAlarmCommandHandler
|
||||
}
|
||||
|
||||
subscribeSessionId = sessionId ?? string.Empty;
|
||||
subscriptionExpression = command.SubscriptionExpression ?? string.Empty;
|
||||
IMxAccessAlarmConsumer consumer = BuildConsumer(command)
|
||||
?? throw new InvalidOperationException("Alarm consumer factory returned null.");
|
||||
|
||||
@@ -171,7 +173,7 @@ public sealed class AlarmCommandHandler : IAlarmCommandHandler
|
||||
dispatcher = new AlarmDispatcher(consumer, sink, subscribeSessionId);
|
||||
try
|
||||
{
|
||||
dispatcher.Subscribe(command.SubscriptionExpression ?? string.Empty);
|
||||
dispatcher.Subscribe(subscriptionExpression);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -212,7 +214,8 @@ public sealed class AlarmCommandHandler : IAlarmCommandHandler
|
||||
return BuildStandby(watchList);
|
||||
}
|
||||
|
||||
if (command.ForcedMode == AlarmProviderMode.Unspecified && watchList.Count > 0)
|
||||
if (command.ForcedMode == AlarmProviderMode.Unspecified
|
||||
&& (command.FallbackEnabled || watchList.Count > 0))
|
||||
{
|
||||
IMxAccessAlarmConsumer primary = consumerFactory()
|
||||
?? throw new InvalidOperationException("Alarm consumer factory returned null.");
|
||||
@@ -230,6 +233,26 @@ public sealed class AlarmCommandHandler : IAlarmCommandHandler
|
||||
?? throw new InvalidOperationException("Alarm consumer factory returned null.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateWatchList(IReadOnlyList<AlarmSubtagTarget> watchList)
|
||||
{
|
||||
if (disposed) throw new ObjectDisposedException(nameof(AlarmCommandHandler));
|
||||
if (watchList is null) throw new ArgumentNullException(nameof(watchList));
|
||||
threadAffinityCheck?.Invoke();
|
||||
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (dispatcher is null || failoverConsumer is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"UpdateAlarmWatchList requires an active fallback-capable alarm subscription.");
|
||||
}
|
||||
|
||||
IMxAccessAlarmConsumer replacement = BuildStandby(watchList);
|
||||
failoverConsumer.ReplaceStandby(replacement, subscriptionExpression);
|
||||
}
|
||||
}
|
||||
|
||||
private IMxAccessAlarmConsumer BuildStandby(IReadOnlyList<AlarmSubtagTarget> watchList)
|
||||
{
|
||||
if (standbyFactory is not null)
|
||||
@@ -276,6 +299,7 @@ public sealed class AlarmCommandHandler : IAlarmCommandHandler
|
||||
DetachProviderModeChanged();
|
||||
toDispose = dispatcher;
|
||||
dispatcher = null;
|
||||
subscriptionExpression = string.Empty;
|
||||
}
|
||||
toDispose?.Dispose();
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public sealed class FailoverAlarmConsumer : IMxAccessAlarmConsumer
|
||||
}
|
||||
|
||||
private readonly IMxAccessAlarmConsumer primary;
|
||||
private readonly IMxAccessAlarmConsumer standby;
|
||||
private IMxAccessAlarmConsumer standby;
|
||||
private readonly FailoverSettings settings;
|
||||
|
||||
private Active active = Active.Primary;
|
||||
@@ -103,6 +103,36 @@ public sealed class FailoverAlarmConsumer : IMxAccessAlarmConsumer
|
||||
/// </summary>
|
||||
public AlarmProviderMode Mode => mode;
|
||||
|
||||
/// <summary>
|
||||
/// Arms and attaches a replacement standby before atomically swapping
|
||||
/// it into the current provider mode. Setup failure disposes only the
|
||||
/// replacement and leaves the existing standby untouched.
|
||||
/// </summary>
|
||||
/// <param name="replacement">The newly built standby consumer.</param>
|
||||
/// <param name="subscription">The retained subscription expression.</param>
|
||||
public void ReplaceStandby(IMxAccessAlarmConsumer replacement, string subscription)
|
||||
{
|
||||
if (disposed) throw new ObjectDisposedException(nameof(FailoverAlarmConsumer));
|
||||
if (replacement is null) throw new ArgumentNullException(nameof(replacement));
|
||||
|
||||
try
|
||||
{
|
||||
replacement.Subscribe(subscription ?? string.Empty);
|
||||
replacement.AlarmTransitionEmitted += OnChildTransition;
|
||||
}
|
||||
catch
|
||||
{
|
||||
try { replacement.Dispose(); } catch { /* preserve setup failure */ }
|
||||
throw;
|
||||
}
|
||||
|
||||
IMxAccessAlarmConsumer previous = standby;
|
||||
standby = replacement;
|
||||
|
||||
try { previous.AlarmTransitionEmitted -= OnChildTransition; } catch { /* best effort */ }
|
||||
try { previous.Dispose(); } catch { /* replacement is already active */ }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Subscribe(string subscription)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,8 @@ namespace ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
||||
/// <summary>
|
||||
/// Per-session interface routing the worker's alarm IPC commands —
|
||||
/// <c>SubscribeAlarmsCommand</c>, <c>AcknowledgeAlarmCommand</c>,
|
||||
/// <c>QueryActiveAlarmsCommand</c>, <c>UnsubscribeAlarmsCommand</c> —
|
||||
/// <c>UpdateAlarmWatchListCommand</c>, <c>QueryActiveAlarmsCommand</c>,
|
||||
/// <c>UnsubscribeAlarmsCommand</c> —
|
||||
/// to the underlying <see cref="AlarmDispatcher"/>. Production binding
|
||||
/// is <see cref="AlarmCommandHandler"/>; tests substitute a fake.
|
||||
/// </summary>
|
||||
@@ -24,6 +25,13 @@ public interface IAlarmCommandHandler : IDisposable
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
void Subscribe(SubscribeAlarmsCommand command, string sessionId);
|
||||
|
||||
/// <summary>
|
||||
/// Replace the active auto-failover subscription's subtag standby
|
||||
/// targets without restarting its primary provider.
|
||||
/// </summary>
|
||||
/// <param name="watchList">The complete replacement watch list.</param>
|
||||
void UpdateWatchList(IReadOnlyList<AlarmSubtagTarget> watchList);
|
||||
|
||||
/// <summary>Tear down the active subscription. No-op if not subscribed.</summary>
|
||||
void Unsubscribe();
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
MxCommandKind.WriteSecured2Bulk => ExecuteWriteSecured2Bulk(command),
|
||||
MxCommandKind.ReadBulk => ExecuteReadBulk(command),
|
||||
MxCommandKind.SubscribeAlarms => ExecuteSubscribeAlarms(command),
|
||||
MxCommandKind.UpdateAlarmWatchList => ExecuteUpdateAlarmWatchList(command),
|
||||
MxCommandKind.UnsubscribeAlarms => ExecuteUnsubscribeAlarms(command),
|
||||
MxCommandKind.AcknowledgeAlarm => ExecuteAcknowledgeAlarm(command),
|
||||
MxCommandKind.AcknowledgeAlarmByName => ExecuteAcknowledgeAlarmByName(command),
|
||||
@@ -866,6 +867,30 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
}
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteUpdateAlarmWatchList(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.UpdateAlarmWatchList)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "UpdateAlarmWatchList command payload is required.");
|
||||
}
|
||||
if (alarmCommandHandler is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
"UpdateAlarmWatchList requires an alarm command handler; the worker was constructed without one.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
alarmCommandHandler.UpdateWatchList(command.Command.UpdateAlarmWatchList.WatchList);
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CreateAlarmFailureReply(command, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAcknowledgeAlarm(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AcknowledgeAlarmCommand)
|
||||
|
||||
Reference in New Issue
Block a user