AuroraRuntime/Source/Exit/AuExit.cpp
Jamie Reece Wilson 0571aa8dd4 [+] AU_LOCK_GLOBAL_GUARD
[+] AuThreading::GetShutdownReadLock()
2024-09-09 03:46:38 +01:00

264 lines
6.5 KiB
C++

/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: AuExit.cpp
Date: 2022-2-5
Author: Reece
***/
#include <Source/RuntimeInternal.hpp>
#include "AuExit.hpp"
#include <Source/Grug/AuGrug.hpp>
#include "AuMTWatchDog.hpp"
#if defined(AURORA_IS_POSIX_DERIVED)
#include "AuExit.Unix.hpp"
namespace Aurora::Processes
{
void PosixProcessShutdown();
}
#endif
#include <Source/Debug/MemoryCrunch.hpp>
namespace Aurora::Console::ConsoleTTY
{
void LeaveScrollMode();
void Exit();
}
namespace Aurora::Exit
{
static AuMutex gMutex;
static AuList<AuTuple<AuSPtr<IExitSubscriber>, 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<IExitSubscriber> &callback)
{
if (!ETriggerLevelIsValid(level))
{
return false;
}
AU_LOCK_GLOBAL_GUARD(gMutex);
return AuTryInsert(gTriggerSubscribers, AuMakePair(callback, level));
}
AUKN_SYM void ExitHandlerRemove(const AuSPtr<IExitSubscriber> &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
}
}