You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Zero/ZeroLevel/Services/AsService/Windows/WindowsServiceHost.cs

353 lines
11 KiB

using System;
using System.IO;
using System.Reflection;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
namespace ZeroLevel.Services.AsService.Windows
{
public class WindowsServiceHost :
ServiceBase,
Host,
HostControl
{
readonly HostConfigurator _configurator;
readonly HostEnvironment _environment;
readonly ServiceHandle _serviceHandle;
readonly HostSettings _settings;
int _deadThread;
bool _disposed;
Exception _unhandledException;
public WindowsServiceHost(HostEnvironment environment, HostSettings settings, ServiceHandle serviceHandle, HostConfigurator configurator)
{
if (settings == null)
throw new ArgumentNullException(nameof(settings));
if (serviceHandle == null)
throw new ArgumentNullException(nameof(serviceHandle));
_settings = settings;
_serviceHandle = serviceHandle;
_environment = environment;
_configurator = configurator;
CanPauseAndContinue = settings.CanPauseAndContinue;
CanShutdown = settings.CanShutdown;
CanHandlePowerEvent = settings.CanHandlePowerEvent;
CanHandleSessionChangeEvent = settings.CanSessionChanged;
ServiceName = _settings.ServiceName;
}
public ExitCode Run()
{
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
AppDomain.CurrentDomain.UnhandledException += CatchUnhandledException;
ExitCode = (int)AsService.ExitCode.Ok;
Log.Info("Starting as a Windows service");
if (!_environment.IsServiceInstalled(_settings.ServiceName))
{
string message = $"The {_settings.ServiceName} service has not been installed yet. Please run '{Assembly.GetEntryAssembly().GetName()} install'.";
Log.Fatal(message);
ExitCode = (int)AsService.ExitCode.ServiceNotInstalled;
throw new Exception(message);
}
Log.Debug("[AsService] Starting up as a windows service application");
Run(this);
return (ExitCode)Enum.ToObject(typeof(ExitCode), ExitCode);
}
void HostControl.RequestAdditionalTime(TimeSpan timeRemaining)
{
Log.Debug("Requesting additional time: {0}", timeRemaining);
RequestAdditionalTime((int)timeRemaining.TotalMilliseconds);
}
void HostControl.Stop()
{
InternalStop();
}
void HostControl.Stop(ExitCode exitCode)
{
InternalStop(exitCode);
}
void InternalStop(ExitCode? exitCode = null)
{
if (CanStop)
{
Log.Debug("Stop requested by hosted service");
if (exitCode.HasValue)
ExitCode = (int)exitCode.Value;
Stop();
}
else
{
Log.Debug("Stop requested by hosted service, but service cannot be stopped at this time");
throw new ServiceControlException("The service cannot be stopped at this time");
}
}
protected override void OnStart(string[] args)
{
try
{
Log.Info("[AsService] Starting");
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
Log.Debug("[AsService] Current Directory: {0}", Directory.GetCurrentDirectory());
Log.Debug("[AsService] Arguments: {0}", string.Join(",", args));
string startArgs = string.Join(" ", args);
_configurator.ApplyCommandLine(startArgs);
if (!_serviceHandle.Start(this))
throw new Exception("The service did not start successfully (returned false).");
Log.Info("[AsService] Started");
}
catch (Exception ex)
{
_settings.ExceptionCallback?.Invoke(ex);
Log.Fatal("The service did not start successfully", ex);
ExitCode = (int)AsService.ExitCode.ServiceControlRequestFailed;
throw;
}
}
protected override void OnStop()
{
try
{
Log.Info("[AsService] Stopping");
if (!_serviceHandle.Stop(this))
throw new Exception("The service did not stop successfully (return false).");
Log.Info("[AsService] Stopped");
}
catch (Exception ex)
{
_settings.ExceptionCallback?.Invoke(ex);
Log.Fatal("The service did not shut down gracefully", ex);
ExitCode = (int)AsService.ExitCode.ServiceControlRequestFailed;
throw;
}
if (_unhandledException != null)
{
ExitCode = (int)AsService.ExitCode.AbnormalExit;
Log.Info("[AsService] Unhandled exception detected, rethrowing to cause application to restart.");
throw new InvalidOperationException("An unhandled exception was detected", _unhandledException);
}
}
protected override void OnPause()
{
try
{
Log.Info("[AsService] Pausing service");
if (!_serviceHandle.Pause(this))
throw new Exception("The service did not pause successfully (returned false).");
Log.Info("[AsService] Paused");
}
catch (Exception ex)
{
_settings.ExceptionCallback?.Invoke(ex);
Log.Fatal("The service did not pause gracefully", ex);
throw;
}
}
protected override void OnContinue()
{
try
{
Log.Info("[AsService] Resuming service");
if (!_serviceHandle.Continue(this))
throw new Exception("The service did not continue successfully (returned false).");
Log.Info("[AsService] Resumed");
}
catch (Exception ex)
{
_settings.ExceptionCallback?.Invoke(ex);
Log.Fatal("The service did not resume successfully", ex);
throw;
}
}
protected override void OnShutdown()
{
try
{
Log.Info("[AsService] Service is being shutdown");
_serviceHandle.Shutdown(this);
Log.Info("[AsService] Stopped");
}
catch (Exception ex)
{
_settings.ExceptionCallback?.Invoke(ex);
Log.Fatal("The service did not shut down gracefully", ex);
ExitCode = (int)AsService.ExitCode.ServiceControlRequestFailed;
throw;
}
}
protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
try
{
Log.Info("[AsService] Service session changed");
var arguments = new WindowsSessionChangedArguments(changeDescription);
_serviceHandle.SessionChanged(this, arguments);
Log.Info("[AsService] Service session changed handled");
}
catch (Exception ex)
{
_settings.ExceptionCallback?.Invoke(ex);
Log.Fatal("The did not handle Service session change correctly", ex);
ExitCode = (int)AsService.ExitCode.ServiceControlRequestFailed;
throw;
}
}
protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
{
try
{
Log.Info("[AsService] Power event raised");
var arguments = new WindowsPowerEventArguments(powerStatus);
bool result = _serviceHandle.PowerEvent(this, arguments);
Log.Info("[AsService] Power event handled");
return result;
}
catch (Exception ex)
{
_settings.ExceptionCallback?.Invoke(ex);
Log.Fatal("The service did handle the Power event correctly", ex);
ExitCode = (int)AsService.ExitCode.ServiceControlRequestFailed;
throw;
}
}
protected override void OnCustomCommand(int command)
{
try
{
Log.Info("[AsService] Custom command {0} received", command);
_serviceHandle.CustomCommand(this, command);
Log.Info("[AsService] Custom command {0} processed", command);
}
catch (Exception ex)
{
_settings.ExceptionCallback?.Invoke(ex);
Log.Error("Unhandled exception during custom command processing detected", ex);
}
}
protected override void Dispose(bool disposing)
{
if (disposing && !_disposed)
{
_serviceHandle?.Dispose();
_disposed = true;
}
base.Dispose(disposing);
}
void CatchUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
_settings.ExceptionCallback?.Invoke((Exception)e.ExceptionObject);
if (_settings.UnhandledExceptionPolicy == UnhandledExceptionPolicyCode.TakeNoAction)
return;
Log.Fatal("The service threw an unhandled exception", (Exception)e.ExceptionObject);
if (_settings.UnhandledExceptionPolicy == UnhandledExceptionPolicyCode.LogErrorOnly)
return;
ExitCode = (int)AsService.ExitCode.AbnormalExit;
_unhandledException = (Exception)e.ExceptionObject;
Stop();
// it isn't likely that a TPL thread should land here, but if it does let's no block it
if (Task.CurrentId.HasValue)
return;
int deadThreadId = Interlocked.Increment(ref _deadThread);
Thread.CurrentThread.IsBackground = true;
Thread.CurrentThread.Name = "Unhandled Exception " + deadThreadId;
while (true)
Thread.Sleep(TimeSpan.FromHours(1));
}
class WindowsSessionChangedArguments :
SessionChangedArguments
{
public WindowsSessionChangedArguments(SessionChangeDescription changeDescription)
{
ReasonCode = (SessionChangeReasonCode)Enum.ToObject(typeof(SessionChangeReasonCode), (int)changeDescription.Reason);
SessionId = changeDescription.SessionId;
}
public SessionChangeReasonCode ReasonCode { get; }
public int SessionId { get; }
}
class WindowsPowerEventArguments :
PowerEventArguments
{
public WindowsPowerEventArguments(PowerBroadcastStatus powerStatus)
{
EventCode = (PowerEventCode)Enum.ToObject(typeof(PowerEventCode), (int)powerStatus);
}
public PowerEventCode EventCode { get; }
}
}
}

Powered by TurnKey Linux.