64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
93 lines
4.6 KiB
C#
93 lines
4.6 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Core.Stability;
|
|
|
|
/// <summary>
|
|
/// Tier C opt-in periodic-recycle driver per <c>docs/v2/plan.md</c> 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 <see cref="IDriverSupervisor"/> to recycle the Host.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
///
|
|
/// <para>Keeps no background thread of its own — callers invoke <see cref="TickAsync"/> 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.</para>
|
|
/// </remarks>
|
|
public sealed class ScheduledRecycleScheduler
|
|
{
|
|
private readonly TimeSpan _recycleInterval;
|
|
private readonly IDriverSupervisor _supervisor;
|
|
private readonly ILogger<ScheduledRecycleScheduler> _logger;
|
|
private DateTime _nextRecycleUtc;
|
|
|
|
/// <summary>
|
|
/// Construct the scheduler for a Tier C driver. Throws if <paramref name="tier"/> isn't C.
|
|
/// </summary>
|
|
/// <param name="tier">Driver tier; must be <see cref="DriverTier.C"/>.</param>
|
|
/// <param name="recycleInterval">Interval between recycles (e.g. 7 days).</param>
|
|
/// <param name="startUtc">Anchor time; next recycle fires at <paramref name="startUtc"/> + <paramref name="recycleInterval"/>.</param>
|
|
/// <param name="supervisor">Supervisor that performs the actual recycle.</param>
|
|
/// <param name="logger">Diagnostic sink.</param>
|
|
public ScheduledRecycleScheduler(
|
|
DriverTier tier,
|
|
TimeSpan recycleInterval,
|
|
DateTime startUtc,
|
|
IDriverSupervisor supervisor,
|
|
ILogger<ScheduledRecycleScheduler> 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;
|
|
}
|
|
|
|
/// <summary>Next scheduled recycle UTC. Advances by <see cref="RecycleInterval"/> on each fire.</summary>
|
|
public DateTime NextRecycleUtc => _nextRecycleUtc;
|
|
|
|
/// <summary>Recycle interval this scheduler was constructed with.</summary>
|
|
public TimeSpan RecycleInterval => _recycleInterval;
|
|
|
|
/// <summary>
|
|
/// Tick the scheduler forward. If <paramref name="utcNow"/> is past
|
|
/// <see cref="NextRecycleUtc"/>, requests a recycle from the supervisor and advances
|
|
/// <see cref="NextRecycleUtc"/> by exactly one interval. Returns true when a recycle fired.
|
|
/// </summary>
|
|
/// <param name="utcNow">The current UTC time.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <returns>True if a recycle was triggered; false otherwise.</returns>
|
|
public async Task<bool> 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;
|
|
}
|
|
|
|
/// <summary>Request an immediate recycle outside the schedule (e.g. MemoryRecycle hard-breach escalation).</summary>
|
|
/// <param name="reason">The reason for requesting an immediate recycle.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <returns>A task representing the asynchronous recycle operation.</returns>
|
|
public Task RequestRecycleNowAsync(string reason, CancellationToken cancellationToken) =>
|
|
_supervisor.RecycleAsync(reason, cancellationToken);
|
|
}
|