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.

173 lines
5.5 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. }
  33. private static class WinHttp
  34. {
  35. public delegate bool PeekMessageHook(
  36. bool isW,
  37. uint result,
  38. [MarshalAs(UnmanagedType.LPStruct)]
  39. in Win32.MSG message,
  40. IntPtr hwnd,
  41. uint filterMin,
  42. uint filterMax,
  43. ref Win32.PeekMessageParams removeMsg);
  44. [DllImport("bsipa-doorstop")]
  45. public static extern void SetPeekMessageHook(
  46. [MarshalAs(UnmanagedType.FunctionPtr)]
  47. PeekMessageHook hook);
  48. }
  49. private static Win32.ConsoleCtrlDelegate _handler = null;
  50. private static volatile bool isInExecuteSection = false;
  51. // returns true to continue looping and calling PeekMessage
  52. private static bool PeekMessageHook(
  53. bool isW,
  54. uint result,
  55. [MarshalAs(UnmanagedType.LPStruct)]
  56. in Win32.MSG message,
  57. IntPtr hwnd,
  58. uint filterMin,
  59. uint filterMax,
  60. ref Win32.PeekMessageParams removeMsg)
  61. {
  62. if (isInExecuteSection)
  63. {
  64. if (result == 0) return false;
  65. switch (message.message)
  66. {
  67. case Win32.WM.CLOSE:
  68. if (removeMsg != Win32.PeekMessageParams.PM_REMOVE)
  69. {
  70. removeMsg = Win32.PeekMessageParams.PM_REMOVE;
  71. exitRecieved = true;
  72. return true;
  73. }
  74. else
  75. {
  76. removeMsg = Win32.PeekMessageParams.PM_NOREMOVE;
  77. return true;
  78. }
  79. default:
  80. return false;
  81. }
  82. }
  83. return false;
  84. }
  85. private static bool HandleExit(Win32.CtrlTypes type)
  86. {
  87. if (_handler != null)
  88. return _handler(type);
  89. return false;
  90. }
  91. private static volatile bool exitRecieved = false;
  92. /// <summary>
  93. /// A struct that allows <c>using</c> blocks to manage an execute section.
  94. /// </summary>
  95. public struct AutoExecuteSection : IDisposable
  96. {
  97. private readonly bool constructed;
  98. internal AutoExecuteSection(bool val)
  99. {
  100. constructed = val && !isInExecuteSection;
  101. if (constructed)
  102. EnterExecuteSection();
  103. }
  104. void IDisposable.Dispose()
  105. {
  106. if (constructed)
  107. ExitExecuteSection();
  108. }
  109. }
  110. /// <summary>
  111. /// Creates an <see cref="AutoExecuteSection"/> for automated management of an execute section.
  112. /// </summary>
  113. /// <returns>the new <see cref="AutoExecuteSection"/> that manages the section</returns>
  114. public static AutoExecuteSection ExecuteSection() => new AutoExecuteSection(true);
  115. /// <summary>
  116. /// Enters a critical execution section. Does not nest.
  117. /// </summary>
  118. /// <note>
  119. /// During a critical execution section, the program must execute until the end of the section before
  120. /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
  121. /// will terminate at the end of the section.
  122. /// </note>
  123. public static void EnterExecuteSection()
  124. {
  125. ResetExitHandlers();
  126. exitRecieved = false;
  127. _handler = sig => exitRecieved = true;
  128. isInExecuteSection = true;
  129. }
  130. /// <summary>
  131. /// Exits a critical execution section. Does not nest.
  132. /// </summary>
  133. /// <note>
  134. /// During a critical execution section, the program must execute until the end of the section before
  135. /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
  136. /// will terminate at the end of the section.
  137. /// </note>
  138. public static void ExitExecuteSection()
  139. {
  140. _handler = null;
  141. isInExecuteSection = false;
  142. Reset(null, null);
  143. if (exitRecieved)
  144. Environment.Exit(1);
  145. }
  146. #endregion
  147. }
  148. }