AuroraRuntime/Source/Exit/AuExit.cpp

209 lines
5.2 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"
#endif
#include <Source/Debug/MemoryCrunch.hpp>
namespace Aurora::Exit
{
static AuThreadPrimitives::MutexUnique_t gMutex;
static AuList<AuTuple<AuSPtr<IExitSubscriber>, 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)
{
bool bOldTerminatingValue;
AuDebug::AddMemoryCrunch();
{
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))
{
AuDebug::DecMemoryCrunch();
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)
{
AuDebug::DecMemoryCrunch();
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);
}
}
AuDebug::DecMemoryCrunch();
}
AUKN_SYM bool ExitHandlerAdd(ETriggerLevel level, const AuSPtr<IExitSubscriber> &callback)
{
AU_LOCK_GUARD(gMutex);
return AuTryInsert(gTriggerSubscribers, AuMakePair(callback, level));
}
AUKN_SYM void ExitHandlerRemove(const AuSPtr<IExitSubscriber> &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()
{
gMutex = AuThreadPrimitives::MutexUnique();
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
gMutex.reset();
}
}