feat: consolidate legacy parity migration baseline

This commit is contained in:
2026-07-20 22:06:12 +09:00
parent 5eb4054120
commit 4a54977341
66 changed files with 5391 additions and 460 deletions

View File

@@ -0,0 +1,191 @@
using MBN_STOCK_WEBVIEW.Infrastructure;
using MBN_STOCK_WEBVIEW.Infrastructure.Diagnostics;
using MMoneyCoderSharp.Data;
namespace MBN_STOCK_WEBVIEW.LegacyParityApp;
public sealed partial class MainWindow
{
private static readonly TimeSpan DatabaseHealthPollingInterval =
TimeSpan.FromSeconds(10);
private readonly SemaphoreSlim _databaseActivityGate = new(1, 1);
private readonly INativeOperatorLogWriter _databaseHealthLogWriter =
new NativeOperatorLogWriter();
private readonly NativeOperatorLogTransitionTracker _databaseHealthTransitions =
new();
private int _databaseActivityPriorityWaiterCount;
private CancellationTokenSource? _databaseHealthCheckCancellation;
private Task? _databaseHealthMonitorTask;
private void StartDatabaseHealthMonitor()
{
_databaseHealthMonitorTask ??=
MonitorDatabaseHealthAsync(_lifetimeCancellation.Token);
}
private async Task MonitorDatabaseHealthAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
// WinForms timerAliveChecker first fired after its 10-second interval.
await Task.Delay(DatabaseHealthPollingInterval, cancellationToken)
.ConfigureAwait(false);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
return;
}
try
{
await CheckAndLogDatabaseHealthAsync(cancellationToken)
.ConfigureAwait(false);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
return;
}
}
}
private async Task CheckAndLogDatabaseHealthAsync(CancellationToken cancellationToken)
{
if (Volatile.Read(ref _playoutBusy) != 0 ||
Volatile.Read(ref _databaseActivityPriorityWaiterCount) != 0 ||
!await _databaseActivityGate.WaitAsync(0, cancellationToken)
.ConfigureAwait(false))
{
return;
}
CancellationTokenSource? healthCancellation = null;
try
{
if (Volatile.Read(ref _playoutBusy) != 0)
{
return;
}
if (_databaseRuntime is null)
{
ObserveDatabaseHealthState(
DataSourceKind.Oracle,
DatabaseHealthState.NotConfigured);
ObserveDatabaseHealthState(
DataSourceKind.MariaDb,
DatabaseHealthState.NotConfigured);
return;
}
healthCancellation = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken);
Interlocked.Exchange(
ref _databaseHealthCheckCancellation,
healthCancellation);
// Close the race where an operator command starts after this monitor
// acquired the activity gate but before the provider probes begin.
if (Volatile.Read(ref _playoutBusy) != 0 ||
Volatile.Read(ref _databaseActivityPriorityWaiterCount) != 0)
{
healthCancellation.Cancel();
return;
}
var statuses = await _databaseRuntime.HealthService
.CheckAllAsync(healthCancellation.Token)
.ConfigureAwait(false);
foreach (var status in statuses)
{
ObserveDatabaseHealthState(status.Source, status.State);
}
}
catch (OperationCanceledException) when (
cancellationToken.IsCancellationRequested ||
healthCancellation?.IsCancellationRequested == true)
{
// Shutdown and operator work both have priority over diagnostics.
}
catch
{
ObserveDatabaseHealthState(
DataSourceKind.Oracle,
DatabaseHealthState.Unhealthy);
ObserveDatabaseHealthState(
DataSourceKind.MariaDb,
DatabaseHealthState.Unhealthy);
}
finally
{
if (healthCancellation is not null)
{
Interlocked.CompareExchange(
ref _databaseHealthCheckCancellation,
null,
healthCancellation);
healthCancellation.Dispose();
}
_databaseActivityGate.Release();
}
}
private async Task AcquirePriorityDatabaseActivityAsync(
CancellationToken cancellationToken)
{
// Signal priority before inspecting the active probe. This closes both
// sides of the acquisition/publish race:
// * a probe that has not acquired the gate sees the waiter and yields;
// * a probe that owns the gate either sees the waiter after publishing
// its CTS or is cancelled through that already-published CTS.
Interlocked.Increment(ref _databaseActivityPriorityWaiterCount);
try
{
CancelActiveDatabaseHealthCheck();
await _databaseActivityGate.WaitAsync(cancellationToken)
.ConfigureAwait(false);
}
finally
{
Interlocked.Decrement(ref _databaseActivityPriorityWaiterCount);
}
}
private void CancelActiveDatabaseHealthCheck()
{
var healthCancellation = Volatile.Read(ref _databaseHealthCheckCancellation);
try
{
healthCancellation?.Cancel();
}
catch (ObjectDisposedException)
{
// Probe completion won the race after the reference was read.
}
}
private void ObserveDatabaseHealthState(
DataSourceKind source,
DatabaseHealthState state)
{
if (!_databaseHealthTransitions.TryObserveDatabaseState(
source,
state,
out var record))
{
return;
}
try
{
_ = _databaseHealthLogWriter.TryWrite(record);
}
catch
{
// Logging is best effort and cannot affect DB or playout work.
}
}
}