mirror of https://github.com/winsw/winsw
Decouple Some process management logic to a standalone ProcessHelper class
parent
535e8429e0
commit
24a5e93b67
|
@ -321,7 +321,7 @@ namespace winsw
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
WriteEvent("ProcessKill " + _process.Id);
|
WriteEvent("ProcessKill " + _process.Id);
|
||||||
StopProcessAndChildren(_process.Id);
|
ProcessHelper.StopProcessAndChildren(_process.Id, _descriptor.StopTimeout, _descriptor.StopParentProcessFirst);
|
||||||
ExtensionManager.FireOnProcessTerminated(_process);
|
ExtensionManager.FireOnProcessTerminated(_process);
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
|
@ -370,75 +370,6 @@ namespace winsw
|
||||||
WriteEvent("Finished " + _descriptor.Id);
|
WriteEvent("Finished " + _descriptor.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StopProcessAndChildren(int pid)
|
|
||||||
{
|
|
||||||
var childPids = GetChildPids(pid);
|
|
||||||
|
|
||||||
if (_descriptor.StopParentProcessFirst)
|
|
||||||
{
|
|
||||||
StopProcess(pid);
|
|
||||||
foreach (var childPid in childPids)
|
|
||||||
{
|
|
||||||
StopProcessAndChildren(childPid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var childPid in childPids)
|
|
||||||
{
|
|
||||||
StopProcessAndChildren(childPid);
|
|
||||||
}
|
|
||||||
StopProcess(pid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<int> GetChildPids(int pid)
|
|
||||||
{
|
|
||||||
var searcher = new ManagementObjectSearcher("Select * From Win32_Process Where ParentProcessID=" + pid);
|
|
||||||
var childPids = new List<int>();
|
|
||||||
foreach (var mo in searcher.Get())
|
|
||||||
{
|
|
||||||
var childProcessId = mo["ProcessID"];
|
|
||||||
WriteEvent("Found child process: " + childProcessId + " Name: " + mo["Name"]);
|
|
||||||
childPids.Add(Convert.ToInt32(childProcessId));
|
|
||||||
}
|
|
||||||
return childPids;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopProcess(int pid)
|
|
||||||
{
|
|
||||||
WriteEvent("Stopping process " + pid);
|
|
||||||
Process proc;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
proc = Process.GetProcessById(pid);
|
|
||||||
}
|
|
||||||
catch (ArgumentException)
|
|
||||||
{
|
|
||||||
WriteEvent("Process " + pid + " is already stopped");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteEvent("Send SIGINT " + pid);
|
|
||||||
bool successful = SigIntHelper.SendSIGINTToProcess(proc, _descriptor.StopTimeout);
|
|
||||||
if (successful)
|
|
||||||
{
|
|
||||||
WriteEvent("SIGINT to" + pid + " successful");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
WriteEvent("SIGINT to " + pid + " failed - Killing as fallback", Level.Warn);
|
|
||||||
proc.Kill();
|
|
||||||
}
|
|
||||||
catch (ArgumentException)
|
|
||||||
{
|
|
||||||
// Process already exited.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WaitForProcessToExit(Process processoWait)
|
private void WaitForProcessToExit(Process processoWait)
|
||||||
{
|
{
|
||||||
SignalShutdownPending();
|
SignalShutdownPending();
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
using log4net;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Management;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace winsw.Util
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides helper classes for Process Management
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Since WinSW 2.0</remarks>
|
||||||
|
public class ProcessHelper
|
||||||
|
{
|
||||||
|
private static readonly ILog Logger = LogManager.GetLogger(typeof(ProcessHelper));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all children of the specified process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pid">Process PID</param>
|
||||||
|
/// <returns>List of child process PIDs</returns>
|
||||||
|
public static List<int> GetChildPids(int pid)
|
||||||
|
{
|
||||||
|
var searcher = new ManagementObjectSearcher("Select * From Win32_Process Where ParentProcessID=" + pid);
|
||||||
|
var childPids = new List<int>();
|
||||||
|
foreach (var mo in searcher.Get())
|
||||||
|
{
|
||||||
|
var childProcessId = mo["ProcessID"];
|
||||||
|
Logger.Info("Found child process: " + childProcessId + " Name: " + mo["Name"]);
|
||||||
|
childPids.Add(Convert.ToInt32(childProcessId));
|
||||||
|
}
|
||||||
|
return childPids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the process.
|
||||||
|
/// If the process cannot be stopped within the stop timeout, it gets killed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pid">PID of the process</param>
|
||||||
|
/// <param name="stopTimeout">Stop timeout</param>
|
||||||
|
public static void StopProcess(int pid, TimeSpan stopTimeout)
|
||||||
|
{
|
||||||
|
Logger.Info("Stopping process " + pid);
|
||||||
|
Process proc;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
proc = Process.GetProcessById(pid);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
Logger.Info("Process " + pid + " is already stopped", ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info("Send SIGINT " + pid);
|
||||||
|
bool successful = SigIntHelper.SendSIGINTToProcess(proc, stopTimeout);
|
||||||
|
if (successful)
|
||||||
|
{
|
||||||
|
Logger.Info("SIGINT to" + pid + " successful");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.Warn("SIGINT to " + pid + " failed - Killing as fallback");
|
||||||
|
proc.Kill();
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
// Process already exited.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Propagate error if process kill fails? Currently we use the legacy behavior
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Terminate process and its children.
|
||||||
|
/// By default the child processes get terminated first.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pid">Process PID</param>
|
||||||
|
/// <param name="stopTimeout">Stop timeout (for each process)</param>
|
||||||
|
/// <param name="stopParentProcessFirst">If enabled, the perent process will be terminated before its children on all levels</param>
|
||||||
|
public static void StopProcessAndChildren(int pid, TimeSpan stopTimeout, bool stopParentProcessFirst)
|
||||||
|
{
|
||||||
|
var childPids = GetChildPids(pid);
|
||||||
|
|
||||||
|
if (stopParentProcessFirst)
|
||||||
|
{
|
||||||
|
StopProcess(pid, stopTimeout);
|
||||||
|
foreach (var childPid in childPids)
|
||||||
|
{
|
||||||
|
StopProcessAndChildren(childPid, stopTimeout, stopParentProcessFirst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var childPid in childPids)
|
||||||
|
{
|
||||||
|
StopProcessAndChildren(childPid, stopTimeout, stopParentProcessFirst);
|
||||||
|
}
|
||||||
|
StopProcess(pid, stopTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Also move StartProcess methods once LogEvent()/WriteEvent() mess gets solved
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace winsw.Util
|
||||||
|
{
|
||||||
|
public static class SigIntHelper
|
||||||
|
{
|
||||||
|
private const string KERNEL32 = "kernel32.dll";
|
||||||
|
|
||||||
|
[DllImport(KERNEL32, SetLastError = true)]
|
||||||
|
private static extern bool AttachConsole(uint dwProcessId);
|
||||||
|
|
||||||
|
[DllImport(KERNEL32, SetLastError = true, ExactSpelling = true)]
|
||||||
|
private static extern bool FreeConsole();
|
||||||
|
|
||||||
|
[DllImport(KERNEL32)]
|
||||||
|
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);
|
||||||
|
// Delegate type to be used as the Handler Routine for SCCH
|
||||||
|
private delegate Boolean ConsoleCtrlDelegate(CtrlTypes CtrlType);
|
||||||
|
|
||||||
|
// Enumerated type for the control messages sent to the handler routine
|
||||||
|
private enum CtrlTypes : uint
|
||||||
|
{
|
||||||
|
CTRL_C_EVENT = 0,
|
||||||
|
CTRL_BREAK_EVENT,
|
||||||
|
CTRL_CLOSE_EVENT,
|
||||||
|
CTRL_LOGOFF_EVENT = 5,
|
||||||
|
CTRL_SHUTDOWN_EVENT
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport(KERNEL32)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses the native funciton "AttachConsole" to attach the thread to the executing process to try to trigger a CTRL_C event (SIGINT). If the application
|
||||||
|
/// doesn't honor the event and shut down gracefully, the. wait period will time out after 15 seconds.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="process">The process to attach to and send the SIGINT</param>
|
||||||
|
/// <returns>True if the process shut down successfully to the SIGINT, false if it did not.</returns>
|
||||||
|
public static bool SendSIGINTToProcess(Process process, TimeSpan shutdownTimeout)
|
||||||
|
{
|
||||||
|
if (AttachConsole((uint)process.Id))
|
||||||
|
{
|
||||||
|
//Disable Ctrl-C handling for our program
|
||||||
|
SetConsoleCtrlHandler(null, true);
|
||||||
|
GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);
|
||||||
|
|
||||||
|
process.WaitForExit((int)shutdownTimeout.TotalMilliseconds);
|
||||||
|
|
||||||
|
return process.HasExited;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,6 +62,8 @@
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="ServiceDescriptor.cs" />
|
<Compile Include="ServiceDescriptor.cs" />
|
||||||
<Compile Include="Util\IEventWriter.cs" />
|
<Compile Include="Util\IEventWriter.cs" />
|
||||||
|
<Compile Include="Util\ProcessHelper.cs" />
|
||||||
|
<Compile Include="Util\SigIntHelper.cs" />
|
||||||
<Compile Include="Util\XmlHelper.cs" />
|
<Compile Include="Util\XmlHelper.cs" />
|
||||||
<Compile Include="WinSWException.cs" />
|
<Compile Include="WinSWException.cs" />
|
||||||
<Compile Include="Wmi.cs" />
|
<Compile Include="Wmi.cs" />
|
||||||
|
|
Loading…
Reference in New Issue