mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-02-05 11:53:36 -05:00
192 lines
6.7 KiB
C#
192 lines
6.7 KiB
C#
//-----------------------------------------------------------------------
|
|
// <copyright file="StatusWorker.cs" company="aliasvault">
|
|
// Copyright (c) aliasvault. All rights reserved.
|
|
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
|
// </copyright>
|
|
//-----------------------------------------------------------------------
|
|
|
|
namespace AliasVault.WorkerStatus;
|
|
|
|
using AliasVault.WorkerStatus.Database;
|
|
using AliasVault.WorkerStatus.Extensions;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
/// <summary>
|
|
/// StatusWorker class for monitoring and controlling the status of individual worker services through a database.
|
|
/// </summary>
|
|
public class StatusWorker(ILogger<StatusWorker> logger, Func<IWorkerStatusDbContext> createDbContext, GlobalServiceStatus globalServiceStatus) : BackgroundService
|
|
{
|
|
private IWorkerStatusDbContext _dbContext = null!;
|
|
|
|
/// <summary>
|
|
/// Worker service execution method.
|
|
/// </summary>
|
|
/// <param name="stoppingToken">CancellationToken.</param>
|
|
/// <returns>Task.</returns>
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
_dbContext = createDbContext();
|
|
|
|
try
|
|
{
|
|
var statusEntry = await GetServiceStatus();
|
|
|
|
switch (statusEntry.CurrentStatus.ToStatusEnum())
|
|
{
|
|
case Status.Started:
|
|
// Ensure that all workers are running, if not, revert to "Starting" CurrentStatus.
|
|
await HandleStartedStatus(statusEntry);
|
|
break;
|
|
case Status.Starting:
|
|
await HandleStartingStatus(statusEntry);
|
|
break;
|
|
case Status.Stopping:
|
|
await HandleStoppingStatus(statusEntry);
|
|
break;
|
|
case Status.Stopped:
|
|
logger.LogInformation("Service is (soft) stopped.");
|
|
break;
|
|
}
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
// Ignore exception, this is expected when the service is stopped.
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.LogError(e, "StatusWorker exception");
|
|
}
|
|
|
|
await Task.Delay(5000, stoppingToken);
|
|
}
|
|
|
|
// If we reach this point, the service is hard stopping: not in software but on OS level.
|
|
// Mark the service as stopped.
|
|
_dbContext = createDbContext();
|
|
await SetServiceStatus(await GetServiceStatus(), "Stopped");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the Started status.
|
|
/// </summary>
|
|
/// <param name="statusEntry">The WorkerServiceStatus entry.</param>
|
|
/// <returns>Task.</returns>
|
|
private async Task HandleStartedStatus(WorkerServiceStatus statusEntry)
|
|
{
|
|
if (!globalServiceStatus.AreAllWorkersRunning())
|
|
{
|
|
await SetServiceStatus(statusEntry, Status.Starting.ToString());
|
|
logger.LogInformation("Status was set to Started but not all workers are running (yet). Reverting to Starting.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the Starting status.
|
|
/// </summary>
|
|
/// <param name="statusEntry">The WorkerServiceStatus entry.</param>
|
|
/// <returns>Task.</returns>
|
|
private async Task HandleStartingStatus(WorkerServiceStatus statusEntry)
|
|
{
|
|
if (globalServiceStatus.AreAllWorkersRunning())
|
|
{
|
|
await SetServiceStatus(statusEntry, Status.Started.ToString());
|
|
logger.LogInformation("All workers started.");
|
|
}
|
|
else
|
|
{
|
|
logger.LogInformation("Waiting for all workers to start.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the Stopping status.
|
|
/// </summary>
|
|
/// <param name="statusEntry">The WorkerServiceStatus entry.</param>
|
|
/// <returns>Task.</returns>
|
|
private async Task HandleStoppingStatus(WorkerServiceStatus statusEntry)
|
|
{
|
|
if (globalServiceStatus.AreAllWorkersStopped())
|
|
{
|
|
await SetServiceStatus(statusEntry, Status.Stopped.ToString());
|
|
logger.LogInformation("All workers stopped.");
|
|
}
|
|
else
|
|
{
|
|
logger.LogInformation("Waiting for all workers to stop.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current status record of the service from database.
|
|
/// </summary>
|
|
/// <returns>New current status.</returns>
|
|
private async Task<WorkerServiceStatus> GetServiceStatus()
|
|
{
|
|
var entry = GetOrCreateInitialStatusRecord();
|
|
|
|
if (!string.IsNullOrEmpty(entry.DesiredStatus) && entry.CurrentStatus != entry.DesiredStatus)
|
|
{
|
|
entry.CurrentStatus = entry.DesiredStatus.ToStatusEnum() switch
|
|
{
|
|
Status.Started => Status.Starting.ToString(),
|
|
Status.Stopped => Status.Stopping.ToString(),
|
|
_ => entry.CurrentStatus,
|
|
};
|
|
}
|
|
|
|
globalServiceStatus.Status = entry.CurrentStatus;
|
|
globalServiceStatus.CurrentStatus = entry.CurrentStatus;
|
|
|
|
entry.Heartbeat = DateTime.UtcNow;
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
return entry;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the status of the service.
|
|
/// </summary>
|
|
/// <param name="statusEntry">The WorkerServiceStatus entry to update.</param>
|
|
/// <param name="newStatus">The new status.</param>
|
|
/// <returns>New current status.</returns>
|
|
private async Task SetServiceStatus(WorkerServiceStatus statusEntry, string newStatus = "")
|
|
{
|
|
if (!string.IsNullOrEmpty(newStatus) && statusEntry.CurrentStatus != newStatus)
|
|
{
|
|
statusEntry.CurrentStatus = newStatus;
|
|
}
|
|
|
|
var status = statusEntry.CurrentStatus;
|
|
globalServiceStatus.Status = status;
|
|
globalServiceStatus.CurrentStatus = status;
|
|
|
|
statusEntry.Heartbeat = DateTime.UtcNow;
|
|
await _dbContext.SaveChangesAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves status record or creates an initial status record if it does not exist.
|
|
/// </summary>
|
|
private WorkerServiceStatus GetOrCreateInitialStatusRecord()
|
|
{
|
|
var entry = _dbContext.WorkerServiceStatuses.FirstOrDefault(x => x.ServiceName == globalServiceStatus.ServiceName);
|
|
if (entry != null)
|
|
{
|
|
return entry;
|
|
}
|
|
|
|
entry = new WorkerServiceStatus
|
|
{
|
|
ServiceName = globalServiceStatus.ServiceName,
|
|
CurrentStatus = Status.Started.ToString(),
|
|
DesiredStatus = string.Empty,
|
|
};
|
|
_dbContext.WorkerServiceStatuses.Add(entry);
|
|
|
|
return entry;
|
|
}
|
|
}
|