diff --git a/src/WinSW.Core/Configuration/XmlServiceConfig.cs b/src/WinSW.Core/Configuration/XmlServiceConfig.cs
index ebb3dbc..6021a9f 100644
--- a/src/WinSW.Core/Configuration/XmlServiceConfig.cs
+++ b/src/WinSW.Core/Configuration/XmlServiceConfig.cs
@@ -708,6 +708,8 @@ namespace WinSW
///
public override TimeSpan StopTimeout => this.SingleTimeSpanElement(this.dom, "stoptimeout", base.StopTimeout);
+ public int StopTimeoutInMs => (int)this.StopTimeout.TotalMilliseconds;
+
///
/// Desired process priority or null if not specified.
///
diff --git a/src/WinSW.Core/Util/ProcessExtensions.cs b/src/WinSW.Core/Util/ProcessExtensions.cs
index 07fb212..f0c9b5f 100644
--- a/src/WinSW.Core/Util/ProcessExtensions.cs
+++ b/src/WinSW.Core/Util/ProcessExtensions.cs
@@ -2,35 +2,38 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
+using System.Runtime.InteropServices;
using log4net;
+using WinSW.Native;
+using static WinSW.Native.ConsoleApis;
using static WinSW.Native.ProcessApis;
namespace WinSW.Util
{
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))
{
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))
{
using (child)
{
- StopTree(child, stopTimeout);
+ StopTree(child, millisecondsTimeout);
}
}
}
@@ -64,7 +67,7 @@ namespace WinSW.Util
if ((int)information.InheritedFromUniqueProcessId == processId)
{
- Logger.Info($"Found child process '{other.Format()}'.");
+ Log.Debug($"Found child process '{other.Format()}'.");
children.Add(other);
continue;
}
@@ -84,22 +87,41 @@ namespace WinSW.Util
// true => canceled
// false => terminated
// null => finished
- internal static bool? Stop(this Process process, TimeSpan stopTimeout)
+ internal static bool? Stop(this Process process, int millisecondsTimeout)
{
if (process.HasExited)
{
return null;
}
- // (bool sent, bool exited)
- KeyValuePair result = SignalHelper.SendCtrlCToProcess(process, stopTimeout);
- bool exited = result.Value;
- if (exited)
+ if (!(SendCtrlC(process) is bool sent))
{
- bool sent = result.Key;
- return sent ? true : (bool?)null;
+ return 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
{
process.Kill();
@@ -107,41 +129,88 @@ namespace WinSW.Util
catch when (process.HasExited)
{
}
+#endif
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)
{
- Logger.Info("Process " + process.Id + " is already stopped");
- return;
+ goto Exited;
}
- // (bool sent, bool exited)
- KeyValuePair result = SignalHelper.SendCtrlCToProcess(process, stopTimeout);
- bool exited = result.Value;
- if (!exited)
+ if (!(SendCtrlC(process) is bool sent))
{
- bool sent = result.Key;
- if (sent)
- {
- Logger.Info("Process " + process.Id + " did not respond to Ctrl+C signal - Killing as fallback");
- }
+ goto Exited;
+ }
+ if (!sent)
+ {
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;
}
}
}
diff --git a/src/WinSW.Core/Util/SignalHelper.cs b/src/WinSW.Core/Util/SignalHelper.cs
deleted file mode 100644
index 35d46e2..0000000
--- a/src/WinSW.Core/Util/SignalHelper.cs
+++ /dev/null
@@ -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 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(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(true, process.WaitForExit((int)shutdownTimeout.TotalMilliseconds));
- }
- }
-}
diff --git a/src/WinSW/WrapperService.cs b/src/WinSW/WrapperService.cs
index c9d0a71..81dd175 100644
--- a/src/WinSW/WrapperService.cs
+++ b/src/WinSW/WrapperService.cs
@@ -18,7 +18,7 @@ namespace WinSW
{
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(
#if NETCOREAPP
@@ -380,14 +380,14 @@ namespace WinSW
{
Process process = this.process;
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
{
true => $"canceled with code {process.ExitCode}.",
false => "terminated.",
null => $"finished with code '{process.ExitCode}'."
});
- this.process.StopDescendants(this.config.StopTimeout);
+ this.process.StopDescendants(this.config.StopTimeoutInMs);
this.ExtensionManager.FireOnProcessTerminated(process);
}
else
@@ -407,11 +407,11 @@ namespace WinSW
this.stoppingProcess = null;
this.WaitForProcessToExit(this.process);
- this.process.StopDescendants(this.config.StopTimeout);
+ this.process.StopDescendants(this.config.StopTimeoutInMs);
}
catch
{
- this.process.StopTree(this.config.StopTimeout);
+ this.process.StopTree(this.config.StopTimeoutInMs);
throw;
}
}
@@ -505,7 +505,7 @@ namespace WinSW
{
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.stoppingProcess?.StopTree(additionalStopTimeout);