mirror of https://github.com/winsw/winsw
Support sending `WM_CLOSE` message
parent
6cefa93c69
commit
b0423e3156
|
@ -708,6 +708,8 @@ namespace WinSW
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override TimeSpan StopTimeout => this.SingleTimeSpanElement(this.dom, "stoptimeout", base.StopTimeout);
|
public override TimeSpan StopTimeout => this.SingleTimeSpanElement(this.dom, "stoptimeout", base.StopTimeout);
|
||||||
|
|
||||||
|
public int StopTimeoutInMs => (int)this.StopTimeout.TotalMilliseconds;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desired process priority or null if not specified.
|
/// Desired process priority or null if not specified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -2,35 +2,38 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using log4net;
|
using log4net;
|
||||||
|
using WinSW.Native;
|
||||||
|
using static WinSW.Native.ConsoleApis;
|
||||||
using static WinSW.Native.ProcessApis;
|
using static WinSW.Native.ProcessApis;
|
||||||
|
|
||||||
namespace WinSW.Util
|
namespace WinSW.Util
|
||||||
{
|
{
|
||||||
public static class ProcessExtensions
|
public static class ProcessExtensions
|
||||||
{
|
{
|
||||||
private static readonly ILog Logger = LogManager.GetLogger(typeof(ProcessExtensions));
|
private static readonly ILog Log = LogManager.GetLogger(typeof(ProcessExtensions));
|
||||||
|
|
||||||
public static void StopTree(this Process process, TimeSpan stopTimeout)
|
public static void StopTree(this Process process, int millisecondsTimeout)
|
||||||
{
|
{
|
||||||
StopPrivate(process, stopTimeout);
|
StopPrivate(process, millisecondsTimeout);
|
||||||
|
|
||||||
foreach (Process child in GetChildren(process))
|
foreach (Process child in GetChildren(process))
|
||||||
{
|
{
|
||||||
using (child)
|
using (child)
|
||||||
{
|
{
|
||||||
StopTree(child, stopTimeout);
|
StopTree(child, millisecondsTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void StopDescendants(this Process process, TimeSpan stopTimeout)
|
internal static void StopDescendants(this Process process, int millisecondsTimeout)
|
||||||
{
|
{
|
||||||
foreach (Process child in GetChildren(process))
|
foreach (Process child in GetChildren(process))
|
||||||
{
|
{
|
||||||
using (child)
|
using (child)
|
||||||
{
|
{
|
||||||
StopTree(child, stopTimeout);
|
StopTree(child, millisecondsTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +67,7 @@ namespace WinSW.Util
|
||||||
|
|
||||||
if ((int)information.InheritedFromUniqueProcessId == processId)
|
if ((int)information.InheritedFromUniqueProcessId == processId)
|
||||||
{
|
{
|
||||||
Logger.Info($"Found child process '{other.Format()}'.");
|
Log.Debug($"Found child process '{other.Format()}'.");
|
||||||
children.Add(other);
|
children.Add(other);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -84,22 +87,41 @@ namespace WinSW.Util
|
||||||
// true => canceled
|
// true => canceled
|
||||||
// false => terminated
|
// false => terminated
|
||||||
// null => finished
|
// null => finished
|
||||||
internal static bool? Stop(this Process process, TimeSpan stopTimeout)
|
internal static bool? Stop(this Process process, int millisecondsTimeout)
|
||||||
{
|
{
|
||||||
if (process.HasExited)
|
if (process.HasExited)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// (bool sent, bool exited)
|
if (!(SendCtrlC(process) is bool sent))
|
||||||
KeyValuePair<bool, bool> result = SignalHelper.SendCtrlCToProcess(process, stopTimeout);
|
|
||||||
bool exited = result.Value;
|
|
||||||
if (exited)
|
|
||||||
{
|
{
|
||||||
bool sent = result.Key;
|
return null;
|
||||||
return sent ? true : (bool?)null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!sent)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sent = process.CloseMainWindow();
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sent)
|
||||||
|
{
|
||||||
|
if (process.WaitForExit(millisecondsTimeout))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NETCOREAPP
|
||||||
|
process.Kill();
|
||||||
|
#else
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
process.Kill();
|
process.Kill();
|
||||||
|
@ -107,41 +129,88 @@ namespace WinSW.Util
|
||||||
catch when (process.HasExited)
|
catch when (process.HasExited)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StopPrivate(Process process, TimeSpan stopTimeout)
|
private static void StopPrivate(Process process, int millisecondsTimeout)
|
||||||
{
|
{
|
||||||
Logger.Info("Stopping process " + process.Id);
|
Log.Debug($"Stopping process '{process.Format()}'...");
|
||||||
|
|
||||||
if (process.HasExited)
|
if (process.HasExited)
|
||||||
{
|
{
|
||||||
Logger.Info("Process " + process.Id + " is already stopped");
|
goto Exited;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// (bool sent, bool exited)
|
if (!(SendCtrlC(process) is bool sent))
|
||||||
KeyValuePair<bool, bool> result = SignalHelper.SendCtrlCToProcess(process, stopTimeout);
|
|
||||||
bool exited = result.Value;
|
|
||||||
if (!exited)
|
|
||||||
{
|
{
|
||||||
bool sent = result.Key;
|
goto Exited;
|
||||||
if (sent)
|
}
|
||||||
{
|
|
||||||
Logger.Info("Process " + process.Id + " did not respond to Ctrl+C signal - Killing as fallback");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!sent)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
process.Kill();
|
sent = process.CloseMainWindow();
|
||||||
}
|
}
|
||||||
catch when (process.HasExited)
|
catch (InvalidOperationException)
|
||||||
{
|
{
|
||||||
|
goto Exited;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Propagate error if process kill fails? Currently we use the legacy behavior
|
if (sent)
|
||||||
|
{
|
||||||
|
if (process.WaitForExit(millisecondsTimeout))
|
||||||
|
{
|
||||||
|
Log.Debug($"Process '{process.Format()}' canceled with code {process.ExitCode}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NETCOREAPP
|
||||||
|
process.Kill();
|
||||||
|
#else
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
catch when (process.HasExited)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Log.Debug($"Process '{process.Format()}' terminated.");
|
||||||
|
return;
|
||||||
|
|
||||||
|
Exited:
|
||||||
|
Log.Debug($"Process '{process.Format()}' has already exited.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool? SendCtrlC(Process process)
|
||||||
|
{
|
||||||
|
if (!AttachConsole(process.Id))
|
||||||
|
{
|
||||||
|
int error = Marshal.GetLastWin32Error();
|
||||||
|
Log.Debug("Failed to attach to console. " + error switch
|
||||||
|
{
|
||||||
|
Errors.ERROR_ACCESS_DENIED => "WinSW is already attached to a console.", // TODO: test mode
|
||||||
|
Errors.ERROR_INVALID_HANDLE => "The process does not have a console.",
|
||||||
|
Errors.ERROR_INVALID_PARAMETER => "The process has exited.",
|
||||||
|
_ => new Win32Exception(error).Message // unreachable
|
||||||
|
});
|
||||||
|
|
||||||
|
return error == Errors.ERROR_INVALID_PARAMETER ? (bool?)null : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = SetConsoleCtrlHandler(null, true);
|
||||||
|
_ = GenerateConsoleCtrlEvent(CtrlEvents.CTRL_C_EVENT, 0);
|
||||||
|
_ = SetConsoleCtrlHandler(null, false);
|
||||||
|
bool succeeded = FreeConsole();
|
||||||
|
Debug.Assert(succeeded);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using log4net;
|
|
||||||
using WinSW.Native;
|
|
||||||
|
|
||||||
namespace WinSW.Util
|
|
||||||
{
|
|
||||||
internal static class SignalHelper
|
|
||||||
{
|
|
||||||
private static readonly ILog Logger = LogManager.GetLogger(typeof(SignalHelper));
|
|
||||||
|
|
||||||
// (bool sent, bool exited)
|
|
||||||
internal static KeyValuePair<bool, bool> SendCtrlCToProcess(Process process, TimeSpan shutdownTimeout)
|
|
||||||
{
|
|
||||||
if (!ConsoleApis.AttachConsole(process.Id))
|
|
||||||
{
|
|
||||||
int error = Marshal.GetLastWin32Error();
|
|
||||||
Logger.Info("Failed to attach to console. " + error switch
|
|
||||||
{
|
|
||||||
Errors.ERROR_ACCESS_DENIED => "WinSW is already attached to a console.", // TODO: test mode
|
|
||||||
Errors.ERROR_INVALID_HANDLE => "The process does not have a console.",
|
|
||||||
Errors.ERROR_INVALID_PARAMETER => "The process has exited.",
|
|
||||||
_ => new Win32Exception(error).Message // unreachable
|
|
||||||
});
|
|
||||||
|
|
||||||
return new KeyValuePair<bool, bool>(false, error == Errors.ERROR_INVALID_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = ConsoleApis.SetConsoleCtrlHandler(null, true);
|
|
||||||
_ = ConsoleApis.GenerateConsoleCtrlEvent(ConsoleApis.CtrlEvents.CTRL_C_EVENT, 0);
|
|
||||||
_ = ConsoleApis.SetConsoleCtrlHandler(null, false);
|
|
||||||
bool succeeded = ConsoleApis.FreeConsole();
|
|
||||||
Debug.Assert(succeeded);
|
|
||||||
|
|
||||||
return new KeyValuePair<bool, bool>(true, process.WaitForExit((int)shutdownTimeout.TotalMilliseconds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,7 +18,7 @@ namespace WinSW
|
||||||
{
|
{
|
||||||
internal static readonly WrapperServiceEventLogProvider eventLogProvider = new WrapperServiceEventLogProvider();
|
internal static readonly WrapperServiceEventLogProvider eventLogProvider = new WrapperServiceEventLogProvider();
|
||||||
|
|
||||||
private static readonly TimeSpan additionalStopTimeout = new TimeSpan(TimeSpan.TicksPerSecond);
|
private static readonly int additionalStopTimeout = 1_000;
|
||||||
|
|
||||||
private static readonly ILog Log = LogManager.GetLogger(
|
private static readonly ILog Log = LogManager.GetLogger(
|
||||||
#if NETCOREAPP
|
#if NETCOREAPP
|
||||||
|
@ -380,14 +380,14 @@ namespace WinSW
|
||||||
{
|
{
|
||||||
Process process = this.process;
|
Process process = this.process;
|
||||||
Log.Debug("ProcessKill " + process.Id);
|
Log.Debug("ProcessKill " + process.Id);
|
||||||
bool? result = process.Stop(this.config.StopTimeout);
|
bool? result = process.Stop(this.config.StopTimeoutInMs);
|
||||||
this.LogMinimal($"Child process '{process.Format()}' " + result switch
|
this.LogMinimal($"Child process '{process.Format()}' " + result switch
|
||||||
{
|
{
|
||||||
true => $"canceled with code {process.ExitCode}.",
|
true => $"canceled with code {process.ExitCode}.",
|
||||||
false => "terminated.",
|
false => "terminated.",
|
||||||
null => $"finished with code '{process.ExitCode}'."
|
null => $"finished with code '{process.ExitCode}'."
|
||||||
});
|
});
|
||||||
this.process.StopDescendants(this.config.StopTimeout);
|
this.process.StopDescendants(this.config.StopTimeoutInMs);
|
||||||
this.ExtensionManager.FireOnProcessTerminated(process);
|
this.ExtensionManager.FireOnProcessTerminated(process);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -407,11 +407,11 @@ namespace WinSW
|
||||||
this.stoppingProcess = null;
|
this.stoppingProcess = null;
|
||||||
|
|
||||||
this.WaitForProcessToExit(this.process);
|
this.WaitForProcessToExit(this.process);
|
||||||
this.process.StopDescendants(this.config.StopTimeout);
|
this.process.StopDescendants(this.config.StopTimeoutInMs);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
this.process.StopTree(this.config.StopTimeout);
|
this.process.StopTree(this.config.StopTimeoutInMs);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -505,7 +505,7 @@ namespace WinSW
|
||||||
{
|
{
|
||||||
Log.Warn($"Child process '{process.Format()}' finished with code {process.ExitCode}.");
|
Log.Warn($"Child process '{process.Format()}' finished with code {process.ExitCode}.");
|
||||||
|
|
||||||
process.StopDescendants(this.config.StopTimeout);
|
process.StopDescendants(this.config.StopTimeoutInMs);
|
||||||
|
|
||||||
this.startingProcess?.StopTree(additionalStopTimeout);
|
this.startingProcess?.StopTree(additionalStopTimeout);
|
||||||
this.stoppingProcess?.StopTree(additionalStopTimeout);
|
this.stoppingProcess?.StopTree(additionalStopTimeout);
|
||||||
|
|
Loading…
Reference in New Issue