Skip to content

Commit

Permalink
windows fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
mdaneri committed Nov 22, 2024
1 parent 5ee2225 commit 3cdfb0d
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 48 deletions.
2 changes: 2 additions & 0 deletions src/PodeMonitor/IPausableHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ public interface IPausableHostedService


void Restart();

public ServiceState State { get; }
}
}
102 changes: 79 additions & 23 deletions src/PodeMonitor/PodeMonitor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
Expand Down Expand Up @@ -36,31 +37,22 @@ public class PodeMonitor
private readonly bool _disableTermination; // Indicates whether termination is disabled
private readonly int _shutdownWaitTimeMs; // Timeout for shutting down the process
private readonly string _pipeName; // Name of the named pipe for communication
private readonly string _stateFilePath; // Path to the service state file

private DateTime _lastLogTime; // Tracks the last time the process logged activity

public int StartMaxRetryCount { get; } // Maximum number of retries for starting the process
public int StartRetryDelayMs { get; } // Delay between retries in milliseconds

// Volatile variables ensure thread-safe visibility for all threads
private volatile bool _suspended;
private volatile bool _starting;
private volatile bool _running;
private volatile ServiceState _state;


public ServiceState State { get => _state; set => _state = value; }

public bool DisableTermination { get => _disableTermination; }

/// <summary>
/// Indicates whether the service is suspended.
/// </summary>
public bool Suspended => _suspended;

/// <summary>
/// Indicates whether the service is starting.
/// </summary>
public bool Starting => _starting;

/// <summary>
/// Indicates whether the service is running.
/// </summary>
public bool Running => _running;

/// <summary>
/// Initializes a new instance of the <see cref="PodeMonitor"/> class with the specified configuration options.
Expand All @@ -81,6 +73,15 @@ public PodeMonitor(PodeMonitorWorkerOptions options)
// Generate a unique pipe name
_pipeName = PipeNameGenerator.GeneratePipeName();
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, $"Initialized PodeMonitor with pipe name: {_pipeName}");
// Define the state file path only for Linux/macOS
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
// Define the state file path (default to /var/tmp for Linux/macOS)
_stateFilePath = Path.Combine("/var/tmp", $"PodeService_{Environment.ProcessId}.state");

PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, $"Initialized PodeMonitor with pipe name: {_pipeName} and state file: {_stateFilePath}");
}

}

/// <summary>
Expand Down Expand Up @@ -155,7 +156,7 @@ public void StopPowerShellProcess()
{
lock (_syncLock)
{
if (_powerShellProcess == null || _powerShellProcess.HasExited)
if (_powerShellProcess == null || (_powerShellProcess.HasExited && Process.GetProcessById(_powerShellProcess.Id) == null))
{
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, "Pode process is not running.");
return;
Expand All @@ -169,8 +170,10 @@ public void StopPowerShellProcess()
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, $"Waiting for {_shutdownWaitTimeMs} milliseconds for Pode process to exit...");
WaitForProcessExit(_shutdownWaitTimeMs);

if (!_powerShellProcess.HasExited)
if (_powerShellProcess != null || !_powerShellProcess.HasExited || Process.GetProcessById(_powerShellProcess.Id) != null)
{
// var p=Process.GetProcessById(_powerShellProcess.Id);
// p.Kill();
PodeMonitorLogger.Log(LogLevel.WARN, "PodeMonitor", Environment.ProcessId, "Pode process did not terminate gracefully. Killing process.");
_powerShellProcess.Kill();
}
Expand Down Expand Up @@ -205,6 +208,8 @@ public void StopPowerShellProcess()
/// </summary>
public void RestartPowerShellProcess() => ExecutePipeCommand("restart");



/// <summary>
/// Executes a command by sending it to the Pode process via named pipe.
/// </summary>
Expand Down Expand Up @@ -243,7 +248,7 @@ private void ParseServiceState(string output)

if (output.StartsWith("Service State: ", StringComparison.OrdinalIgnoreCase))
{
string state = output.Substring("Service State: ".Length).Trim().ToLowerInvariant();
string state = output["Service State: ".Length..].Trim().ToLowerInvariant();

switch (state)
{
Expand All @@ -270,11 +275,14 @@ private void ParseServiceState(string output)
/// <param name="state">The new service state.</param>
private void UpdateServiceState(ServiceState state)
{
_suspended = state == ServiceState.Suspended;
_starting = state == ServiceState.Starting;
_running = state == ServiceState.Running;

_state = state;
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, $"Service state updated to: {state}");
// Write the state to the state file only on Linux/macOS
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
// Write the state to the state file
WriteServiceStateToFile(state);
}
}

/// <summary>
Expand Down Expand Up @@ -357,6 +365,49 @@ private void WaitForProcessExit(int timeout)
}
}


