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.

185 lines
5.9 KiB

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