Skip to content

Commit

Permalink
Add ability to force close sessions on process crash/kill
Browse files Browse the repository at this point in the history
  • Loading branch information
GieltjE committed Apr 10, 2021
1 parent e1c19ac commit 52c256c
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 3 deletions.
96 changes: 95 additions & 1 deletion Data/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

using System;
using System.Drawing;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

Expand Down Expand Up @@ -54,10 +55,34 @@ public static class NativeMethods
private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, Int32 nIndex, IntPtr dwNewLong);
public static IntPtr SetWindowLongPtr(HandleRef hWnd, Int32 nIndex, IntPtr dwNewLong) => IntPtr.Size == 8 ? SetWindowLongPtr64(hWnd, nIndex, dwNewLong) : new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));

[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean DeleteFile(String name);

[DllImport("kernel32.dll", SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
internal static extern Boolean CloseHandle(IntPtr hObject);

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateJobObject(IntPtr a, String lpName);

[DllImport("kernel32.dll")]
internal static extern Boolean SetInformationJobObject(SafeJobHandle hJob, NativeMethods.JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern Boolean AssignProcessToJobObject(SafeJobHandle job, SafeProcessHandle process);

public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}

public enum GWL : SByte
{
GWL_WNDPROC = -4,
Expand Down Expand Up @@ -139,6 +164,25 @@ public enum WindowStyles : UInt32
WS_EX_NOACTIVATE = 0x08000000
}

[Flags]
public enum JobObjectLimitFlags : UInt32
{
JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x00000008,
JOB_OBJECT_LIMIT_AFFINITY = 0x00000010,
JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800,
JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x00000400,
JOB_OBJECT_LIMIT_JOB_MEMORY = 0x00000200,
JOB_OBJECT_LIMIT_JOB_TIME = 0x00000004,
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000,
JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME = 0x00000040,
JOB_OBJECT_LIMIT_PRIORITY_CLASS = 0x00000020,
JOB_OBJECT_LIMIT_PROCESS_MEMORY = 0x00000100,
JOB_OBJECT_LIMIT_PROCESS_TIME = 0x00000002,
JOB_OBJECT_LIMIT_SCHEDULING_CLASS = 0x00000080,
JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000,
JOB_OBJECT_LIMIT_WORKINGSET = 0x00000001
}

public enum ShowWindowCommands : Byte
{
// Hides the window and activates another window.
Expand Down Expand Up @@ -201,5 +245,55 @@ private Rect(Int32 left, Int32 top, Int32 right, Int32 bottom)

public Rect(Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) {}
}

[StructLayout(LayoutKind.Sequential)]
internal struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public Int64 PerProcessUserTimeLimit;
public Int64 PerJobUserTimeLimit;
public UInt32 LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public UInt32 ActiveProcessLimit;
public UIntPtr Affinity;
public UInt32 PriorityClass;
public UInt32 SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public UInt32 nLength;
public IntPtr lpSecurityDescriptor;
public Int32 bInheritHandle;
}

[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
}

internal sealed class SafeJobHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeJobHandle(IntPtr handle) : base(true) => SetHandle(handle);
protected override Boolean ReleaseHandle() => NativeMethods.CloseHandle(handle);
}
}
3 changes: 2 additions & 1 deletion Data/Statics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
//

using MSM.Extends;
using MSM.Functions;

namespace MSM.Data
{
public static class Statics
{
public static readonly NewtonsoftJsonSerializer NewtonsoftJsonSerializer = new();
public static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public static ChildProcessManager ChildProcessManager;
}
}
51 changes: 51 additions & 0 deletions Functions/ChildProcessManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using MSM.Data;
using MSM.Service;

namespace MSM.Functions
{
public sealed class ChildProcessManager : IDisposable
{
private SafeJobHandle _handle;
private Boolean _disposed;

public ChildProcessManager()
{
_handle = new SafeJobHandle(NativeMethods.CreateJobObject(IntPtr.Zero, null));

NativeMethods.JOBOBJECT_BASIC_LIMIT_INFORMATION info = new() {
LimitFlags = (UInt32)NativeMethods.JobObjectLimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
};
NativeMethods.JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new() { BasicLimitInformation = info };

Int32 length = Marshal.SizeOf(typeof(NativeMethods.JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

if (!NativeMethods.SetInformationJobObject(_handle, NativeMethods.JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (UInt32)length))
{
Logger.Log(Enumerations.LogTarget.General, Enumerations.LogLevel.Fatal, "Could not initialise child process manager: " + Marshal.GetLastWin32Error().ToString(CultureInfo.InvariantCulture), null);
}
}
public void Dispose()
{
if (_disposed) return;

_handle.Close();
_handle.Dispose();
_handle = null;
_disposed = true;
}

public void AddProcess(SafeProcessHandle processHandle)
{
if (!NativeMethods.AssignProcessToJobObject(_handle, processHandle))
{
Logger.Log(Enumerations.LogTarget.General, Enumerations.LogLevel.Fatal, "Could not add the process to the child process manager", null);
}
}
}
}
3 changes: 2 additions & 1 deletion MSM.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
Expand Down Expand Up @@ -82,6 +82,7 @@
<Compile Include="Extends\DockPanelOptimized.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Functions\ChildProcessManager.cs" />
<Compile Include="Extends\NewtonsoftJsonSerializer.cs" />
<Compile Include="Extends\PropertyGridHelpers.cs" />
<Compile Include="Extends\StatusStripOptimized.cs">
Expand Down
31 changes: 31 additions & 0 deletions Service/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,37 @@ public Boolean CloseTabOnCrash
}
[XmlIgnore] private Boolean _closeTabOnCrash = true;

[Category("Sessions"), DisplayName("Force close all sessions on application crash/closure"), TypeConverter(typeof(BooleanYesNoConverter))]
public Boolean ForceCloseSessionsOnCrash
{
get => _forceCloseSessionsOnCrash;
set
{
Boolean update = _forceCloseSessionsOnCrash != value;
_forceCloseSessionsOnCrash = value;

if (Settings.Values != null)
{
if (value || Statics.ChildProcessManager == null)
{
Statics.ChildProcessManager = new ChildProcessManager();
Statics.ChildProcessManager.AddProcess(Process.GetCurrentProcess().SafeHandle);
}
else
{
Statics.ChildProcessManager?.Dispose();
Statics.ChildProcessManager = null;
}
}

if (update && Settings.Values != null)
{
Settings.Flush();
}
}
}
[XmlIgnore] private Boolean _forceCloseSessionsOnCrash = true;

[Category("Servers"), DisplayName("Available keywords"), TypeConverter(typeof(CsvConverter)), XmlArrayItem(ElementName = "Keyword")]
public String[] Keywords
{
Expand Down

0 comments on commit 52c256c

Please sign in to comment.