feat(transport): observability for the import-session cap (register row 53)
Nothing counted open import sessions and nothing logged a rejection at MaxConcurrentImportSessions (default 8), so the register-row-43 session leak could only be inferred from rotating test failures. - Gauge scadabridge.central.transport.import_sessions.open on the existing ZB.MOM.WW.ScadaBridge meter — already in SiteServiceRegistration.ObservedMeters (the ZbTelemetryOptions.Meters allowlist), so no telemetry config change is needed and the series is not silently dropped. Provider-backed, mirroring the StoreAndForward queue-depth gauge: registered by BundleSessionEvictionService .StartAsync (Transport's only lifecycle seam) and identity-checked-cleared on StopAsync so a late stop cannot clobber a newer registrant. - IBundleSessionStore.OpenSessionCount counts only UNEXPIRED sessions. The raw dictionary count would report the node as pinned at the cap for up to a one-minute sweep interval after a burst had drained — the exact false positive the instrument exists to rule out. - BundleSessionStore.Open logs Warning with open count, cap and TTL when it rejects. Fires only on a real cap hit: a store full of expired sessions is reclaimed first and logs nothing. - Docs: docs/components/Transport.md + docs/requirements/Component-Transport.md. Tests: Transport 173 passed (+6 new — Warning content, two negative controls, unexpired-only counting, gauge register/clear, no-clobber-on-late-stop).
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Observability;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Transport.Import;
|
||||
|
||||
@@ -21,6 +22,15 @@ internal sealed class BundleSessionEvictionService : BackgroundService
|
||||
private readonly IBundleSessionStore _sessionStore;
|
||||
private readonly ILogger<BundleSessionEvictionService> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Register row 53: the open-session gauge callback, held in a field so
|
||||
/// <see cref="StopAsync"/> can pass the SAME delegate reference to
|
||||
/// <see cref="ScadaBridgeTelemetry.ClearImportSessionCountProvider"/> — the clear is
|
||||
/// an identity-checked compare-and-clear, so a stale stop cannot unregister a newer
|
||||
/// instance's provider. Mirrors StoreAndForwardService's queue-depth registration.
|
||||
/// </summary>
|
||||
private readonly Func<long> _openSessionCountProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="BundleSessionEvictionService"/>.
|
||||
/// </summary>
|
||||
@@ -32,6 +42,35 @@ internal sealed class BundleSessionEvictionService : BackgroundService
|
||||
{
|
||||
_sessionStore = sessionStore ?? throw new ArgumentNullException(nameof(sessionStore));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_openSessionCountProvider = () => _sessionStore.OpenSessionCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register row 53: registers the open-import-sessions gauge provider before the
|
||||
/// sweep loop starts. This service is the Transport component's only lifecycle seam
|
||||
/// (the session store itself is a plain singleton), and it is registered by
|
||||
/// <c>AddTransport</c>, which only the Central branch of the host calls — so the
|
||||
/// gauge reports 0 on a site node, where there are no import sessions to count.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Host startup cancellation token.</param>
|
||||
/// <returns>A task that completes when the base <see cref="BackgroundService"/> has started.</returns>
|
||||
public override Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
ScadaBridgeTelemetry.SetImportSessionCountProvider(_openSessionCountProvider);
|
||||
return base.StartAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deregisters this instance's gauge provider on graceful stop so the gauge falls
|
||||
/// back to 0 rather than reading through a dead store. The clear is identity-checked,
|
||||
/// so a late stop cannot clobber a successor that already re-registered.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Host shutdown cancellation token.</param>
|
||||
/// <returns>A task that completes when the base <see cref="BackgroundService"/> has stopped.</returns>
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
ScadaBridgeTelemetry.ClearImportSessionCountProvider(_openSessionCountProvider);
|
||||
await base.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in New Issue
Block a user