/*** Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: AuExit.cpp Date: 2022-2-5 Author: Reece ***/ #include #include "AuExit.hpp" #include #include "AuMTWatchDog.hpp" #if defined(AURORA_IS_POSIX_DERIVED) #include "AuExit.Unix.hpp" namespace Aurora::Processes { void PosixProcessShutdown(); } #endif #include namespace Aurora::Console::ConsoleTTY { void LeaveScrollMode(); void Exit(); } namespace Aurora::Exit { static AuMutex gMutex; static AuList, ETriggerLevel>> gTriggerSubscribers; static bool gIsAppRunning { true }; static bool gIsAppForceTerminating { false }; static void DispatchHandlersForThread(AuThreads::IAuroraThread *pThread, ETriggerLevel level) { ExitInvoker invoker; invoker.pCaller = pThread; for (const auto &[sub, subLevel] : gTriggerSubscribers) { if (level == subLevel) { try { sub->OnTrigger(level, &invoker); } catch (...) { // Intentionally left empty. Telemetry hooks will report, if possible. } } } } static void UnsafeProcessShutdownHook() { #if defined(AURORA_IS_POSIX_DERIVED) Processes::PosixProcessShutdown(); #endif } void PostLevel(AuThreads::IAuroraThread *thread, ETriggerLevel level) { AU_DEBUG_MEMCRUNCH; bool bOldTerminatingValue; { AU_LOCK_GLOBAL_GUARD(gMutex); bool isTerminate = level == ETriggerLevel::eSafeTermination; bool isPreempting {}; // Prevent double eSafeTermination if (isTerminate && gHasSentTerminate) { isPreempting = true; } // bOldTerminatingValue = gIsAppRunning; if (isTerminate || level == ETriggerLevel::eSigTerminate || level == ETriggerLevel::eSigQuitNow) { gIsAppRunning = false; } if (level == ETriggerLevel::eSigQuitNow) { gIsAppForceTerminating = true; } static AuUInt32 gProblemCounter = {}; // Mitigate reused stack, nested try/catch, spam if (level == ETriggerLevel::eProblematicEvent) { if (AuAtomicTestAndSet(&gProblemCounter, 1)) { return; } } // HACK: gHasCanceled = false; // Dispatch DispatchHandlersForThread(thread, level); // ... if (level == ETriggerLevel::eProblematicEvent) { Grug::NotifyGrugOfLogs(); } else { Grug::GrugFlushWrites(); Grug::GrugFlushFlushs(); #if defined(AURORA_PLATFORM_WIN32) Aurora::Console::ConsoleTTY::LeaveScrollMode(); #endif Aurora::Console::ConsoleTTY::Exit(); } gProblemCounter = 0; // Has already sent eSafeTermination? if (isPreempting) { return; } // ... gHasSentTerminate |= isTerminate; } if (gIsAppForceTerminating) { gHasCanceled = false; } if ((level == ETriggerLevel::eSigTerminate && !gHasCanceled) || level == ETriggerLevel::eFatalException || level == ETriggerLevel::eSafeTermination|| level == ETriggerLevel::eSigQuitNow) { UnsafeProcessShutdownHook(); } // Force exit after calling the subscribers, should the level be eSigTerminate if (level == ETriggerLevel::eSigTerminate || level == ETriggerLevel::eSigQuitNow) { // HACK: if (gHasCanceled && !gIsAppForceTerminating) { gIsAppRunning = bOldTerminatingValue; } else { Process::Exit(0); } } } AUKN_SYM bool ExitHandlerAdd(ETriggerLevel level, const AuSPtr &callback) { if (!ETriggerLevelIsValid(level)) { return false; } AU_LOCK_GLOBAL_GUARD(gMutex); return AuTryInsert(gTriggerSubscribers, AuMakePair(callback, level)); } AUKN_SYM void ExitHandlerRemove(const AuSPtr &callback) { AU_LOCK_GLOBAL_GUARD(gMutex); AuRemoveAllIf(gTriggerSubscribers, [=](const auto &entry) -> bool { return AuGet<0>(entry) == callback; }); } AUKN_SYM bool IsAppRunning() { return gIsAppRunning && !gIsAppForceTerminating; } AUKN_SYM void CancelExit() { // HACK: gHasCanceled = true; } #if defined(AURORA_PLATFORM_WIN32) static void SendExitSignal(Grug::Arrow *) { PostLevel(AuThreads::GetThread(), Exit::ETriggerLevel::eSigTerminate); } static void SendExitNowSignal(Grug::Arrow *) { PostLevel(AuThreads::GetThread(), Exit::ETriggerLevel::eSigQuitNow); } static void SendTerminateSignalAndBlock() { static Grug::Arrow arrow; Grug::HurlArrow(&arrow, SendExitSignal, {}); Grug::ArrowWait(&arrow); } BOOL Win32BasicHandler(DWORD ctrlType) { if (ctrlType == CTRL_C_EVENT || ctrlType == CTRL_CLOSE_EVENT || ctrlType == CTRL_SHUTDOWN_EVENT) { Exit::gHasCanceled = false; //SendTerminateSignalAndBlock();// for some reason we aren't getting win32 console flushes **reliably** :( if (ctrlType == CTRL_C_EVENT) { SendExitSignal(nullptr); } else { SendExitNowSignal(nullptr); } return !AuExchange(Exit::gHasCanceled, false); } return true; } #endif void InitExit() { InitWatchdog(); #if defined(AURORA_IS_POSIX_DERIVED) InitUnix(); #endif #if defined(AURORA_PLATFORM_WIN32) ::SetConsoleCtrlHandler((PHANDLER_ROUTINE)Win32BasicHandler, TRUE); #endif } void DeinitExit() { #if defined(AURORA_IS_POSIX_DERIVED) DeinitUnix(); #endif } }