using Microsoft.Extensions.Logging; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Core.Stability; /// /// Tier C opt-in periodic-recycle driver per docs/v2/plan.md decision #67. /// A tick method advanced by the caller (fed by a background timer in prod; by test clock /// in unit tests) decides whether the configured interval has elapsed and, if so, drives the /// supplied to recycle the Host. /// /// /// Tier A/B drivers MUST NOT use this class — scheduled recycle for in-process drivers would /// kill every OPC UA session and every co-hosted driver. The ctor throws when constructed /// with any tier other than C to make the misuse structurally impossible. /// /// Keeps no background thread of its own — callers invoke on /// their ambient scheduler tick (Phase 6.1 Stream C's health-endpoint host runs one). That /// decouples the unit under test from wall-clock time and thread-pool scheduling. /// public sealed class ScheduledRecycleScheduler { private readonly TimeSpan _recycleInterval; private readonly IDriverSupervisor _supervisor; private readonly ILogger _logger; private DateTime _nextRecycleUtc; /// /// Construct the scheduler for a Tier C driver. Throws if isn't C. /// /// Driver tier; must be . /// Interval between recycles (e.g. 7 days). /// Anchor time; next recycle fires at + . /// Supervisor that performs the actual recycle. /// Diagnostic sink. public ScheduledRecycleScheduler( DriverTier tier, TimeSpan recycleInterval, DateTime startUtc, IDriverSupervisor supervisor, ILogger logger) { if (tier != DriverTier.C) throw new ArgumentException( $"ScheduledRecycleScheduler is Tier C only (got {tier}). " + "In-process drivers must not use scheduled recycle; see decisions #74 and #145.", nameof(tier)); if (recycleInterval <= TimeSpan.Zero) throw new ArgumentException("RecycleInterval must be positive.", nameof(recycleInterval)); _recycleInterval = recycleInterval; _supervisor = supervisor; _logger = logger; _nextRecycleUtc = startUtc + recycleInterval; } /// Next scheduled recycle UTC. Advances by on each fire. public DateTime NextRecycleUtc => _nextRecycleUtc; /// Recycle interval this scheduler was constructed with. public TimeSpan RecycleInterval => _recycleInterval; /// /// Tick the scheduler forward. If is past /// , requests a recycle from the supervisor and advances /// by exactly one interval. Returns true when a recycle fired. /// public async Task TickAsync(DateTime utcNow, CancellationToken cancellationToken) { if (utcNow < _nextRecycleUtc) return false; _logger.LogInformation( "Scheduled recycle due for Tier C driver {DriverId} at {Now:o}; advancing next to {Next:o}.", _supervisor.DriverInstanceId, utcNow, _nextRecycleUtc + _recycleInterval); await _supervisor.RecycleAsync("Scheduled periodic recycle", cancellationToken).ConfigureAwait(false); _nextRecycleUtc += _recycleInterval; return true; } /// Request an immediate recycle outside the schedule (e.g. MemoryRecycle hard-breach escalation). public Task RequestRecycleNowAsync(string reason, CancellationToken cancellationToken) => _supervisor.RecycleAsync(reason, cancellationToken); }