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.

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