You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
6.0 KiB

  1. #nullable enable
  2. using System;
  3. using System.Runtime.InteropServices;
  4. using IPA.Logging;
  5. namespace IPA.Utilities
  6. {
  7. /// <summary>
  8. /// Provides utilities for managing various critical sections.
  9. /// </summary>
  10. public static class CriticalSection
  11. {
  12. internal static void Configure()
  13. {
  14. Logger.Default.Debug("Configuring exit handlers");
  15. ResetExitHandlers();
  16. }
  17. private static void Reset(object? sender, EventArgs? e)
  18. {
  19. _ = Win32.SetConsoleCtrlHandler(registeredHandler, false);
  20. WinHttp.SetPeekMessageHook(null);
  21. }
  22. #region Execute section
  23. private static readonly Win32.ConsoleCtrlDelegate registeredHandler = HandleExit;
  24. internal static void ResetExitHandlers()
  25. {
  26. _ = Win32.SetConsoleCtrlHandler(registeredHandler, false);
  27. _ = Win32.SetConsoleCtrlHandler(registeredHandler, true);
  28. WinHttp.SetPeekMessageHook(PeekMessageHook);
  29. AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
  30. AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
  31. }
  32. private static void OnProcessExit(object sender, EventArgs args)
  33. {
  34. WinHttp.SetIgnoreUnhandledExceptions(true);
  35. }
  36. private static class WinHttp
  37. {
  38. public delegate bool PeekMessageHook(
  39. bool isW,
  40. uint result,
  41. [MarshalAs(UnmanagedType.LPStruct)]
  42. in Win32.MSG message,
  43. IntPtr hwnd,
  44. uint filterMin,
  45. uint filterMax,
  46. ref Win32.PeekMessageParams removeMsg);
  47. [DllImport("bsipa-doorstop")]
  48. public static extern void SetPeekMessageHook(
  49. [MarshalAs(UnmanagedType.FunctionPtr)]
  50. PeekMessageHook? hook);
  51. [DllImport("bsipa-doorstop")]
  52. public static extern void SetIgnoreUnhandledExceptions(
  53. [MarshalAs(UnmanagedType.Bool)] bool ignore);
  54. }
  55. private static Win32.ConsoleCtrlDelegate? _handler = null;
  56. private static volatile bool isInExecuteSection = false;
  57. // returns true to continue looping and calling PeekMessage
  58. private static bool PeekMessageHook(
  59. bool isW,
  60. uint result,
  61. [MarshalAs(UnmanagedType.LPStruct)]
  62. in Win32.MSG message,
  63. IntPtr hwnd,
  64. uint filterMin,
  65. uint filterMax,
  66. ref Win32.PeekMessageParams removeMsg)
  67. {
  68. if (isInExecuteSection)
  69. {
  70. if (result == 0) return false;
  71. switch (message.message)
  72. {
  73. case Win32.WM.CLOSE:
  74. if (removeMsg != Win32.PeekMessageParams.PM_REMOVE)
  75. {
  76. removeMsg = Win32.PeekMessageParams.PM_REMOVE;
  77. exitRecieved = true;
  78. return true;
  79. }
  80. else
  81. {
  82. removeMsg = Win32.PeekMessageParams.PM_NOREMOVE;
  83. return true;
  84. }
  85. default:
  86. return false;
  87. }
  88. }
  89. return false;
  90. }
  91. private static bool HandleExit(Win32.CtrlTypes type)
  92. {
  93. if (_handler != null)
  94. return _handler(type);
  95. return false;
  96. }
  97. private static volatile bool exitRecieved = false;
  98. /// <summary>
  99. /// A struct that allows <c>using</c> blocks to manage an execute section.
  100. /// </summary>
  101. public struct AutoExecuteSection : IDisposable
  102. {
  103. private readonly bool constructed;
  104. internal AutoExecuteSection(bool val)
  105. {
  106. constructed = val && !isInExecuteSection;
  107. if (constructed)
  108. EnterExecuteSection();
  109. }
  110. void IDisposable.Dispose()
  111. {
  112. if (constructed)
  113. ExitExecuteSection();
  114. }
  115. }
  116. /// <summary>
  117. /// Creates an <see cref="AutoExecuteSection"/> for automated management of an execute section.
  118. /// </summary>
  119. /// <returns>the new <see cref="AutoExecuteSection"/> that manages the section</returns>
  120. public static AutoExecuteSection ExecuteSection() => new AutoExecuteSection(true);
  121. /// <summary>
  122. /// Enters a critical execution section. Does not nest.
  123. /// </summary>
  124. /// <note>
  125. /// During a critical execution section, the program must execute until the end of the section before
  126. /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
  127. /// will terminate at the end of the section.
  128. /// </note>
  129. public static void EnterExecuteSection()
  130. {
  131. ResetExitHandlers();
  132. exitRecieved = false;
  133. _handler = sig => exitRecieved = true;
  134. isInExecuteSection = true;
  135. }
  136. /// <summary>
  137. /// Exits a critical execution section. Does not nest.
  138. /// </summary>
  139. /// <note>
  140. /// During a critical execution section, the program must execute until the end of the section before
  141. /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
  142. /// will terminate at the end of the section.
  143. /// </note>
  144. public static void ExitExecuteSection()
  145. {
  146. _handler = null;
  147. isInExecuteSection = false;
  148. Reset(null, null);
  149. if (exitRecieved)
  150. Environment.Exit(1);
  151. }
  152. #endregion
  153. }
  154. }