mirror of
https://github.com/winsw/winsw.git
synced 2025-12-10 18:37:28 +08:00
Support sending WM_CLOSE message
This commit is contained in:
@@ -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<bool, bool> 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<bool, bool> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user