fix(scripted-alarms): resolve Medium code-review finding (Core.ScriptedAlarms-007)
Reorder persist/update in ApplyAsync, ReevaluateAsync, and ShelvingCheckAsync: SaveAsync is now called before the in-memory _alarms entry is advanced. A store failure therefore leaves both the persisted and in-memory views at the prior state rather than diverging, maintaining the invariant that startup recovery reflects actual persisted state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -238,8 +238,12 @@ public sealed class ScriptedAlarmEngine : IDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = op(state.Condition);
|
var result = op(state.Condition);
|
||||||
_alarms[alarmId] = state with { Condition = result.State };
|
// Persist BEFORE updating in-memory so a store failure leaves both
|
||||||
|
// in-memory and persisted at the prior state rather than diverging.
|
||||||
|
// If SaveAsync throws the in-memory _alarms entry stays unchanged and
|
||||||
|
// the exception propagates to the caller. (Core.ScriptedAlarms-007)
|
||||||
await _store.SaveAsync(result.State, ct).ConfigureAwait(false);
|
await _store.SaveAsync(result.State, ct).ConfigureAwait(false);
|
||||||
|
_alarms[alarmId] = state with { Condition = result.State };
|
||||||
if (result.Emission != EmissionKind.None) EmitEvent(state, result.State, result.Emission);
|
if (result.Emission != EmissionKind.None) EmitEvent(state, result.State, result.Emission);
|
||||||
}
|
}
|
||||||
finally { _evalGate.Release(); }
|
finally { _evalGate.Release(); }
|
||||||
@@ -279,8 +283,10 @@ public sealed class ScriptedAlarmEngine : IDisposable
|
|||||||
state, state.Condition, _clock(), ct).ConfigureAwait(false);
|
state, state.Condition, _clock(), ct).ConfigureAwait(false);
|
||||||
if (!ReferenceEquals(newState, state.Condition))
|
if (!ReferenceEquals(newState, state.Condition))
|
||||||
{
|
{
|
||||||
_alarms[id] = state with { Condition = newState };
|
// Persist before updating in-memory so a store failure leaves
|
||||||
|
// both sides at the prior state. (Core.ScriptedAlarms-007)
|
||||||
await _store.SaveAsync(newState, ct).ConfigureAwait(false);
|
await _store.SaveAsync(newState, ct).ConfigureAwait(false);
|
||||||
|
_alarms[id] = state with { Condition = newState };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,8 +428,10 @@ public sealed class ScriptedAlarmEngine : IDisposable
|
|||||||
var result = Part9StateMachine.ApplyShelvingCheck(state.Condition, now);
|
var result = Part9StateMachine.ApplyShelvingCheck(state.Condition, now);
|
||||||
if (!ReferenceEquals(result.State, state.Condition))
|
if (!ReferenceEquals(result.State, state.Condition))
|
||||||
{
|
{
|
||||||
_alarms[id] = state with { Condition = result.State };
|
// Persist before updating in-memory so a store failure leaves
|
||||||
|
// both sides at the prior state. (Core.ScriptedAlarms-007)
|
||||||
await _store.SaveAsync(result.State, ct).ConfigureAwait(false);
|
await _store.SaveAsync(result.State, ct).ConfigureAwait(false);
|
||||||
|
_alarms[id] = state with { Condition = result.State };
|
||||||
if (result.Emission != EmissionKind.None)
|
if (result.Emission != EmissionKind.None)
|
||||||
EmitEvent(state, result.State, result.Emission);
|
EmitEvent(state, result.State, result.Emission);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user