shoc-backend/Api.SeaHavenIndustries/HostedServices/WorkOrderReconciliationHostedService.cs

114 lines
4.4 KiB
C#
Raw Normal View History

using Api.SeaHavenIndustries.Observability;
using Microsoft.Extensions.Options;
using SeaHaven.Services.Configuration;
using SeaHaven.Services.Interfaces;
using Sentry;
namespace Api.SeaHavenIndustries.HostedServices
{
public sealed class WorkOrderReconciliationHostedService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IOptionsMonitor<WorkOrderReconciliationOptions> _options;
private readonly TimeProvider _timeProvider;
private readonly ILogger<WorkOrderReconciliationHostedService> _logger;
private readonly IHub _sentryHub;
public WorkOrderReconciliationHostedService(
IServiceScopeFactory scopeFactory,
IOptionsMonitor<WorkOrderReconciliationOptions> options,
TimeProvider timeProvider,
ILogger<WorkOrderReconciliationHostedService> logger,
IHub sentryHub)
{
_scopeFactory = scopeFactory;
_options = options;
_timeProvider = timeProvider;
_logger = logger;
_sentryHub = sentryHub;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var nextScheduledAt = _timeProvider.GetUtcNow();
if (_options.CurrentValue.Enabled && _options.CurrentValue.RunOnStartup)
{
await TriggerAsync("startup", stoppingToken);
nextScheduledAt = _timeProvider.GetUtcNow().AddMinutes(_options.CurrentValue.ScheduleMinutes);
}
while (!stoppingToken.IsCancellationRequested)
{
var options = _options.CurrentValue;
if (options.Enabled)
{
var now = _timeProvider.GetUtcNow();
if (now >= nextScheduledAt)
{
await TriggerAsync("schedule", stoppingToken);
nextScheduledAt = now.AddMinutes(options.ScheduleMinutes);
}
await RunPendingAsync(stoppingToken);
}
await Task.Delay(
TimeSpan.FromSeconds(Math.Clamp(options.PollSeconds, 1, 300)),
_timeProvider,
stoppingToken);
}
}
private async Task TriggerAsync(string reason, CancellationToken cancellationToken)
{
using var transaction = SentryObservability.BeginBackgroundTransaction(
_sentryHub,
"workorders.reconciliation-trigger",
$"{nameof(WorkOrderReconciliationHostedService)}.{nameof(TriggerAsync)}",
description: reason);
try
{
await using var scope = _scopeFactory.CreateAsyncScope();
var runner = scope.ServiceProvider.GetRequiredService<IWorkOrderReconciliationRunner>();
await runner.TriggerAsync(reason, cancellationToken);
transaction.FinishOk();
}
catch (OperationCanceledException)
{
transaction.FinishCancelled();
throw;
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogError(ex, "Could not queue procurement reconciliation.");
transaction.FinishError(ex);
}
}
private async Task RunPendingAsync(CancellationToken cancellationToken)
{
using var transaction = SentryObservability.BeginBackgroundTransaction(
_sentryHub,
"workorders.reconciliation-run-pending",
$"{nameof(WorkOrderReconciliationHostedService)}.{nameof(RunPendingAsync)}");
try
{
await using var scope = _scopeFactory.CreateAsyncScope();
var runner = scope.ServiceProvider.GetRequiredService<IWorkOrderReconciliationRunner>();
await runner.RunPendingAsync(cancellationToken);
transaction.FinishOk();
}
catch (OperationCanceledException)
{
transaction.FinishCancelled();
throw;
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogError(ex, "Could not execute procurement reconciliation.");
transaction.FinishError(ex);
}
}
}
}