Preshutdown support

pull/616/head
NextTurn 2020-07-29 00:00:00 +08:00 committed by Next Turn
parent ddab4150ac
commit c3771e391c
12 changed files with 154 additions and 62 deletions

View File

@ -55,6 +55,8 @@ namespace WinSW.Configuration
public bool DelayedAutoStart => false;
public bool Preshutdown => false;
public string[] ServiceDependencies => new string[0];
public bool Interactive => false;

View File

@ -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" />
internal void SetSecurityDescriptor(RawSecurityDescriptor securityDescriptor)
{

View File

@ -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
{
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>

View File

@ -574,6 +574,17 @@ namespace WinSW
/// </summary>
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>
/// 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

View File

@ -26,40 +26,35 @@ namespace WinSW.Util
foreach (Process process in Process.GetProcesses())
{
if (process.StartTime <= startTime)
{
process.Dispose();
continue;
}
IntPtr handle;
try
{
handle = process.Handle;
}
catch (Win32Exception)
{
process.Dispose();
continue;
}
if (process.StartTime <= startTime)
{
goto Next;
}
if (NtQueryInformationProcess(
handle,
PROCESSINFOCLASS.ProcessBasicInformation,
out PROCESS_BASIC_INFORMATION information,
sizeof(PROCESS_BASIC_INFORMATION)) != 0)
{
Logger.Warn("Failed to locate children of the process with PID=" + processId + ". Child processes won't be terminated");
process.Dispose();
continue;
}
IntPtr handle = process.Handle;
if ((int)information.InheritedFromUniqueProcessId == processId)
{
Logger.Info($"Found child process '{process.Format()}'.");
children.Add(process);
if (NtQueryInformationProcess(
handle,
PROCESSINFOCLASS.ProcessBasicInformation,
out PROCESS_BASIC_INFORMATION information,
sizeof(PROCESS_BASIC_INFORMATION)) != 0)
{
goto Next;
}
if ((int)information.InheritedFromUniqueProcessId == processId)
{
Logger.Info($"Found child process '{process.Format()}'.");
children.Add(process);
continue;
}
Next:
process.Dispose();
}
else
catch (Exception e) when (e is InvalidOperationException || e is Win32Exception)
{
process.Dispose();
}

View File

@ -0,0 +1,9 @@
using System.Diagnostics;
namespace WinSW.Logging
{
internal interface IServiceEventLog
{
void WriteEntry(string message, EventLogEntryType type);
}
}

View File

@ -1,16 +1,14 @@
using System.Diagnostics;
namespace WinSW.Logging
namespace WinSW.Logging
{
/// <summary>
/// Indicates that the class may reference the event log
/// </summary>
public interface IServiceEventLogProvider
internal interface IServiceEventLogProvider
{
/// <summary>
/// Locates Event Log for the service.
/// </summary>
/// <returns>Event Log or null if it is not avilable</returns>
EventLog? Locate();
IServiceEventLog? Locate();
}
}

View File

@ -8,15 +8,18 @@ namespace WinSW.Logging
/// Implementes service Event log appender for log4j.
/// The implementation presumes that service gets initialized after the logging.
/// </summary>
public class ServiceEventLogAppender : AppenderSkeleton
internal sealed class ServiceEventLogAppender : AppenderSkeleton
{
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public IServiceEventLogProvider Provider { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
private readonly IServiceEventLogProvider provider;
internal ServiceEventLogAppender(IServiceEventLogProvider provider)
{
this.provider = provider;
}
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
eventLog?.WriteEntry(loggingEvent.RenderedMessage, ToEventLogEntryType(loggingEvent.Level));

View File

@ -1,14 +1,12 @@
using System.Diagnostics;
namespace WinSW.Logging
namespace WinSW.Logging
{
/// <summary>
/// Implements caching of the WindowsService reference in WinSW.
/// </summary>
public class WrapperServiceEventLogProvider : IServiceEventLogProvider
internal sealed class WrapperServiceEventLogProvider : IServiceEventLogProvider
{
public WrapperService? Service { get; set; }
public EventLog? Locate() => this.Service?.EventLog;
public IServiceEventLog? Locate() => this.Service;
}
}

View File

@ -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
{
/// <summary>Applied to a method that will never return under any circumstance.</summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
internal sealed class DoesNotReturnAttribute : Attribute { }
internal sealed class DoesNotReturnAttribute : Attribute
{
}
}
#endif

View File

@ -396,6 +396,11 @@ namespace WinSW
sc.SetDelayedAutoStart(true);
}
if (descriptor.PreshutdownTimeout is TimeSpan preshutdownTimeout)
{
sc.SetPreshutdownTimeout(preshutdownTimeout);
}
string? securityDescriptor = descriptor.SecurityDescriptor;
if (securityDescriptor != null)
{
@ -740,6 +745,11 @@ namespace WinSW
sc.SetDelayedAutoStart(true);
}
if (descriptor.PreshutdownTimeout is TimeSpan preshutdownTimeout)
{
sc.SetPreshutdownTimeout(preshutdownTimeout);
}
string? securityDescriptor = descriptor.SecurityDescriptor;
if (securityDescriptor != null)
{
@ -846,11 +856,10 @@ namespace WinSW
}
// event log
var systemEventLogger = new ServiceEventLogAppender
var systemEventLogger = new ServiceEventLogAppender(WrapperService.eventLogProvider)
{
Name = "Wrapper event log",
Threshold = eventLogLevel,
Provider = WrapperService.eventLogProvider,
};
systemEventLogger.ActivateOptions();
appenders.Add(systemEventLogger);

View File

@ -14,7 +14,7 @@ using WinSW.Util;
namespace WinSW
{
public class WrapperService : ServiceBase, IEventLogger
public sealed class WrapperService : ServiceBase, IEventLogger, IServiceEventLog
{
private readonly Process process = new Process();
private readonly ServiceDescriptor descriptor;
@ -58,11 +58,11 @@ namespace WinSW
// Register the event log provider
eventLogProvider.Service = this;
}
public WrapperService()
: this(new ServiceDescriptor())
{
if (descriptor.Preshutdown)
{
this.AcceptPreshutdown();
}
}
/// <summary>
@ -135,34 +135,51 @@ namespace WinSW
public void LogEvent(string message)
{
if (this.shuttingdown)
{
// The Event Log service exits earlier.
return;
}
try
{
this.EventLog.WriteEntry(message);
}
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)
{
if (this.shuttingdown)
{
// The Event Log service exits earlier.
return;
}
try
{
this.EventLog.WriteEntry(message, type);
}
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)
{
this.LogEvent(message);
@ -213,6 +230,15 @@ namespace WinSW
}
}
protected override void OnCustomCommand(int command)
{
if (command == 0x0000000F)
{
// SERVICE_CONTROL_PRESHUTDOWN
this.Stop();
}
}
private void DoStart()
{
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()
{
this.RequestAdditionalTime(15_000);