264 lines
6.5 KiB
C++
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
|
|
}
|
|
} |