mbproxy: strip historical phase/wave/plan references from source comments
Comments described the *history* of how the code arrived (phase numbers, wave IDs, review IDs, dated TODOs) instead of what it does today. That scaffolding rotted as the codebase evolved. Cleaned 60 source files + .gitignore; behaviour unchanged (387/387 tests still pass). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,8 +24,8 @@ namespace Mbproxy.Proxy;
|
||||
/// log <c>mbproxy.startup.ready</c> with bound/configured counts.</item>
|
||||
/// </list>
|
||||
///
|
||||
/// Phase 06: passes the supervisor dictionary to <see cref="ConfigReconciler.Attach"/>
|
||||
/// after initial startup so hot-reload changes are applied by the reconciler.
|
||||
/// Passes the supervisor dictionary to <see cref="ConfigReconciler.Attach"/> after
|
||||
/// initial startup so hot-reload changes are applied by the reconciler.
|
||||
///
|
||||
/// Stop: cancels all supervisors in parallel with a 5-second hard deadline.
|
||||
/// </summary>
|
||||
@@ -36,30 +36,30 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
private readonly ILogger<ProxyWorker> _logger;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ConfigReconciler _reconciler;
|
||||
// Phase 12 (W1.5) — admin endpoint is no longer IHostedService; ProxyWorker drives its
|
||||
// Admin endpoint is not registered as IHostedService; ProxyWorker drives its
|
||||
// lifecycle directly so the design's "drain THEN stop admin" ordering is honoured.
|
||||
//
|
||||
// Resolved LAZILY (in ExecuteAsync) rather than in the constructor because the DI graph
|
||||
// is circular: AdminEndpointHost → StatusSnapshotBuilder → ProxyWorker. A constructor
|
||||
// GetService<AdminEndpointHost>() during ProxyWorker's own construction returns null
|
||||
// silently. Lazy resolution sidesteps the cycle — by the time ExecuteAsync runs the DI
|
||||
// container is fully built.
|
||||
// Resolved LAZILY (in ExecuteAsync) rather than in the constructor because the DI
|
||||
// graph is circular: AdminEndpointHost → StatusSnapshotBuilder → ProxyWorker. A
|
||||
// constructor GetService<AdminEndpointHost>() during ProxyWorker's own construction
|
||||
// returns null silently. Lazy resolution sidesteps the cycle — by the time
|
||||
// ExecuteAsync runs the DI container is fully built.
|
||||
private readonly IServiceProvider _services;
|
||||
private AdminEndpointHost? _admin;
|
||||
|
||||
// Phase 06: supervisors are now managed jointly by ProxyWorker (initial bootstrap)
|
||||
// and ConfigReconciler (subsequent hot-reload changes). The dictionary is shared
|
||||
// via ConfigReconciler.Attach() after initial startup.
|
||||
// Supervisors are managed jointly by ProxyWorker (initial bootstrap) and
|
||||
// ConfigReconciler (subsequent hot-reload changes). The dictionary is shared via
|
||||
// ConfigReconciler.Attach() after initial startup.
|
||||
//
|
||||
// Phase 12 (W2.3) — ConcurrentDictionary because ConfigReconciler mutates this from
|
||||
// parallel Task.WhenAll continuations (Add/Remove/Restart paths). The outer Apply is
|
||||
// serialised by a semaphore but the inner per-PLC tasks run concurrently. Status-page
|
||||
// reads via IReadOnlyDictionary still work without locking.
|
||||
// ConcurrentDictionary because ConfigReconciler mutates this from parallel
|
||||
// Task.WhenAll continuations (Add/Remove/Restart paths). The outer Apply is
|
||||
// serialised by a semaphore but the inner per-PLC tasks run concurrently.
|
||||
// Status-page reads via IReadOnlyDictionary still work without locking.
|
||||
private readonly ConcurrentDictionary<string, PlcListenerSupervisor> _supervisors =
|
||||
new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Read-only view of the live supervisor dictionary. Consumed by Phase 07's
|
||||
/// Read-only view of the live supervisor dictionary. Consumed by
|
||||
/// <see cref="Admin.StatusSnapshotBuilder"/> to enumerate per-PLC state.
|
||||
/// The caller should read this on the status-page path only (not the hot path).
|
||||
/// </summary>
|
||||
@@ -79,7 +79,7 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
_loggerFactory = loggerFactory;
|
||||
_reconciler = reconciler;
|
||||
_services = services;
|
||||
// Phase 12 (W1.5) — admin endpoint resolved lazily in ExecuteAsync (see field comment).
|
||||
// Admin endpoint resolved lazily in ExecuteAsync (see field comment).
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
@@ -107,11 +107,11 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
continue;
|
||||
}
|
||||
|
||||
// Phase 11 — construct a per-PLC response cache only when at least one
|
||||
// resolved tag opts in (CacheTtlMs > 0). Skipping cache construction for a
|
||||
// PLC with no cacheable tags keeps the no-cache path free of the eviction
|
||||
// timer and the per-call resolution cost, preserving "default behaviour =
|
||||
// Phase 10 unchanged" when no operator has opted any tag in.
|
||||
// Construct a per-PLC response cache only when at least one resolved tag
|
||||
// opts in (CacheTtlMs > 0). Skipping cache construction for a PLC with no
|
||||
// cacheable tags keeps the no-cache path free of the eviction timer and the
|
||||
// per-call resolution cost, preserving the "no caching" default behaviour
|
||||
// when no operator has opted any tag in.
|
||||
var cache = HasAnyCacheableTag(result.Map)
|
||||
? new ResponseCache(opts.Cache.MaxEntriesPerPlc, opts.Cache.EvictionIntervalMs)
|
||||
: null;
|
||||
@@ -144,9 +144,9 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
resilienceOpts.ListenerRecovery,
|
||||
_loggerFactory.CreateLogger($"Mbproxy.Proxy.ListenerRecovery.{plc.Name}"));
|
||||
|
||||
// Phase 10 — give the supervisor a live accessor for ReadCoalescingOptions
|
||||
// so a hot-reload of `Mbproxy.Resilience.ReadCoalescing.Enabled` propagates
|
||||
// to the multiplexer's per-PDU coalescing decision.
|
||||
// Give the supervisor a live accessor for ReadCoalescingOptions so a
|
||||
// hot-reload of `Mbproxy.Resilience.ReadCoalescing.Enabled` propagates to
|
||||
// the multiplexer's per-PDU coalescing decision.
|
||||
Func<ReadCoalescingOptions> coalescingAccessor =
|
||||
() => _options.CurrentValue.Resilience.ReadCoalescing;
|
||||
|
||||
@@ -166,13 +166,13 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
_supervisors[plc.Name] = supervisor;
|
||||
}
|
||||
|
||||
// ── Phase 06: wire reconciler BEFORE starting supervisors ─────────────────
|
||||
// ── Wire reconciler BEFORE starting supervisors ──────────────────────────
|
||||
// Attach hands the reconciler the authoritative supervisor dictionary and the
|
||||
// initial options snapshot. The reconciler won't process OnChange events until
|
||||
// after this call — the brief window between Attach and first supervisor start
|
||||
// is safe because the channel signal only enqueues; apply runs asynchronously.
|
||||
// Phase 12 (W2.1) — also pass the live coalescing accessor so reconciler-built
|
||||
// supervisors (add/restart paths) honour hot-reloaded ReadCoalescing values.
|
||||
// Pass the live coalescing accessor so reconciler-built supervisors
|
||||
// (add/restart paths) honour hot-reloaded ReadCoalescing values.
|
||||
Func<ReadCoalescingOptions> reconcilerCoalescingAccessor =
|
||||
() => _options.CurrentValue.Resilience.ReadCoalescing;
|
||||
_reconciler.Attach(_supervisors, opts, reconcilerCoalescingAccessor);
|
||||
@@ -213,10 +213,10 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
int boundCount = _supervisors.Values.Count(s => s.Snapshot().State == SupervisorState.Bound);
|
||||
LogStartupReady(_logger, boundCount, plcsConfigured);
|
||||
|
||||
// Phase 12 (W1.5) — start the admin endpoint AFTER listeners are bound so the
|
||||
// status page can never observe the service in a "no PLCs configured yet" state.
|
||||
// The admin endpoint is no longer registered as IHostedService (the host's reverse
|
||||
// stop order would tear it down BEFORE drain). ProxyWorker drives both ends.
|
||||
// Start the admin endpoint AFTER listeners are bound so the status page can
|
||||
// never observe the service in a "no PLCs configured yet" state. The admin
|
||||
// endpoint is not registered as IHostedService (the host's reverse stop order
|
||||
// would tear it down BEFORE drain) — ProxyWorker drives both ends.
|
||||
//
|
||||
// Resolution happens here, not in the constructor — the DI graph is circular
|
||||
// (admin → StatusSnapshotBuilder → ProxyWorker) and a constructor-time lookup
|
||||
@@ -235,10 +235,9 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
}
|
||||
else
|
||||
{
|
||||
// Phase 12 (W4 / Nm6) — surface the absence. The previous IHostedService
|
||||
// registration would have hard-errored in DI if AddMbproxyAdmin() was missing
|
||||
// from Program.cs; the W1.5 lazy lookup returns null silently. A single warning
|
||||
// makes a botched composition observable without blocking startup.
|
||||
// Surface the absence. The lazy lookup returns null silently if
|
||||
// AddMbproxyAdmin() is missing from Program.cs; a single warning makes a
|
||||
// botched composition observable without blocking startup.
|
||||
_logger.LogWarning(
|
||||
"Admin endpoint not registered (AddMbproxyAdmin() missing from composition). " +
|
||||
"Status page will be unavailable; service continues without it.");
|
||||
@@ -250,8 +249,7 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase 12 (W1.5) — graceful shutdown sequence (replaces the deleted
|
||||
/// <c>ShutdownCoordinator</c>):
|
||||
/// Graceful shutdown sequence:
|
||||
/// <list type="number">
|
||||
/// <item>Cancel <see cref="ExecuteAsync"/> via <c>base.StopAsync</c>.</item>
|
||||
/// <item><b>Snapshot</b> per-PLC in-flight counts BEFORE stopping supervisors —
|
||||
@@ -263,10 +261,7 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
/// stop is the actual drain — it cancels the listener, which exits its
|
||||
/// accept loop, which disposes the multiplexer, which cascades all attached
|
||||
/// pipes. There is no separate "drain in-flight" phase because there is
|
||||
/// nothing to drain that wouldn't be killed by the supervisor stop itself
|
||||
/// (the original Phase-08 ShutdownCoordinator's drain loop had this same
|
||||
/// shape and was structurally always-zero — call out from
|
||||
/// codereviews/2026-05-14/ReReviewAfterRemediation.md NC1).</item>
|
||||
/// nothing to drain that wouldn't be killed by the supervisor stop itself.</item>
|
||||
/// <item>Stop the admin endpoint LAST so the status page survives the supervisor
|
||||
/// stop phase and operators can observe the live state right up to shutdown.</item>
|
||||
/// <item>Dispose every supervisor to release sockets, channels, and watchdog timers.</item>
|
||||
@@ -277,16 +272,15 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
/// </summary>
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Phase 12 (W5 / m2) — snapshot in-flight BEFORE base.StopAsync so the field
|
||||
// matches its name: "the count at the moment the host signalled stop", not "the
|
||||
// count at the moment we got around to computing it." `base.StopAsync` cancels the
|
||||
// ExecuteAsync stoppingToken; in the milliseconds before it returns, in-flight
|
||||
// requests whose responses arrive will be removed from _correlation and the
|
||||
// watchdog can clear stale entries — the count would otherwise drift downward.
|
||||
// Snapshot in-flight BEFORE base.StopAsync so the field matches its name: "the
|
||||
// count at the moment the host signalled stop", not "the count at the moment we
|
||||
// got around to computing it." `base.StopAsync` cancels the ExecuteAsync
|
||||
// stoppingToken; in the milliseconds before it returns, in-flight requests
|
||||
// whose responses arrive will be removed from _correlation and the watchdog can
|
||||
// clear stale entries — the count would otherwise drift downward.
|
||||
//
|
||||
// Phase 12 (W4 / NC1) — must run BEFORE supervisor stop too: after
|
||||
// supervisor.StopAsync, multiplexers are disposed and CountInFlight returns 0
|
||||
// unconditionally (the original ShutdownCoordinator had the same defect).
|
||||
// Must run BEFORE supervisor stop too: after supervisor.StopAsync, multiplexers
|
||||
// are disposed and CountInFlight returns 0 unconditionally.
|
||||
int inFlightAtCancel = CountInFlight();
|
||||
|
||||
// Cancel ExecuteAsync first.
|
||||
@@ -294,9 +288,9 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
// Phase 12 (W2.20) — supervisor stop deadline read from the live config so a
|
||||
// hot-reloaded GracefulShutdownTimeoutMs is honoured. Supervisor stop is the
|
||||
// drain: cancelling the supervisor cancels the listener, which exits accept, which
|
||||
// Supervisor stop deadline read from the live config so a hot-reloaded
|
||||
// GracefulShutdownTimeoutMs is honoured. Supervisor stop is the drain:
|
||||
// cancelling the supervisor cancels the listener, which exits accept, which
|
||||
// disposes the multiplexer, which cascades all attached pipes.
|
||||
int gracefulMs = _options.CurrentValue.Connection.GracefulShutdownTimeoutMs;
|
||||
|
||||
@@ -352,11 +346,11 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
// ── Logging ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Phase 11 — returns <c>true</c> when at least one BcdTag in the resolved map has a
|
||||
/// positive <see cref="BcdTag.CacheTtlMs"/>. A PLC with no cacheable tags skips the
|
||||
/// Returns <c>true</c> when at least one BcdTag in the resolved map has a positive
|
||||
/// <see cref="BcdTag.CacheTtlMs"/>. A PLC with no cacheable tags skips the
|
||||
/// <see cref="Mbproxy.Proxy.Cache.ResponseCache"/> entirely (no eviction timer, no
|
||||
/// per-call cache resolution cost), so the default-OFF deployment is byte-identical
|
||||
/// to a Phase-10 deployment.
|
||||
/// per-call cache resolution cost), so the default-OFF deployment runs the
|
||||
/// no-cache code path.
|
||||
/// </summary>
|
||||
private static bool HasAnyCacheableTag(BcdTagMap map)
|
||||
{
|
||||
@@ -375,7 +369,6 @@ internal sealed partial class ProxyWorker : BackgroundService
|
||||
Message = "Failed to bind listener: Plc={Plc} Port={Port} Reason={Reason}")]
|
||||
private static partial void LogBindFailed(ILogger logger, string plc, int port, string reason);
|
||||
|
||||
// Phase 12 (W1.5) — moved here from the deleted ShutdownCoordinator.
|
||||
[LoggerMessage(EventId = 80, EventName = "mbproxy.shutdown.complete",
|
||||
Level = LogLevel.Information,
|
||||
Message = "Graceful shutdown complete: InFlightAtCancel={InFlightAtCancel} ElapsedMs={ElapsedMs}")]
|
||||
|
||||
Reference in New Issue
Block a user