Utopia-Canteen-System/UtopiaCanteen.BackendService/CanteenBackendWorker.cs

188 lines
6.9 KiB
C#

using Microsoft.EntityFrameworkCore;
using UtopiaCanteenSystem.Api;
using UtopiaCanteenSystem.Data;
using UtopiaCanteenSystem.Services;
namespace UtopiaCanteen.BackendService;
public sealed class CanteenBackendWorker : BackgroundService
{
private readonly IConfigService _config;
private readonly DbContextFactory _dbFactory;
private readonly IHostApplicationLifetime _lifetime;
private readonly IConfiguration _configuration;
private CancellationTokenSource? _apiCts;
private Task? _apiTask;
private System.Timers.Timer? _cacheSyncTimer;
private System.Timers.Timer? _orderSyncTimer;
private int _cacheSyncRunning;
private int _orderSyncRunning;
private CanteenBackendServices? _backend;
public CanteenBackendWorker(
IConfigService config,
DbContextFactory dbFactory,
IHostApplicationLifetime lifetime,
IConfiguration configuration)
{
_config = config;
_dbFactory = dbFactory;
_lifetime = lifetime;
_configuration = configuration;
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
Logger.Log(new Exception("UtopiaCanteen backend service starting."), "CanteenBackendWorker");
using (var db = _dbFactory.CreateDbContext())
db.EnsureDatabaseCreated();
var employeeRfidTagSync = new EmployeeRfidTagSyncService(_dbFactory, _config);
var mealMenuCacheSync = new MealMenuCacheSyncService(_dbFactory, _config);
var offlineCacheSync = new OfflineCacheSyncService(employeeRfidTagSync, mealMenuCacheSync);
var employeeLookup = new EmployeeLookupService(_dbFactory, _config);
var menuLookup = new MenuLookupService(_dbFactory);
var mealSessionResolver = new DbMealSessionResolver(_dbFactory);
var productionSync = new SyncService(_dbFactory, _config);
var rfid = new RfidService(_dbFactory, _config, employeeLookup, mealSessionResolver, menuLookup, productionSync);
_backend = new CanteenBackendServices(rfid, offlineCacheSync, productionSync, _config);
_apiCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var listenUrls = _config.GetLocalServerListenUrls();
var backend = _backend;
var cts = _apiCts;
_apiTask = Task.Run(async () =>
{
try
{
await Task.Delay(500, cts.Token).ConfigureAwait(false);
await CanteenBackendHost.RunAsync(backend, listenUrls, cts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Shutdown
}
catch (Exception ex)
{
Logger.Log(ex, "CanteenBackendWorker.ApiHost");
}
}, CancellationToken.None);
_ = RunStartupCacheSyncAsync(cancellationToken);
var cacheMinutes = Math.Max(1, _configuration.GetValue("CacheSyncIntervalMinutes", 15));
if (!string.IsNullOrWhiteSpace(_config.GetHrmsLookupConnectionString()))
{
_cacheSyncTimer = new System.Timers.Timer(TimeSpan.FromMinutes(cacheMinutes).TotalMilliseconds) { AutoReset = true };
_cacheSyncTimer.Elapsed += async (_, _) => await RunCacheSyncSafeAsync().ConfigureAwait(false);
_cacheSyncTimer.Start();
}
var orderMinutes = Math.Max(1, _configuration.GetValue("ProductionSyncIntervalMinutes", 1));
if (_config.GetSyncServiceEnabled() && !string.IsNullOrWhiteSpace(_config.GetMySqlConnectionString()))
{
_orderSyncTimer = new System.Timers.Timer(TimeSpan.FromMinutes(orderMinutes).TotalMilliseconds) { AutoReset = true };
_orderSyncTimer.Elapsed += async (_, _) => await RunOrderSyncSafeAsync().ConfigureAwait(false);
_orderSyncTimer.Start();
}
await base.StartAsync(cancellationToken).ConfigureAwait(false);
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
Logger.Log(new Exception("UtopiaCanteen backend service stopping."), "CanteenBackendWorker");
_cacheSyncTimer?.Stop();
_orderSyncTimer?.Stop();
_apiCts?.Cancel();
if (_apiTask != null)
{
try
{
await _apiTask.WaitAsync(TimeSpan.FromSeconds(10), cancellationToken).ConfigureAwait(false);
}
catch
{
// Best-effort
}
}
_cacheSyncTimer?.Dispose();
_orderSyncTimer?.Dispose();
_apiCts?.Dispose();
await base.StopAsync(cancellationToken).ConfigureAwait(false);
}
protected override Task ExecuteAsync(CancellationToken stoppingToken) =>
Task.Delay(Timeout.Infinite, stoppingToken);
private async Task RunStartupCacheSyncAsync(CancellationToken cancellationToken)
{
if (_backend == null || string.IsNullOrWhiteSpace(_config.GetHrmsLookupConnectionString()))
return;
for (var i = 0; i < 30 && !cancellationToken.IsCancellationRequested; i++)
{
try
{
await _backend.RunCacheSyncExclusiveAsync(async () =>
{
await _backend.OfflineCacheSync.SyncEmployeeAndMenuCacheAsync().ConfigureAwait(false);
return true;
}).ConfigureAwait(false);
return;
}
catch (Exception ex)
{
Logger.Log(ex, "CanteenBackendWorker.RunStartupCacheSyncAsync");
}
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
}
}
private async Task RunCacheSyncSafeAsync()
{
if (_backend == null || Interlocked.Exchange(ref _cacheSyncRunning, 1) == 1)
return;
try
{
await _backend.RunCacheSyncExclusiveAsync(async () =>
{
await _backend.OfflineCacheSync.SyncEmployeeAndMenuCacheAsync().ConfigureAwait(false);
return true;
}).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.Log(ex, "CanteenBackendWorker.RunCacheSyncSafeAsync");
}
finally
{
Interlocked.Exchange(ref _cacheSyncRunning, 0);
}
}
private async Task RunOrderSyncSafeAsync()
{
if (_backend == null || Interlocked.Exchange(ref _orderSyncRunning, 1) == 1)
return;
try
{
await _backend.RunOrderSyncExclusiveAsync(async () =>
{
await _backend.ProductionSync.SyncNowAsync().ConfigureAwait(false);
return true;
}).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.Log(ex, "CanteenBackendWorker.RunOrderSyncSafeAsync");
}
finally
{
Interlocked.Exchange(ref _orderSyncRunning, 0);
}
}
}