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.

188 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. AppDomain.CurrentDomain.ProcessExit -= Reset;
  19. AppDomain.CurrentDomain.ProcessExit += Reset;
  20. ResetExitHandlers();
  21. }
  22. private static void Reset(object sender, EventArgs e)
  23. {
  24. Win32.SetConsoleCtrlHandler(registeredHandler, false);
  25. WinHttp.SetPeekMessageHook(null);
  26. }
  27. #region Execute section
  28. private static readonly Win32.ConsoleCtrlDelegate registeredHandler = HandleExit;
  29. internal static void ResetExitHandlers()
  30. {
  31. Win32.SetConsoleCtrlHandler(registeredHandler, false);
  32. Win32.SetConsoleCtrlHandler(registeredHandler, true);
  33. WinHttp.SetPeekMessageHook(PeekMessageHook);
  34. }
  35. private static class WinHttp
  36. {
  37. public delegate bool PeekMessageHook(
  38. bool isW,
  39. uint result,
  40. [MarshalAs(UnmanagedType.LPStruct)]
  41. in Win32.MSG message,
  42. IntPtr hwnd,
  43. uint filterMin,
  44. uint filterMax,
  45. ref Win32.PeekMessageParams removeMsg);
  46. [DllImport("winhttp")]
  47. public static extern void SetPeekMessageHook(
  48. [MarshalAs(UnmanagedType.FunctionPtr)]
  49. PeekMessageHook hook);
  50. }
  51. private static Win32.ConsoleCtrlDelegate _handler = null;
  52. private static volatile bool isInExecuteSection = false;
  53. // returns true to continue looping and calling PeekMessage
  54. private static bool PeekMessageHook(
  55. bool isW,
  56. uint result,
  57. [MarshalAs(UnmanagedType.LPStruct)]
  58. in Win32.MSG message,
  59. IntPtr hwnd,
  60. uint filterMin,
  61. uint filterMax,
  62. ref Win32.PeekMessageParams removeMsg)
  63. {
  64. if (isInExecuteSection)
  65. {
  66. if (result == 0) return false;
  67. switch (message.message)
  68. {
  69. case Win32.WM.CLOSE:
  70. if (removeMsg != Win32.PeekMessageParams.PM_REMOVE)
  71. {
  72. removeMsg = Win32.PeekMessageParams.PM_REMOVE;
  73. exitRecieved = true;
  74. return true;
  75. }
  76. else
  77. {
  78. removeMsg = Win32.PeekMessageParams.PM_NOREMOVE;
  79. return true;
  80. }
  81. default:
  82. return false;
  83. }
  84. }
  85. return false;
  86. }
  87. private static bool HandleExit(Win32.CtrlTypes type)
  88. {
  89. if (_handler != null)
  90. return _handler(type);
  91. return false;
  92. }
  93. private static volatile bool exitRecieved = false;
  94. /// <summary>
  95. /// Enters a critical execution section. Does not nest.
  96. /// </summary>
  97. /// <note>
  98. /// During a critical execution section, the program must execute until the end of the section before
  99. /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
  100. /// will terminate at the end of the section.
  101. /// </note>
  102. public static void EnterExecuteSection()
  103. {
  104. ResetExitHandlers();
  105. exitRecieved = false;
  106. _handler = sig => exitRecieved = true;
  107. isInExecuteSection = true;
  108. }
  109. /// <summary>
  110. /// Exits a critical execution section. Does not nest.
  111. /// </summary>
  112. /// <note>
  113. /// During a critical execution section, the program must execute until the end of the section before
  114. /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
  115. /// will terminate at the end of the section.
  116. /// </note>
  117. public static void ExitExecuteSection()
  118. {
  119. _handler = null;
  120. isInExecuteSection = false;
  121. if (exitRecieved)
  122. Environment.Exit(1);
  123. }
  124. #endregion
  125. #region GC section
  126. // i wish i could reference GC_enable and GC_disable directly
  127. [DllImport("mono-2.0-bdwgc")]
  128. private static extern void mono_unity_gc_enable();
  129. [DllImport("mono-2.0-bdwgc")]
  130. private static extern void mono_unity_gc_disable();
  131. /// <summary>
  132. /// Enters a GC critical section. Each call to this must be paired with a call to <see cref="ExitGCSection"/>.
  133. /// </summary>
  134. /// <note>
  135. /// During a GC critical section, no GCs will occur.
  136. ///
  137. /// This may throw an <see cref="EntryPointNotFoundException"/> if the build of Mono the game is running on does
  138. /// not have `mono_unity_gc_disable` exported. Use with caution.
  139. /// </note>
  140. public static void EnterGCSection()
  141. {
  142. mono_unity_gc_disable();
  143. }
  144. /// <summary>
  145. /// Exits a GC critical section. Each call to this must have a preceding call to <see cref="EnterGCSection"/>.
  146. /// </summary>
  147. /// <note>
  148. /// During a GC critical section, no GCs will occur.
  149. ///
  150. /// This may throw an <see cref="EntryPointNotFoundException"/> if the build of Mono the game is running on does
  151. /// not have `mono_unity_gc_enable` exported. Use with caution.
  152. /// </note>
  153. public static void ExitGCSection()
  154. {
  155. mono_unity_gc_enable();
  156. }
  157. #endregion
  158. }
  159. }