Skip to content

Commit

Permalink
Fix a few bugs caused by the internal refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Fleny113 committed May 15, 2024
1 parent c4c044d commit 96a3e25
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,28 @@ internal sealed class RestartApplicationEndpoint : IEndpoint
[HttpMap(HttpMapMethod.Patch, "/{name}/restart")]
public static Results<NoContent, NotFound, StatusCodeHttpResult> Handle(
[FromServices] ProcessManagerService processManager,
[FromServices] ProcessStatisticsService processStatisticsService,
[FromServices] HexusConfiguration configuration,
[FromRoute] string name,
[FromQuery] bool forceStop = false)
{
if (!configuration.Applications.TryGetValue(name, out var application))
return TypedResults.NotFound();

processManager.StopApplication(application, forceStop);
processStatisticsService.StopTrackingApplicationUsage(application);

if (!processManager.StopApplication(application, forceStop))
{
return TypedResults.StatusCode((int)HttpStatusCode.InternalServerError);
}

processStatisticsService.TrackApplicationUsages(application);

if (!processManager.StartApplication(application))
{
processStatisticsService.StopTrackingApplicationUsage(application);
return TypedResults.StatusCode((int)HttpStatusCode.InternalServerError);
}

return TypedResults.NoContent();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal sealed class StartApplicationEndpoint : IEndpoint
public static Results<NoContent, NotFound, ValidationProblem, StatusCodeHttpResult> Handle(
[FromRoute] string name,
[FromServices] ProcessManagerService processManager,
[FromServices] ProcessStatisticsService processStatisticsService,
[FromServices] HexusConfiguration configuration)
{
if (!configuration.Applications.TryGetValue(name, out var application))
Expand All @@ -22,8 +23,13 @@ public static Results<NoContent, NotFound, ValidationProblem, StatusCodeHttpResu
if (processManager.IsApplicationRunning(application, out _))
return TypedResults.ValidationProblem(ErrorResponses.ApplicationAlreadyRunning);

processStatisticsService.TrackApplicationUsages(application);

if (!processManager.StartApplication(application))
{
processStatisticsService.StopTrackingApplicationUsage(application);
return TypedResults.StatusCode((int)HttpStatusCode.InternalServerError);
}

return TypedResults.NoContent();
}
Expand Down
19 changes: 13 additions & 6 deletions Hexus.Daemon/Services/ProcessLogsService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Hexus.Daemon.Configuration;
using Hexus.Daemon.Contracts;
using System.Collections.Concurrent;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
Expand All @@ -13,11 +12,11 @@ internal partial class ProcessLogsService(ILogger<ProcessLogsService> logger)
public const string ApplicationStartedLog = "-- Application started --";
public static readonly CompositeFormat ApplicationStoppedLog = CompositeFormat.Parse("-- Application stopped [Exit code: {0}] --");

private readonly ConcurrentDictionary<HexusApplication, LogController> _logControllers = new();
private readonly Dictionary<string, LogController> _logControllers = [];

public void ProcessApplicationLog(HexusApplication application, LogType logType, string message)
{
if (!_logControllers.TryGetValue(application, out var logController))
if (!_logControllers.TryGetValue(application.Name, out var logController))
{
LogUnableToGetLogController(logger, application.Name);
return;
Expand Down Expand Up @@ -47,7 +46,7 @@ public void ProcessApplicationLog(HexusApplication application, LogType logType,
public async IAsyncEnumerable<ApplicationLog> GetLogs(HexusApplication application, int lines, bool streaming,
bool currentExecution, DateTimeOffset? before, DateTimeOffset? after, [EnumeratorCancellation] CancellationToken ct)
{
if (!_logControllers.TryGetValue(application, out var logController))
if (!_logControllers.TryGetValue(application.Name, out var logController))
{
LogUnableToGetLogController(logger, application.Name);
yield break;
Expand Down Expand Up @@ -94,12 +93,14 @@ public async IAsyncEnumerable<ApplicationLog> GetLogs(HexusApplication applicati

public void RegisterApplication(HexusApplication application)
{
_logControllers[application] = new LogController();
LogRegisteringApplication(logger, application.Name);
_logControllers[application.Name] = new LogController();
}

public bool UnregisterApplication(HexusApplication application)
{
return _logControllers.TryRemove(application, out _);
LogUnRegisteringApplication(logger, application.Name);
return _logControllers.Remove(application.Name, out _);
}

public void DeleteApplication(HexusApplication application)
Expand Down Expand Up @@ -254,6 +255,12 @@ private static bool IsLogDateInRange(DateTimeOffset time, DateTimeOffset? before
[LoggerMessage(LogLevel.Warning, "Unable to get log controller for application \"{Name}\"")]
private static partial void LogUnableToGetLogController(ILogger logger, string name);

[LoggerMessage(LogLevel.Debug, "Application \"{Name}\" is being registered in the process logs service ")]
private static partial void LogRegisteringApplication(ILogger logger, string name);

[LoggerMessage(LogLevel.Debug, "Application \"{Name}\" is being unregistered in the process logs service ")]
private static partial void LogUnRegisteringApplication(ILogger logger, string name);

[LoggerMessage(LogLevel.Trace, "Application \"{Name}\" says: '{OutputData}'")]
private static partial void LogApplicationOutput(ILogger logger, string name, string outputData);

Expand Down
49 changes: 28 additions & 21 deletions Hexus.Daemon/Services/ProcessManagerService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Hexus.Daemon.Configuration;
using Hexus.Daemon.Contracts;
using Hexus.Daemon.Interop;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
Expand All @@ -13,8 +12,8 @@ internal partial class ProcessManagerService(
HexusConfigurationManager configManager,
ProcessLogsService processLogsService)
{
private readonly ConcurrentDictionary<Process, HexusApplication> _processToApplicationMap = new();
private readonly ConcurrentDictionary<HexusApplication, Process> _applicationToProcessMap = new();
private readonly Dictionary<Process, HexusApplication> _processToApplicationMap = [];
private readonly Dictionary<string, Process> _applicationToProcessMap = [];

public bool StartApplication(HexusApplication application)
{
Expand Down Expand Up @@ -44,11 +43,14 @@ public bool StartApplication(HexusApplication application)
return false;

_processToApplicationMap[process] = application;
_applicationToProcessMap[application] = process;

processLogsService.ProcessApplicationLog(application, LogType.System, ProcessLogsService.ApplicationStartedLog);
_applicationToProcessMap[application.Name] = process;

// Enable the emitting of events and the reading of the STDOUT and STDERR
process.EnableRaisingEvents = true;
process.BeginOutputReadLine();
process.BeginErrorReadLine();

processLogsService.ProcessApplicationLog(application, LogType.System, ProcessLogsService.ApplicationStartedLog);

// Register callbacks
process.OutputDataReceived += HandleStdOutLogs;
Expand All @@ -57,10 +59,6 @@ public bool StartApplication(HexusApplication application)
process.Exited += AcknowledgeProcessExit;
process.Exited += HandleProcessRestart;

// Enable the emitting of events and the reading of the STDOUT and STDERR
process.BeginOutputReadLine();
process.BeginErrorReadLine();

application.Status = HexusApplicationStatus.Running;
configManager.SaveConfiguration();

Expand All @@ -74,13 +72,14 @@ public bool StopApplication(HexusApplication application, bool forceStop = false

// Remove the restart event handler, or else it will restart the process as soon as it stops
process.Exited -= HandleProcessRestart;
process.Exited += ClearApplicationStateOnExit;

StopProcess(process, forceStop);

application.Status = HexusApplicationStatus.Exited;

_processToApplicationMap.TryRemove(process, out _);
_applicationToProcessMap.TryRemove(application, out _);
_processToApplicationMap.Remove(process, out _);
_applicationToProcessMap.Remove(application.Name, out _);

// If the daemon is shutting down we don't want to save, or else when the daemon is booted up again, all the applications will be marked as stopped
if (!HexusLifecycle.IsDaemonStopped)
Expand All @@ -96,7 +95,7 @@ public void StopApplications()

public bool IsApplicationRunning(HexusApplication application, [NotNullWhen(true)] out Process? process)
{
if (!_applicationToProcessMap.TryGetValue(application, out process))
if (!_applicationToProcessMap.TryGetValue(application.Name, out process))
{
return false;
}
Expand All @@ -108,8 +107,8 @@ public bool IsApplicationRunning(HexusApplication application, [NotNullWhen(true
catch (InvalidOperationException exception) when (exception.Message == "No process is associated with this object.")
{
// The process does not exist. so it isn't running
_applicationToProcessMap.TryRemove(application, out _);
_processToApplicationMap.TryRemove(process, out _);
_applicationToProcessMap.Remove(application.Name, out _);
_processToApplicationMap.Remove(process, out _);

return false;
}
Expand Down Expand Up @@ -201,7 +200,7 @@ private void HandleStdErrLogs(object? sender, DataReceivedEventArgs e)
private static readonly TimeSpan ResetTimeWindow = TimeSpan.FromSeconds(30);
private const int MaxRestarts = 10;

private readonly ConcurrentDictionary<string, (int Restarts, CancellationTokenSource? CancellationTokenSource)> _consequentialRestarts = new();
private readonly Dictionary<string, (int Restarts, CancellationTokenSource? CancellationTokenSource)> _consequentialRestarts = [];

private void AcknowledgeProcessExit(object? sender, EventArgs e)
{
Expand All @@ -217,6 +216,15 @@ private void AcknowledgeProcessExit(object? sender, EventArgs e)
LogAcknowledgeProcessExit(logger, application.Name, exitCode);
}

private void ClearApplicationStateOnExit(object? sender, EventArgs e)
{
if (sender is not Process process || !_processToApplicationMap.TryGetValue(process, out var application))
return;

_processToApplicationMap.Remove(process, out _);
_applicationToProcessMap.Remove(application.Name, out _);
}

private void HandleProcessRestart(object? sender, EventArgs e)
{
if (sender is not Process process || !_processToApplicationMap.TryGetValue(process, out var application))
Expand All @@ -229,20 +237,19 @@ private void HandleProcessRestart(object? sender, EventArgs e)
status.CancellationTokenSource = new CancellationTokenSource(ResetTimeWindow);

_consequentialRestarts[application.Name] = status;
_processToApplicationMap.Remove(process, out _);
_applicationToProcessMap.Remove(application.Name, out _);

if (status.Restarts > MaxRestarts)
{
LogCrashedApplication(logger, application.Name, status.Restarts, ResetTimeWindow.TotalSeconds);

status.CancellationTokenSource.Dispose();
_consequentialRestarts.TryRemove(application.Name, out _);
_consequentialRestarts.Remove(application.Name, out _);

application.Status = HexusApplicationStatus.Crashed;
configManager.SaveConfiguration();

_processToApplicationMap.TryRemove(process, out _);
_applicationToProcessMap.TryRemove(application, out _);

return;
}

Expand All @@ -263,7 +270,7 @@ private void ResetConsequentialRestarts(object? state)
if (state is not string name)
return;

_consequentialRestarts.TryRemove(name, out var status);
_consequentialRestarts.Remove(name, out var status);
status.CancellationTokenSource?.Dispose();

LogConsequentialRestartsStop(logger, status.Restarts, name);
Expand Down
17 changes: 9 additions & 8 deletions Hexus.Daemon/Services/ProcessStatisticsService.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
using Hexus.Daemon.Configuration;
using Hexus.Daemon.Extensions;
using Hexus.Daemon.Interop;
using System.Collections.Concurrent;
using System.Diagnostics;

namespace Hexus.Daemon.Services;

internal sealed class ProcessStatisticsService(ProcessManagerService processManagerService)
internal sealed class ProcessStatisticsService(ProcessManagerService processManagerService, HexusConfigurationManager configurationManager)
{
private readonly ConcurrentDictionary<HexusApplication, ApplicationCpuStatistics> _cpuStatisticsMap = new();
private readonly Dictionary<string, ApplicationCpuStatistics> _cpuStatisticsMap = [];

public ApplicationStatistics GetApplicationStats(HexusApplication application)
{
if (!processManagerService.IsApplicationRunning(application, out var process) ||
!_cpuStatisticsMap.TryGetValue(application, out var cpuStatistics))
!_cpuStatisticsMap.TryGetValue(application.Name, out var cpuStatistics))
{
return new ApplicationStatistics(TimeSpan.Zero, 0, 0, 0);
}
Expand All @@ -28,12 +27,12 @@ public ApplicationStatistics GetApplicationStats(HexusApplication application)

public void TrackApplicationUsages(HexusApplication application)
{
_cpuStatisticsMap[application] = new ApplicationCpuStatistics();
_cpuStatisticsMap[application.Name] = new ApplicationCpuStatistics();
}

public bool StopTrackingApplicationUsage(HexusApplication application)
{
return _cpuStatisticsMap.TryRemove(application, out _);
return _cpuStatisticsMap.Remove(application.Name, out _);
}

internal void RefreshCpuUsage()
Expand All @@ -45,15 +44,17 @@ internal void RefreshCpuUsage()
if (!children.TryGetValue(Environment.ProcessId, out var hexusChildren)) return;

var liveApplications = _cpuStatisticsMap.Keys
.Select(app => (IsRunning: processManagerService.IsApplicationRunning(app, out var process), Application: app, Process: process))
.Select(name => configurationManager.Configuration.Applications.TryGetValue(name, out var app) ? app : null)
.Where(x => x is not null)
.Select(app => (IsRunning: processManagerService.IsApplicationRunning(app!, out var process), Application: app!, Process: process))
.Where(tuple => tuple.IsRunning && hexusChildren.Contains(tuple.Process!.Id))
.ToDictionary(tuple => tuple.Process!.Id, t => t);

foreach (var child in hexusChildren)
{
if (!liveApplications.TryGetValue(child, out var tuple)) continue;

if (!_cpuStatisticsMap.TryGetValue(tuple.Application, out var statistics)) continue;
if (!_cpuStatisticsMap.TryGetValue(tuple.Application.Name, out var statistics)) continue;

var processes = Traverse(child, children).ToArray();
var cpuUsage = GetApplicationCpuUsage(statistics, processes).Sum();
Expand Down

0 comments on commit 96a3e25

Please sign in to comment.