/// <summary>
/// Writes the current service state to the state file.
/// </summary>
/// <param name="state">The service state to write.</param>
private void WriteServiceStateToFile(ServiceState state)
{
lock (_syncLock) // Ensure thread-safe access
{
try
{
File.WriteAllText(_stateFilePath, state.ToString().ToLowerInvariant());
PodeMonitorLogger.Log(LogLevel.DEBUG, "PodeMonitor", Environment.ProcessId, $"Service state written to file: {_stateFilePath}");
}
catch (Exception ex)
{
PodeMonitorLogger.Log(LogLevel.ERROR, "PodeMonitor", Environment.ProcessId, $"Failed to write service state to file: {ex.Message}");
}
}
}

/// <summary>
/// Deletes the service state file during cleanup.
/// </summary>
private void DeleteServiceStateFile()
{
lock (_syncLock) // Ensure thread-safe access
{
try
{
if (File.Exists(_stateFilePath))
{
File.Delete(_stateFilePath);
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, $"Service state file deleted: {_stateFilePath}");
}
}
catch (Exception ex)
{
PodeMonitorLogger.Log(LogLevel.ERROR, "PodeMonitor", Environment.ProcessId, $"Failed to delete service state file: {ex.Message}");
}
}
}

/// <summary>
/// Cleans up resources associated with the Pode process and the pipe client.
/// </summary>
Expand All @@ -366,6 +417,11 @@ private void CleanupResources()
_powerShellProcess = null;

CleanupPipeClient();

if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
DeleteServiceStateFile();
}
}

/// <summary>
Expand Down
31 changes: 11 additions & 20 deletions src/PodeMonitor/PodeMonitorWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public sealed class PodeMonitorWorker : BackgroundService, IPausableHostedServic

private bool _terminating = false;

public ServiceState State => _pwshMonitor.State;


/// <summary>
/// Initializes a new instance of the PodeMonitorWorker class.
Expand All @@ -47,7 +49,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, "PodeMonitorWorker running at: {0}", DateTimeOffset.Now);
int retryCount = 0; // Tracks the number of retries in case of failures

