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.

187 lines
6.0 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. /// Enters a critical execution section. Does not nest.
  94. /// </summary>
  95. /// <note>
  96. /// During a critical execution section, the program must execute until the end of the section before
  97. /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
  98. /// will terminate at the end of the section.
  99. /// </note>
  100. public static void EnterExecuteSection()
  101. {
  102. ResetExitHandlers();
  103. exitRecieved = false;
  104. _handler = sig => exitRecieved = true;
  105. isInExecuteSection = true;
  106. }
  107. /// <summary>
  108. /// Exits a critical execution section. Does not nest.
  109. /// </summary>
  110. /// <note>
  111. /// During a critical execution section, the program must execute until the end of the section before
  112. /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
  113. /// will terminate at the end of the section.
  114. /// </note>
  115. public static void ExitExecuteSection()
  116. {
  117. _handler = null;
  118. isInExecuteSection = false;
  119. Reset(null, null);
  120. if (exitRecieved)
  121. Environment.Exit(1);
  122. }
  123. #endregion
  124. #region GC section
  125. // i wish i could reference GC_enable and GC_disable directly
  126. [DllImport("mono-2.0-bdwgc")]
  127. private static extern void mono_unity_gc_enable();
  128. [DllImport("mono-2.0-bdwgc")]
  129. private static extern void mono_unity_gc_disable();
  130. /// <summary>
  131. /// Enters a GC critical section. Each call to this must be paired with a call to <see cref="ExitGCSection"/>.
  132. /// </summary>
  133. /// <note>
  134. /// During a GC critical section, no GCs will occur.
  135. ///
  136. /// This may throw an <see cref="EntryPointNotFoundException"/> if the build of Mono the game is running on does
  137. /// not have `mono_unity_gc_disable` exported. Use with caution.
  138. /// </note>
  139. public static void EnterGCSection()
  140. {
  141. mono_unity_gc_disable();
  142. }
  143. /// <summary>
  144. /// Exits a GC critical section. Each call to this must have a preceding call to <see cref="EnterGCSection"/>.
  145. /// </summary>
  146. /// <note>
  147. /// During a GC critical section, no GCs will occur.
  148. ///
  149. /// This may throw an <see cref="EntryPointNotFoundException"/> if the build of Mono the game is running on does
  150. /// not have `mono_unity_gc_enable` exported. Use with caution.
  151. /// </note>
  152. public static void ExitGCSection()
  153. {
  154. mono_unity_gc_enable();
  155. }
  156. #endregion
  157. }
  158. }