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 Preshutdown => false;
|
||||
|
||||
public string[] ServiceDependencies => new string[0];
|
||||
|
||||
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" />
|
||||
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
|
||||
{
|
||||
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
/// 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();
|
||||
}
|
||||
}
|
|
@ -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));
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue