|
|
- #nullable enable
- using System;
- using System.Runtime.InteropServices;
- using IPA.Logging;
-
- namespace IPA.Utilities
- {
- /// <summary>
- /// Provides utilities for managing various critical sections.
- /// </summary>
- public static class CriticalSection
- {
-
- internal static void Configure()
- {
- Logger.Default.Debug("Configuring exit handlers");
-
- ResetExitHandlers();
- }
-
- private static void Reset(object? sender, EventArgs? e)
- {
- _ = Win32.SetConsoleCtrlHandler(registeredHandler, false);
- WinHttp.SetPeekMessageHook(null);
- }
-
- #region Execute section
-
- private static readonly Win32.ConsoleCtrlDelegate registeredHandler = HandleExit;
- internal static void ResetExitHandlers()
- {
- _ = Win32.SetConsoleCtrlHandler(registeredHandler, false);
- _ = Win32.SetConsoleCtrlHandler(registeredHandler, true);
- WinHttp.SetPeekMessageHook(PeekMessageHook);
-
- AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
- AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
- }
-
- private static void OnProcessExit(object sender, EventArgs args)
- {
- WinHttp.SetIgnoreUnhandledExceptions(true);
- }
-
- private static class WinHttp
- {
- public delegate bool PeekMessageHook(
- bool isW,
- uint result,
- [MarshalAs(UnmanagedType.LPStruct)]
- in Win32.MSG message,
- IntPtr hwnd,
- uint filterMin,
- uint filterMax,
- ref Win32.PeekMessageParams removeMsg);
-
- [DllImport("bsipa-doorstop")]
- public static extern void SetPeekMessageHook(
- [MarshalAs(UnmanagedType.FunctionPtr)]
- PeekMessageHook? hook);
-
- [DllImport("bsipa-doorstop")]
- public static extern void SetIgnoreUnhandledExceptions(
- [MarshalAs(UnmanagedType.Bool)] bool ignore);
- }
-
- private static Win32.ConsoleCtrlDelegate? _handler = null;
- private static volatile bool isInExecuteSection = false;
-
- // returns true to continue looping and calling PeekMessage
- private static bool PeekMessageHook(
- bool isW,
- uint result,
- [MarshalAs(UnmanagedType.LPStruct)]
- in Win32.MSG message,
- IntPtr hwnd,
- uint filterMin,
- uint filterMax,
- ref Win32.PeekMessageParams removeMsg)
- {
- if (isInExecuteSection)
- {
- if (result == 0) return false;
-
- switch (message.message)
- {
- case Win32.WM.CLOSE:
- if (removeMsg != Win32.PeekMessageParams.PM_REMOVE)
- {
- removeMsg = Win32.PeekMessageParams.PM_REMOVE;
- exitRecieved = true;
- return true;
- }
- else
- {
- removeMsg = Win32.PeekMessageParams.PM_NOREMOVE;
- return true;
- }
-
- default:
- return false;
- }
- }
-
- return false;
- }
-
- private static bool HandleExit(Win32.CtrlTypes type)
- {
- if (_handler != null)
- return _handler(type);
-
- return false;
- }
-
- private static volatile bool exitRecieved = false;
-
- /// <summary>
- /// A struct that allows <c>using</c> blocks to manage an execute section.
- /// </summary>
- public struct AutoExecuteSection : IDisposable
- {
- private readonly bool constructed;
- internal AutoExecuteSection(bool val)
- {
- constructed = val && !isInExecuteSection;
- if (constructed)
- EnterExecuteSection();
- }
-
- void IDisposable.Dispose()
- {
- if (constructed)
- ExitExecuteSection();
- }
- }
-
- /// <summary>
- /// Creates an <see cref="AutoExecuteSection"/> for automated management of an execute section.
- /// </summary>
- /// <returns>the new <see cref="AutoExecuteSection"/> that manages the section</returns>
- public static AutoExecuteSection ExecuteSection() => new AutoExecuteSection(true);
-
- /// <summary>
- /// Enters a critical execution section. Does not nest.
- /// </summary>
- /// <note>
- /// During a critical execution section, the program must execute until the end of the section before
- /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
- /// will terminate at the end of the section.
- /// </note>
- public static void EnterExecuteSection()
- {
- ResetExitHandlers();
-
- exitRecieved = false;
- _handler = sig => exitRecieved = true;
- isInExecuteSection = true;
- }
-
- /// <summary>
- /// Exits a critical execution section. Does not nest.
- /// </summary>
- /// <note>
- /// During a critical execution section, the program must execute until the end of the section before
- /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
- /// will terminate at the end of the section.
- /// </note>
- public static void ExitExecuteSection()
- {
- _handler = null;
- isInExecuteSection = false;
-
- Reset(null, null);
-
- if (exitRecieved)
- Environment.Exit(1);
- }
-
- #endregion
- }
- }
|