115 lines
5.1 KiB
C#
115 lines
5.1 KiB
C#
using PARR.Core.Common.Interfaces;
|
||
using PARR.Core.Services.Snapshots.Interfaces;
|
||
|
||
namespace PARR.SnapshotWorker
|
||
{
|
||
public class Worker : BackgroundService
|
||
{
|
||
private readonly ILogger<Worker> _logger;
|
||
private readonly IIntervalService _intervalService;
|
||
private readonly IServiceProvider _serviceProvider;
|
||
|
||
// Хранилище времени СЛЕДУЮЩЕГО запуска для каждого сервиса
|
||
private readonly Dictionary<string, DateTimeOffset> _nextRunTimers = new();
|
||
|
||
// День последней успешной очистки базы данных
|
||
private int _lastCleanupDay = -1;
|
||
|
||
// Интервал, должен быть меньше минуты, чтобы попадать во все возможные интервалы
|
||
TimeSpan tickInterval = TimeSpan.FromSeconds(25);
|
||
|
||
public Worker(
|
||
ILogger<Worker> logger,
|
||
IIntervalService intervalService,
|
||
IServiceProvider serviceProvider
|
||
)
|
||
{
|
||
_logger = logger;
|
||
_intervalService = intervalService;
|
||
_serviceProvider = serviceProvider;
|
||
}
|
||
|
||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||
{
|
||
_logger.LogInformation("Запуск фонового сервиса создания системных снапшотов");
|
||
|
||
await _intervalService.IntervalInitAsync(async () =>
|
||
{
|
||
var now = DateTimeOffset.UtcNow;
|
||
|
||
using (var scope = _serviceProvider.CreateScope())
|
||
{
|
||
// Собираем абсолютно все зарегистрированные сервисы снапшотов
|
||
var providers = scope.ServiceProvider.GetServices<ISnapshotProvider>();
|
||
|
||
#region Параллельный сбор снапшотов
|
||
|
||
var tasksToRun = new List<Task>();
|
||
|
||
foreach (var provider in providers)
|
||
{
|
||
var providerKey = provider.GetType().FullName!;
|
||
|
||
// Если сервис видим впервые, планируем его первый старт прямо сейчас
|
||
if (!_nextRunTimers.ContainsKey(providerKey))
|
||
{
|
||
_nextRunTimers[providerKey] = now;
|
||
}
|
||
|
||
// Если текущее время добежало до запланированного «будильника»
|
||
if (now >= _nextRunTimers[providerKey])
|
||
{
|
||
// СРАЗУ планируем следующий старт, чтобы сетка времени не съезжала
|
||
// из-за времени выполнения самого метода
|
||
_nextRunTimers[providerKey] = now.Add(provider.Interval);
|
||
|
||
// Добавляем таску в список для параллельного выполнения (без await!)
|
||
tasksToRun.Add(ExecuteSafelyAsync(provider, () => provider.TakeSnapshotAsync(stoppingToken), "Создание снапшота"));
|
||
}
|
||
}
|
||
|
||
// Запускаем все готовые снапшоты ОДНОВРЕМЕННО
|
||
if (tasksToRun.Any())
|
||
{
|
||
await Task.WhenAll(tasksToRun);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Ежедневная очистка БД
|
||
|
||
// Если наступили новые сутки в формате UTC — запускаем ротацию старых данных
|
||
if (now.Day != _lastCleanupDay)
|
||
{
|
||
_logger.LogInformation("Наступили новые сутки. Запуск процесса очистки устаревших снапшотов...");
|
||
|
||
foreach (var provider in providers)
|
||
{
|
||
await ExecuteSafelyAsync(provider, () => provider.CleanUpOldSnapshotsAsync(stoppingToken), "Очистка старых данных");
|
||
}
|
||
|
||
// Запоминаем, что за сегодня очистку уже провели успешно
|
||
_lastCleanupDay = now.Day;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
}, tickInterval);
|
||
}
|
||
|
||
private async Task ExecuteSafelyAsync(ISnapshotProvider provider, Func<Task> action, string operationName)
|
||
{
|
||
try
|
||
{
|
||
await action();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Критическая ошибка во время операции '{Operation}' в сервисе {ProviderName}", operationName, provider.GetType().Name);
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|