/*** 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" #endif #include namespace Aurora::Exit { static AuThreadPrimitives::Mutex gMutex; static AuList, ETriggerLevel>> gTriggerSubscribers; static bool gIsAppRunning {true}; 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. } } } } void PostLevel(AuThreads::IAuroraThread *thread, ETriggerLevel level) { AU_DEBUG_MEMCRUNCH; bool bOldTerminatingValue; { AU_LOCK_GUARD(gMutex); bool isTerminate = level == ETriggerLevel::eSafeTermination; bool isPreempting {}; // Prevent double eSafeTermination if (isTerminate && gHasSentTerminate) { isPreempting = true; } // bOldTerminatingValue = gIsAppRunning; if (isTerminate || level == ETriggerLevel::eSigTerminate) { gIsAppRunning = false; } 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(); } gProblemCounter = 0; // Has already sent eSafeTermination? if (isPreempting) { return; } // ... gHasSentTerminate |= isTerminate; } // Force exit after calling the subscribers, should the level be eSigTerminate if (level == ETriggerLevel::eSigTerminate) { // HACK: if (gHasCanceled) { gIsAppRunning = bOldTerminatingValue; } else { Process::Exit(0); } } } AUKN_SYM bool ExitHandlerAdd(ETriggerLevel level, const AuSPtr &callback) { AU_LOCK_GUARD(gMutex); return AuTryInsert(gTriggerSubscribers, AuMakePair(callback, level)); } AUKN_SYM void ExitHandlerRemove(const AuSPtr &callback) { AU_LOCK_GUARD(gMutex); AuRemoveAllIf(gTriggerSubscribers, [=](const auto &entry) -> bool { return AuGet<0>(entry) == callback; }); } AUKN_SYM bool IsAppRunning() { return gIsAppRunning; } 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 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** :( SendExitSignal(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 } }