mirror of https://github.com/winsw/winsw
Preshutdown support
parent
ddab4150ac
commit
c3771e391c
|
@ -55,6 +55,8 @@ namespace WinSW.Configuration
|
||||||
|
|
||||||
public bool DelayedAutoStart => false;
|
public bool DelayedAutoStart => false;
|
||||||
|
|
||||||
|
public bool Preshutdown => false;
|
||||||
|
|
||||||
public string[] ServiceDependencies => new string[0];
|
public string[] ServiceDependencies => new string[0];
|
||||||
|
|
||||||
public bool Interactive => false;
|
public bool Interactive => false;
|
||||||
|
|
|
@ -272,6 +272,18 @@ namespace WinSW.Native
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <exception cref="CommandException" />
|
||||||
|
internal void SetPreshutdownTimeout(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
if (!ChangeServiceConfig2(
|
||||||
|
this.handle,
|
||||||
|
ServiceConfigInfoLevels.PRESHUTDOWN_INFO,
|
||||||
|
new SERVICE_PRESHUTDOWN_INFO { PreshutdownTimeout = (int)timeout.TotalMilliseconds }))
|
||||||
|
{
|
||||||
|
Throw.Command.Win32Exception("Failed to configure the preshutdown timeout.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <exception cref="CommandException" />
|
/// <exception cref="CommandException" />
|
||||||
internal void SetSecurityDescriptor(RawSecurityDescriptor securityDescriptor)
|
internal void SetSecurityDescriptor(RawSecurityDescriptor securityDescriptor)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
#if !NETCOREAPP
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
#if !NETCOREAPP
|
||||||
namespace System.Diagnostics.CodeAnalysis
|
namespace System.Diagnostics.CodeAnalysis
|
||||||
{
|
{
|
||||||
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
||||||
|
|
|
@ -574,6 +574,17 @@ namespace WinSW
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool DelayedAutoStart => this.SingleBoolElement("delayedAutoStart", Defaults.DelayedAutoStart);
|
public bool DelayedAutoStart => this.SingleBoolElement("delayedAutoStart", Defaults.DelayedAutoStart);
|
||||||
|
|
||||||
|
public bool Preshutdown => this.SingleBoolElement("preshutdown", Defaults.Preshutdown);
|
||||||
|
|
||||||
|
public TimeSpan? PreshutdownTimeout
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string? value = this.SingleElement("preshutdownTimeout", true);
|
||||||
|
return value is null ? default : this.ParseTimeSpan(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the service should beep when finished on shutdown.
|
/// True if the service should beep when finished on shutdown.
|
||||||
/// This doesn't work on some OSes. See http://msdn.microsoft.com/en-us/library/ms679277%28VS.85%29.aspx
|
/// This doesn't work on some OSes. See http://msdn.microsoft.com/en-us/library/ms679277%28VS.85%29.aspx
|
||||||
|
|
|
@ -26,40 +26,35 @@ namespace WinSW.Util
|
||||||
|
|
||||||
foreach (Process process in Process.GetProcesses())
|
foreach (Process process in Process.GetProcesses())
|
||||||
{
|
{
|
||||||
if (process.StartTime <= startTime)
|
|
||||||
{
|
|
||||||
process.Dispose();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
IntPtr handle;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
handle = process.Handle;
|
if (process.StartTime <= startTime)
|
||||||
}
|
|
||||||
catch (Win32Exception)
|
|
||||||
{
|
{
|
||||||
process.Dispose();
|
goto Next;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IntPtr handle = process.Handle;
|
||||||
|
|
||||||
if (NtQueryInformationProcess(
|
if (NtQueryInformationProcess(
|
||||||
handle,
|
handle,
|
||||||
PROCESSINFOCLASS.ProcessBasicInformation,
|
PROCESSINFOCLASS.ProcessBasicInformation,
|
||||||
out PROCESS_BASIC_INFORMATION information,
|
out PROCESS_BASIC_INFORMATION information,
|
||||||
sizeof(PROCESS_BASIC_INFORMATION)) != 0)
|
sizeof(PROCESS_BASIC_INFORMATION)) != 0)
|
||||||
{
|
{
|
||||||
Logger.Warn("Failed to locate children of the process with PID=" + processId + ". Child processes won't be terminated");
|
goto Next;
|
||||||
process.Dispose();
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((int)information.InheritedFromUniqueProcessId == processId)
|
if ((int)information.InheritedFromUniqueProcessId == processId)
|
||||||
{
|
{
|
||||||
Logger.Info($"Found child process '{process.Format()}'.");
|
Logger.Info($"Found child process '{process.Format()}'.");
|
||||||
children.Add(process);
|
children.Add(process);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
Next:
|
||||||
|
process.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e is InvalidOperationException || e is Win32Exception)
|
||||||
{
|
{
|
||||||
process.Dispose();
|
process.Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace WinSW.Logging
|
||||||
|
{
|
||||||
|
internal interface IServiceEventLog
|
||||||
|
{
|
||||||
|
void WriteEntry(string message, EventLogEntryType type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,14 @@
|
||||||
using System.Diagnostics;
|
namespace WinSW.Logging
|
||||||
|
|
||||||
namespace WinSW.Logging
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the class may reference the event log
|
/// Indicates that the class may reference the event log
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IServiceEventLogProvider
|
internal interface IServiceEventLogProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Locates Event Log for the service.
|
/// Locates Event Log for the service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Event Log or null if it is not avilable</returns>
|
/// <returns>Event Log or null if it is not avilable</returns>
|
||||||
EventLog? Locate();
|
IServiceEventLog? Locate();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,15 +8,18 @@ namespace WinSW.Logging
|
||||||
/// Implementes service Event log appender for log4j.
|
/// Implementes service Event log appender for log4j.
|
||||||
/// The implementation presumes that service gets initialized after the logging.
|
/// The implementation presumes that service gets initialized after the logging.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ServiceEventLogAppender : AppenderSkeleton
|
internal sealed class ServiceEventLogAppender : AppenderSkeleton
|
||||||
{
|
{
|
||||||
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
|
private readonly IServiceEventLogProvider provider;
|
||||||
public IServiceEventLogProvider Provider { get; set; }
|
|
||||||
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
|
internal ServiceEventLogAppender(IServiceEventLogProvider provider)
|
||||||
|
{
|
||||||
|
this.provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Append(LoggingEvent loggingEvent)
|
protected override void Append(LoggingEvent loggingEvent)
|
||||||
{
|
{
|
||||||
EventLog? eventLog = this.Provider.Locate();
|
IServiceEventLog? eventLog = this.provider.Locate();
|
||||||
|
|
||||||
// We write the event iff the provider is ready
|
// We write the event iff the provider is ready
|
||||||
eventLog?.WriteEntry(loggingEvent.RenderedMessage, ToEventLogEntryType(loggingEvent.Level));
|
eventLog?.WriteEntry(loggingEvent.RenderedMessage, ToEventLogEntryType(loggingEvent.Level));
|
|
@ -1,14 +1,12 @@
|
||||||
using System.Diagnostics;
|
namespace WinSW.Logging
|
||||||
|
|
||||||
namespace WinSW.Logging
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements caching of the WindowsService reference in WinSW.
|
/// Implements caching of the WindowsService reference in WinSW.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WrapperServiceEventLogProvider : IServiceEventLogProvider
|
internal sealed class WrapperServiceEventLogProvider : IServiceEventLogProvider
|
||||||
{
|
{
|
||||||
public WrapperService? Service { get; set; }
|
public WrapperService? Service { get; set; }
|
||||||
|
|
||||||
public EventLog? Locate() => this.Service?.EventLog;
|
public IServiceEventLog? Locate() => this.Service;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
#if !NETCOREAPP
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
#if !NETCOREAPP
|
||||||
namespace System.Diagnostics.CodeAnalysis
|
namespace System.Diagnostics.CodeAnalysis
|
||||||
{
|
{
|
||||||
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
||||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||||
internal sealed class DoesNotReturnAttribute : Attribute { }
|
internal sealed class DoesNotReturnAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -396,6 +396,11 @@ namespace WinSW
|
||||||
sc.SetDelayedAutoStart(true);
|
sc.SetDelayedAutoStart(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (descriptor.PreshutdownTimeout is TimeSpan preshutdownTimeout)
|
||||||
|
{
|
||||||
|
sc.SetPreshutdownTimeout(preshutdownTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
string? securityDescriptor = descriptor.SecurityDescriptor;
|
string? securityDescriptor = descriptor.SecurityDescriptor;
|
||||||
if (securityDescriptor != null)
|
if (securityDescriptor != null)
|
||||||
{
|
{
|
||||||
|
@ -740,6 +745,11 @@ namespace WinSW
|
||||||
sc.SetDelayedAutoStart(true);
|
sc.SetDelayedAutoStart(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (descriptor.PreshutdownTimeout is TimeSpan preshutdownTimeout)
|
||||||
|
{
|
||||||
|
sc.SetPreshutdownTimeout(preshutdownTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
string? securityDescriptor = descriptor.SecurityDescriptor;
|
string? securityDescriptor = descriptor.SecurityDescriptor;
|
||||||
if (securityDescriptor != null)
|
if (securityDescriptor != null)
|
||||||
{
|
{
|
||||||
|
@ -846,11 +856,10 @@ namespace WinSW
|
||||||
}
|
}
|
||||||
|
|
||||||
// event log
|
// event log
|
||||||
var systemEventLogger = new ServiceEventLogAppender
|
var systemEventLogger = new ServiceEventLogAppender(WrapperService.eventLogProvider)
|
||||||
{
|
{
|
||||||
Name = "Wrapper event log",
|
Name = "Wrapper event log",
|
||||||
Threshold = eventLogLevel,
|
Threshold = eventLogLevel,
|
||||||
Provider = WrapperService.eventLogProvider,
|
|
||||||
};
|
};
|
||||||
systemEventLogger.ActivateOptions();
|
systemEventLogger.ActivateOptions();
|
||||||
appenders.Add(systemEventLogger);
|
appenders.Add(systemEventLogger);
|
||||||
|
|
|
@ -14,7 +14,7 @@ using WinSW.Util;
|
||||||
|
|
||||||
namespace WinSW
|
namespace WinSW
|
||||||
{
|
{
|
||||||
public class WrapperService : ServiceBase, IEventLogger
|
public sealed class WrapperService : ServiceBase, IEventLogger, IServiceEventLog
|
||||||
{
|
{
|
||||||
private readonly Process process = new Process();
|
private readonly Process process = new Process();
|
||||||
private readonly ServiceDescriptor descriptor;
|
private readonly ServiceDescriptor descriptor;
|
||||||
|
@ -58,11 +58,11 @@ namespace WinSW
|
||||||
|
|
||||||
// Register the event log provider
|
// Register the event log provider
|
||||||
eventLogProvider.Service = this;
|
eventLogProvider.Service = this;
|
||||||
}
|
|
||||||
|
|
||||||
public WrapperService()
|
if (descriptor.Preshutdown)
|
||||||
: this(new ServiceDescriptor())
|
|
||||||
{
|
{
|
||||||
|
this.AcceptPreshutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -135,32 +135,49 @@ namespace WinSW
|
||||||
|
|
||||||
public void LogEvent(string message)
|
public void LogEvent(string message)
|
||||||
{
|
{
|
||||||
|
if (this.shuttingdown)
|
||||||
|
{
|
||||||
|
// The Event Log service exits earlier.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.EventLog.WriteEntry(message);
|
this.EventLog.WriteEntry(message);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
|
||||||
if (!this.shuttingdown)
|
|
||||||
{
|
{
|
||||||
Log.Error("Failed to log event in Windows Event Log: " + message + "; Reason: ", e);
|
Log.Error("Failed to log event in Windows Event Log: " + message + "; Reason: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void LogEvent(string message, EventLogEntryType type)
|
public void LogEvent(string message, EventLogEntryType type)
|
||||||
{
|
{
|
||||||
|
if (this.shuttingdown)
|
||||||
|
{
|
||||||
|
// The Event Log service exits earlier.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.EventLog.WriteEntry(message, type);
|
this.EventLog.WriteEntry(message, type);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
|
||||||
if (!this.shuttingdown)
|
|
||||||
{
|
{
|
||||||
Log.Error("Failed to log event in Windows Event Log. Reason: ", e);
|
Log.Error("Failed to log event in Windows Event Log. Reason: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IServiceEventLog.WriteEntry(string message, EventLogEntryType type)
|
||||||
|
{
|
||||||
|
if (this.shuttingdown)
|
||||||
|
{
|
||||||
|
// The Event Log service exits earlier.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.EventLog.WriteEntry(message, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogInfo(string message)
|
private void LogInfo(string message)
|
||||||
|
@ -213,6 +230,15 @@ namespace WinSW
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnCustomCommand(int command)
|
||||||
|
{
|
||||||
|
if (command == 0x0000000F)
|
||||||
|
{
|
||||||
|
// SERVICE_CONTROL_PRESHUTDOWN
|
||||||
|
this.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DoStart()
|
private void DoStart()
|
||||||
{
|
{
|
||||||
this.envs = this.descriptor.EnvironmentVariables;
|
this.envs = this.descriptor.EnvironmentVariables;
|
||||||
|
@ -385,6 +411,27 @@ namespace WinSW
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <exception cref="MissingFieldException" />
|
||||||
|
private void AcceptPreshutdown()
|
||||||
|
{
|
||||||
|
const string acceptedCommandsFieldName =
|
||||||
|
#if NETCOREAPP
|
||||||
|
"_acceptedCommands";
|
||||||
|
#else
|
||||||
|
"acceptedCommands";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FieldInfo? acceptedCommandsField = typeof(ServiceBase).GetField(acceptedCommandsFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
if (acceptedCommandsField is null)
|
||||||
|
{
|
||||||
|
throw new MissingFieldException(nameof(ServiceBase), acceptedCommandsFieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
int acceptedCommands = (int)acceptedCommandsField.GetValue(this)!;
|
||||||
|
acceptedCommands |= 0x00000100; // SERVICE_ACCEPT_PRESHUTDOWN
|
||||||
|
acceptedCommandsField.SetValue(this, acceptedCommands);
|
||||||
|
}
|
||||||
|
|
||||||
private void SignalPending()
|
private void SignalPending()
|
||||||
{
|
{
|
||||||
this.RequestAdditionalTime(15_000);
|
this.RequestAdditionalTime(15_000);
|
||||||
|
|
Loading…
Reference in New Issue