b815446940
Both `IDriver` members had no production call site. Their only consumer was `MemoryTracking`, deleted with the driver-tier recycle machinery in #522, and `GetMemoryFootprint`'s doc-comment claimed "polled every 30s by Core" — which had not been true since v3, even before #522 removed the poller. Owner decision on #525 was (a) delete rather than (b) build the process-wide watchdog that would consume them. The driver-side implementations are in git history and were mostly reusable; what was always missing is the trigger, and a member nobody calls is how the misleading doc-comment survived this long. Removed from `IDriver`, all 12 driver implementations, and every test stub — plus the support code that existed only to feed them, which is the part a member-only deletion would have left behind as dead weight: - MTConnect's three per-entry byte-weight constants, its cached `ProbeModelDataItemCount`, and `CountDataItems` — a full device-tree walk run on every cache fill to populate a number with no remaining reader. - OpcUaClient's `_discoveredNodeCount` field and the discovery-pass write to it. - MQTT's `ApproxBytesPerAuthoredTag`. Two behavioural notes, neither a change in what the software does: - `AbCipTemplateCache` now has no production reset path. Its only caller was AB CIP's flush, which itself had no caller — so nothing changes except how visible the fact is. Recorded on the type, along with the correction that its comment's claim about a device `ReinitializeAsync` also flushing it was never true. A UDT redefined on a live controller keeps its stale shape until the driver instance is rebuilt. - MTConnect's `/probe` cache is still dropped, by `InvalidateProbeModel()` on an Agent restart. That is now `internal` rather than `private` so the tests which used the flush purely to empty the cache drive the real path instead — they now cover a case that actually occurs in production. Several unrelated doc comments cited `FlushOptionalCachesAsync` as the canonical CAS example and are repointed to it. Tests: the ones that existed only to assert on the removed members are deleted (3 OpcUaClient, 3 Galaxy, 2 MQTT, 1 MTConnect, 1 AbCip). Worth noting one was vacuous — `FlushOptionalCachesAsync_resets_footprint_counter` asserted a footprint of 0 before the flush and 0 after with no discovery in between, so it would have passed against a flush that did nothing at all. Full suite green except 7 failures verified identical on master before and after (4 AbLegacy integration with the compose profile down, 3 Host.IntegrationTests), checked by name rather than assumed. Docs corrected where they described this as live: `DriverLifecycle.md` (which carried the #525 placeholder), `drivers/AbCip.md`, `drivers/AbLegacy.md`, and `v2/driver-stability.md` — whose "polls this every 30 s" + per-driver `DriverConfig` memory budget were never built as written and are now banner-ed as design intent rather than description. Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
69 lines
4.5 KiB
C#
69 lines
4.5 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
/// <summary>
|
|
/// Required capability for every driver instance. Owns lifecycle, metadata, health.
|
|
/// Other capabilities (<see cref="ITagDiscovery"/>, <see cref="IReadable"/>,
|
|
/// <see cref="IWritable"/>, <see cref="ISubscribable"/>, <see cref="IAlarmSource"/>,
|
|
/// <see cref="IHistoryProvider"/>, <see cref="IRediscoverable"/>,
|
|
/// <see cref="IHostConnectivityProbe"/>) are composable — a driver implements only what its
|
|
/// backend actually supports.
|
|
/// </summary>
|
|
public interface IDriver
|
|
{
|
|
/// <summary>Stable logical ID of this driver instance, sourced from the central config DB.</summary>
|
|
string DriverInstanceId { get; }
|
|
|
|
/// <summary>Driver type name (e.g. "Galaxy", "Modbus", "AbCip"). Matches <c>DriverInstance.DriverType</c>.</summary>
|
|
string DriverType { get; }
|
|
|
|
/// <summary>Initialize the driver from its <c>DriverConfig</c> JSON; open connections; prepare for first use.</summary>
|
|
/// <param name="driverConfigJson">The driver configuration as JSON.</param>
|
|
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Apply a config change in place without tearing down the driver process.
|
|
/// Invoked by the v2 <c>DriverInstanceActor</c> when ApplyDelta reports that only this
|
|
/// driver's config changed in the new deployment.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Per <c>docs/v2/driver-stability.md</c> §"In-process only (Tier A/B)" — Reinitialize is the
|
|
/// only Core-initiated recovery path for in-process drivers; if it fails, the driver instance
|
|
/// is marked Faulted and its nodes go Bad quality, but the server process keeps running.
|
|
/// </remarks>
|
|
/// <param name="driverConfigJson">The driver configuration as JSON.</param>
|
|
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken);
|
|
|
|
/// <summary>Stop the driver, close connections, release resources. Called on shutdown or driver removal.</summary>
|
|
/// <remarks>
|
|
/// <para><b>Land the terminal health value AFTER the teardown, never only before it</b> (Gitea #517).
|
|
/// Teardown drains in-flight poll / probe loops, and those loops write health as they unwind — so a
|
|
/// driver that wrote <see cref="DriverState.Unknown"/> first and then tore down could have a late
|
|
/// loop overwrite it with <see cref="DriverState.Healthy"/> and leave THAT as the resting state,
|
|
/// with every connection already closed. This is why the fleet writes last, and it is the reason
|
|
/// #517's original suggestion — "write the transitional value before disposing" — is only safe as an
|
|
/// ADDITIONAL write, bracketing the teardown, rather than as a move.</para>
|
|
/// <para>Bracketing matters wherever a <see cref="GetHealth"/> can land mid-teardown. It cannot on
|
|
/// this path today: <c>DriverInstanceActor</c> calls this from <c>PostStop</c>, on the actor thread,
|
|
/// after the mailbox has drained, so its health poll is serialised against it. It CAN on the
|
|
/// initialize path, which the same actor runs on a fire-and-forget <c>Task.Run</c> while the health
|
|
/// tick keeps firing — so a driver whose <see cref="InitializeAsync"/> disposes anything on its
|
|
/// failure path must write the failed state before disposing as well as after.</para>
|
|
/// </remarks>
|
|
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
Task ShutdownAsync(CancellationToken cancellationToken);
|
|
|
|
/// <summary>Current health snapshot, polled by Core for the status dashboard and ServiceLevel.
|
|
/// <para>Must reflect the driver's ACTUAL state rather than a constant — a hard-coded
|
|
/// <see cref="DriverState.Healthy"/> reports a healthy driver after shutdown and before initialize
|
|
/// alike, which is what Gitea #517 found in <c>CalculationDriver</c>. A driver with no transport still
|
|
/// has a running / not-running distinction worth reporting.</para></summary>
|
|
/// <returns>The driver's current health snapshot.</returns>
|
|
DriverHealth GetHealth();
|
|
}
|
|
|