feat(GWC-37,WRK-32): refresh live alarm watch lists
This commit is contained in:
@@ -849,15 +849,20 @@ MXAccess item addresses for the `InAlarm`, `Acked`, `AckMsg`, and `Priority`
|
||||
subtags (confirmed AVEVA `AlarmExtension` field names, verified against the live
|
||||
ZB Galaxy `attribute_definition` rows).
|
||||
|
||||
**The list is fixed for the life of the subscription.** The worker builds its
|
||||
subtag consumer from the targets `SubscribeAlarms` carried and keeps them until
|
||||
the subscription ends; a second `SubscribeAlarms` on a subscribed session is
|
||||
rejected, and the only way to replace the list — `UnsubscribeAlarms` then
|
||||
`SubscribeAlarms` — rebuilds `FailoverAlarmConsumer`, restarting it on the
|
||||
primary and discarding the warm standby state. Re-sending on a failover would
|
||||
therefore flap the feed rather than repair it. A monitor restart (a worker fault,
|
||||
or a gateway restart) re-resolves and re-sends, and that is the supported way to
|
||||
pick up Galaxy objects added since the monitor started.
|
||||
The gateway refreshes this list on the existing alarm reconciliation cadence
|
||||
and immediately before publishing a failover-to-subtag status. It compares a
|
||||
canonical fingerprint of all six target fields and sends
|
||||
`UpdateAlarmWatchListCommand` only for a material change. The worker constructs,
|
||||
subscribes, and attaches the replacement standby on its STA before swapping it
|
||||
in; a setup failure leaves the previous standby and the current primary/subtag
|
||||
mode unchanged. The gateway advances its acknowledged fingerprint only after an
|
||||
OK reply, so a rejected update remains degraded and is retried.
|
||||
|
||||
`SubscribeAlarmsCommand.fallback_enabled` distinguishes an initially empty but
|
||||
discovery-enabled fallback from an intentionally alarm-manager-only
|
||||
subscription. In the former case the worker creates an empty standby that can be
|
||||
hot-swapped after discovery recovers. `ForceAlarmManager` leaves the field false
|
||||
and does not create a standby.
|
||||
|
||||
Because the list is fixed, the resolution is **graded** rather than merely
|
||||
logged (GWC-37). `ResolveAsync` returns an `AlarmWatchListResolution` carrying
|
||||
@@ -871,10 +876,10 @@ monitor folds the verdict into the alarm feed's `AlarmProviderStatus`:
|
||||
`AlarmProviderReasons.SubtagDiscoveryFailed` and the failure message, so a
|
||||
Galaxy Repository outage at startup is a standing status rather than one log
|
||||
line emitted possibly weeks before the failover that needs the list.
|
||||
- On the failover edge (`ApplyProviderModeChangeAsync` with `toMode == Subtag`)
|
||||
the monitor re-resolves and compares against what it sent. A difference is
|
||||
reported as `AlarmProviderReasons.SubtagWatchListStale` with both counts, on
|
||||
the same status frame as the failover, because it cannot be applied.
|
||||
- If the worker rejects a material update, the monitor retains the previous
|
||||
acknowledged fingerprint and reports
|
||||
`AlarmProviderReasons.SubtagWatchListStale` with the rejection diagnostic.
|
||||
The next reconciliation or failover edge retries the same resolved targets.
|
||||
|
||||
`degraded` on the status is therefore the OR of "the subtag provider is the one
|
||||
serving" and "the subtag watch-list cannot cover anything". In `Auto` the second
|
||||
|
||||
@@ -40,6 +40,15 @@ the corresponding MXAccess `AddItem`, `Advise`, `UnAdvise`, and `RemoveItem`
|
||||
calls sequentially on the session STA and preserves input order in the result
|
||||
list.
|
||||
|
||||
The shared alarm command model includes `SubscribeAlarms` and
|
||||
`UpdateAlarmWatchList`. `SubscribeAlarmsCommand.fallback_enabled` makes an empty
|
||||
initial watch list explicitly fallback-capable, allowing discovery recovery to
|
||||
populate a standby without restarting the primary alarm-manager subscription.
|
||||
`UpdateAlarmWatchListCommand.watch_list` is the complete replacement list. The
|
||||
worker prepares the replacement on its STA before swapping it into the existing
|
||||
failover consumer and returns a normal `MxCommandReply`; callers must treat only
|
||||
`protocol_status = OK` as acknowledgement of the new list.
|
||||
|
||||
The command model also includes bulk write/read command kinds:
|
||||
`WriteBulk`, `Write2Bulk`, `WriteSecuredBulk`, `WriteSecured2Bulk`, and
|
||||
`ReadBulk`. They are unary `Invoke` payloads on the same `MxAccessGateway`
|
||||
|
||||
@@ -439,7 +439,7 @@ behavior.
|
||||
| `MxGateway:Alarms:Enabled` | `false` | Gates the gateway's always-on central alarm monitor. When `true`, the gateway opens one gateway-owned worker session dedicated to alarms, caches the active-alarm set, and fans it out to every client through the `StreamAlarms` RPC — no client opens its own session to see alarms. |
|
||||
| `MxGateway:Alarms:SubscriptionExpression` | _(empty)_ | AVEVA alarm-subscription expression the monitor subscribes on startup, in canonical `\\<machine>\Galaxy!<area>` form. The literal `Galaxy` provider is correct regardless of the Galaxy database name. When empty and `Enabled` is `true`, the gateway falls back to `\\<MachineName>\Galaxy!<DefaultArea>` if `DefaultArea` is set. |
|
||||
| `MxGateway:Alarms:DefaultArea` | _(empty)_ | Area name used to compose a default subscription when `SubscriptionExpression` is empty. If both are empty while `Enabled` is `true`, the monitor faults with a configuration diagnostic. |
|
||||
| `MxGateway:Alarms:ReconcileIntervalSeconds` | `30` | How often the monitor reconciles its in-process alarm cache against the worker's authoritative active-alarm snapshot, catching transitions the live poll-and-diff feed missed. Floored at 5 seconds. |
|
||||
| `MxGateway:Alarms:ReconcileIntervalSeconds` | `30` | How often the monitor re-resolves and, on material change, hot-swaps the subtag fallback watch-list, then reconciles its in-process alarm cache against the worker's authoritative active-alarm snapshot. The acknowledged watch-list fingerprint advances only after an OK worker reply. Floored at 5 seconds. |
|
||||
| `MxGateway:Alarms:PollIntervalMilliseconds` | `500` | Cadence at which the worker's STA polls the AVEVA alarm consumer (`GetXmlCurrentAlarms2`) for the active-alarm snapshot the live feed diffs. Must be between `100` and `3600000` (one hour): every poll is a COM call plus an XML parse on the same STA that serves reads and writes, so a tighter cadence starves the command path, while a value above an hour stops being a cadence and silently disables alarm polling. The gateway conveys the value to the worker via the `MXGATEWAY_ALARM_POLL_INTERVAL_MS` environment variable; a missing or out-of-range value leaves the worker on the 500 ms default rather than failing the session. |
|
||||
| `MxGateway:Alarms:MaxAlarmsPerFetch` | `1024` | Cap the worker passes to `GetXmlCurrentAlarms2`'s `maxAlmCnt`. Must be between `64` and `65536` — the worker is a 32-bit process that materializes each reply as one BSTR plus a full `XmlDocument`, so an unbounded cap faults the STA with an out-of-memory rather than merely slowing it. It doubles as the **truncation threshold**: a fetch returning exactly this many records is treated as truncated, because the COM API caps its reply with no "more available" flag. On a truncated poll the worker retains the alarms the capped reply could not mention instead of letting their absence read as a clear, and logs a rate-limited `AlarmSnapshotTruncated` warning to its stderr (identifiers and counts only). **Remediation when you see that warning: raise this value** so the steady-state active-alarm count fits inside one fetch. A galaxy permanently above the cap holds stale entries in the snapshot until a sub-cap poll, and loses clear→re-raise cycles that happen entirely out of window (see `docs/DesignDecisions.md`). Conveyed to the worker via the `MXGATEWAY_ALARM_MAX_ALARMS_PER_FETCH` environment variable; a missing or out-of-range value leaves the worker on the 1024 default. |
|
||||
|
||||
|
||||
@@ -351,6 +351,18 @@ native client correlating by handle has the identical ambiguity). The exposure
|
||||
is bounded: `statuses` is an enrichment field, and `Hresult`/`ProtocolStatus`
|
||||
stay acceptance-only regardless of which completion (if any) was attributed.
|
||||
|
||||
## Live alarm watch-list replacement
|
||||
|
||||
An auto-mode `SubscribeAlarms` with `fallback_enabled = true` always builds a
|
||||
`FailoverAlarmConsumer`, even when initial discovery supplied no targets. The
|
||||
handler retains the subscription expression so `UpdateAlarmWatchList` can build
|
||||
and subscribe a new subtag consumer entirely on the session STA. The failover
|
||||
consumer attaches the replacement's transition callback before swapping the
|
||||
standby reference, then detaches and disposes the old standby. If construction,
|
||||
subscription, or attachment fails, it disposes the replacement and preserves
|
||||
the old standby. The active-provider selector is never reset, so primary mode
|
||||
stays primary and subtag mode immediately delegates to the new standby.
|
||||
|
||||
## COM Creation
|
||||
|
||||
The MXAccess analysis source at `C:\Users\dohertj2\Desktop\mxaccess` identifies
|
||||
|
||||
Reference in New Issue
Block a user