feat(localdb): telemetry meters + ISyncStatus
Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
This commit is contained in:
@@ -26,6 +26,8 @@ public sealed class SyncBackgroundService : BackgroundService
|
||||
private readonly ReplicationOptions _options;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILogger<SyncBackgroundService> _logger;
|
||||
private readonly LocalDbMetrics? _metrics;
|
||||
private readonly SyncStatus? _status;
|
||||
|
||||
/// <summary>Test seam: supplies the gRPC channel instead of dialing <see cref="ReplicationOptions.PeerAddress"/>. A channel it returns is owned by the caller and not disposed here.</summary>
|
||||
internal Func<GrpcChannel>? ChannelFactory { get; set; }
|
||||
@@ -33,12 +35,18 @@ public sealed class SyncBackgroundService : BackgroundService
|
||||
/// <summary>Number of connection attempts made this lifetime (one per reconnect loop iteration).</summary>
|
||||
internal int ConnectionAttempts;
|
||||
|
||||
public SyncBackgroundService(ILocalDb db, IOptions<ReplicationOptions> options, ILoggerFactory loggerFactory)
|
||||
// status is taken as the public ISyncStatus (SyncStatus is internal and cannot appear in a
|
||||
// public signature); DI always supplies the concrete SyncStatus this cast recovers.
|
||||
public SyncBackgroundService(
|
||||
ILocalDb db, IOptions<ReplicationOptions> options, ILoggerFactory loggerFactory,
|
||||
LocalDbMetrics? metrics = null, ISyncStatus? status = null)
|
||||
{
|
||||
_db = SyncSessionFactory.RequireSqlite(db, nameof(SyncBackgroundService));
|
||||
_options = options.Value;
|
||||
_loggerFactory = loggerFactory;
|
||||
_logger = loggerFactory.CreateLogger<SyncBackgroundService>();
|
||||
_metrics = metrics;
|
||||
_status = status as SyncStatus;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
@@ -52,7 +60,11 @@ public sealed class SyncBackgroundService : BackgroundService
|
||||
var backoff = InitialBackoff;
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
Interlocked.Increment(ref ConnectionAttempts);
|
||||
var attempt = Interlocked.Increment(ref ConnectionAttempts);
|
||||
_status?.SetConnectionAttempts(attempt);
|
||||
// Every attempt past the first is a reconnect (the initial dial is not).
|
||||
if (attempt > 1)
|
||||
_metrics?.RecordReconnect();
|
||||
var lifetime = System.Diagnostics.Stopwatch.StartNew();
|
||||
Exception? fault = null;
|
||||
GrpcChannel? ownedChannel = null;
|
||||
@@ -75,7 +87,8 @@ public sealed class SyncBackgroundService : BackgroundService
|
||||
// stream this iteration has abandoned.
|
||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
|
||||
var (duplex, readerTask) = GrpcSyncDuplex.Create(call.ResponseStream, call.RequestStream, linkedCts.Token);
|
||||
var session = SyncSessionFactory.Create(_db, _options, _loggerFactory);
|
||||
var session = SyncSessionFactory.Create(_db, _options, _loggerFactory, _metrics, _status);
|
||||
_status?.SetConnected(true);
|
||||
try
|
||||
{
|
||||
await session.RunAsync(duplex, linkedCts.Token);
|
||||
@@ -83,6 +96,7 @@ public sealed class SyncBackgroundService : BackgroundService
|
||||
}
|
||||
finally
|
||||
{
|
||||
_status?.SetConnected(false);
|
||||
linkedCts.Cancel();
|
||||
try
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user