while (!stoppingToken.IsCancellationRequested)
while (!stoppingToken.IsCancellationRequested && !_terminating)
{
try
{
Expand Down Expand Up @@ -98,7 +100,7 @@ public override async Task StopAsync(CancellationToken stoppingToken)
/// </summary>
public void Shutdown()
{
if ((!_terminating) && (_pwshMonitor.Running || _pwshMonitor.Suspended))
if ((!_terminating) && (_pwshMonitor.State == ServiceState.Running || _pwshMonitor.State == ServiceState.Suspended))
{

_terminating = true;
Expand All @@ -122,30 +124,19 @@ public void Shutdown()
/// </summary>
public void Restart()
{
if ((!_terminating) && _pwshMonitor.Running)
if ((!_terminating) && _pwshMonitor.State == ServiceState.Running)
{
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, "Service restarting at: {0}", DateTimeOffset.Now);
try
{
_pwshMonitor.RestartPowerShellProcess(); // Restart the process
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, "Restart message sent via pipe at: {0}", DateTimeOffset.Now);

var retryCount = 0; // Reset retry count on success
while (_pwshMonitor.Starting)
{
if (retryCount >= 100)
{
PodeMonitorLogger.Log(LogLevel.CRITICAL, "PodeMonitor", Environment.ProcessId, "Maximum retry count reached. Exiting monitoring loop.");
break;
}

// Delay before retrying
Thread.Sleep(200);
}
//AddOperationDelay("Pause"); // Delay to ensure stability
}
catch (Exception ex)
{
PodeMonitorLogger.Log(LogLevel.ERROR, ex, "Error during restart: {0}", ex.Message);
PodeMonitorLogger.Log(LogLevel.ERROR, ex, "Error during pause: {0}", ex.Message);
}
}
}
Expand All @@ -155,7 +146,7 @@ public void Restart()
/// </summary>
public void OnPause()
{
if ((!_terminating) && _pwshMonitor.Running)
if ((!_terminating) && _pwshMonitor.State == ServiceState.Running)
{
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, "Pause command received at: {0}", DateTimeOffset.Now);

Expand All @@ -165,7 +156,7 @@ public void OnPause()

PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, "Suspend message sent via pipe at: {0}", DateTimeOffset.Now);
var retryCount = 0; // Reset retry count on success
while (!_pwshMonitor.Suspended)
while (_pwshMonitor.State != ServiceState.Suspended)
{
if (retryCount >= 100)
{
Expand All @@ -190,7 +181,7 @@ public void OnPause()
/// </summary>
public void OnContinue()
{
if ((!_terminating) && _pwshMonitor.Suspended)
if ((!_terminating) && _pwshMonitor.State == ServiceState.Suspended)
{
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, "Continue command received at: {0}", DateTimeOffset.Now);

Expand All @@ -200,7 +191,7 @@ public void OnContinue()

PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, "Resume message sent via pipe at: {0}", DateTimeOffset.Now);
var retryCount = 0; // Reset retry count on success
while (_pwshMonitor.Suspended)
while (_pwshMonitor.State == ServiceState.Suspended)
{
if (retryCount >= 100)
{
Expand Down
8 changes: 5 additions & 3 deletions src/Private/Service.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,13 @@ function Start-PodeServiceHearthbeat {
'restart' {
# Process 'restart' message
Write-PodeHost -Message 'Server requested restart. Restarting Pode ...' -Force
Restart-PodeServer # Restart Pode server
Start-Sleep 1

$serviceState = 'starting'
Write-PodeHost -Message 'Closing Service Monitoring Heartbeat' -Force
Write-PodeHost -Message "Service State: $serviceState" -Force
Start-Sleep 1
Restart-PodeServer # Restart Pode server
Write-PodeHost -Message 'Closing Service Monitoring Heartbeat' -Force
Start-Sleep 1
return
# Exit the loop
}
Expand Down
6 changes: 4 additions & 2 deletions src/Public/Service.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ function Start-PodeService {
$service = Get-Service -Name $Name -ErrorAction SilentlyContinue
if ($service) {
# Check if the service is already running
if ($service.Status -ne 'Running') {
if ($service.Status -eq 'Stopped') {
$null = Invoke-PodeWinElevatedCommand -Command 'Start-Service' -Arguments "-Name '$Name'"

$service = Get-Service -Name $Name -ErrorAction SilentlyContinue
Expand All @@ -313,7 +313,8 @@ function Start-PodeService {
}
else {
# Log service is already running
Write-Verbose -Message "Service '$Name' is already running."
Write-Verbose -Message "Service '$Name' is $($service.Status)."
return ($service.Status -eq 'Running')
}
}
else {
Expand Down Expand Up @@ -1107,6 +1108,7 @@ function Restart-PodeService {
if ($service.Status -eq 'Running' -or $service.Status -eq 'Paused') {
Write-Verbose -Message "Sending restart (128) signal to service '$Name'."
$null = Invoke-PodeWinElevatedCommand -Command 'sc control' -Arguments "'$Name' 128"
Start-Sleep 5
$service = Get-Service -Name $Name -ErrorAction SilentlyContinue
Write-Verbose -Message "Service '$Name' restart signal sent successfully."
}
Expand Down

0 comments on commit 3cdfb0d

Please sign in to comment.