mirror of https://github.com/ogoun/Zero.git
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.
353 lines
11 KiB
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; }
|
|
}
|
|
}
|
|
}
|