feat: add callback for unhandled exception handler.

- add callback for unhandled exception handler to give programmer a chance to fetch log and coredump path, especially for GUI application because its stderr is invisible.
- fix fatal anto-recursive calling bug in unhandled exception handler.
This commit is contained in:
yyc12345 2024-07-29 21:42:27 +08:00
parent e8a0299fbc
commit 650fcd12ec
3 changed files with 56 additions and 8 deletions

View File

@ -26,6 +26,7 @@ namespace YYCC::ExceptionHelper {
ExceptionRegister() : ExceptionRegister() :
m_CoreMutex(), m_CoreMutex(),
m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr), m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr),
m_UserCallback(nullptr),
m_SingletonMutex(NULL) {} m_SingletonMutex(NULL) {}
~ExceptionRegister() { ~ExceptionRegister() {
Unregister(); Unregister();
@ -35,7 +36,7 @@ namespace YYCC::ExceptionHelper {
/** /**
* @brief Try to register unhandled exception handler. * @brief Try to register unhandled exception handler.
*/ */
void Register() { void Register(ExceptionCallback callback) {
std::lock_guard<std::mutex> locker(m_CoreMutex); std::lock_guard<std::mutex> locker(m_CoreMutex);
// if we have registered, return // if we have registered, return
if (m_IsRegistered) return; if (m_IsRegistered) return;
@ -63,6 +64,8 @@ namespace YYCC::ExceptionHelper {
// okey, we can register it. // okey, we can register it.
// backup old handler // backup old handler
m_PrevProcHandler = SetUnhandledExceptionFilter(UExceptionImpl); m_PrevProcHandler = SetUnhandledExceptionFilter(UExceptionImpl);
// set user callback
m_UserCallback = callback;
// mark registered // mark registered
m_IsRegistered = true; m_IsRegistered = true;
} }
@ -115,6 +118,14 @@ namespace YYCC::ExceptionHelper {
std::lock_guard<std::mutex> locker(m_CoreMutex); std::lock_guard<std::mutex> locker(m_CoreMutex);
return m_PrevProcHandler; return m_PrevProcHandler;
} }
/**
* @brief Get user specified callback.
* @return The function pointer to user callback. nullptr if no associated callback.
*/
ExceptionCallback GetUserCallback() const {
std::lock_guard<std::mutex> locker(m_CoreMutex);
return m_UserCallback;
}
/** /**
* @brief Try to start process unhandled exception. * @brief Try to start process unhandled exception.
@ -154,6 +165,12 @@ namespace YYCC::ExceptionHelper {
* True if it is, otherwise false. * True if it is, otherwise false.
*/ */
bool m_IsProcessing; bool m_IsProcessing;
/**
* @brief User defined callback.
* @details It will be called at the tail of unhandled exception handler, because it may raise exception.
* We must make sure all log and coredump have been done before calling it.
*/
ExceptionCallback m_UserCallback;
/** /**
* @brief The backup of old unhandled exception handler. * @brief The backup of old unhandled exception handler.
*/ */
@ -166,6 +183,7 @@ namespace YYCC::ExceptionHelper {
HANDLE m_SingletonMutex; HANDLE m_SingletonMutex;
}; };
/// @brief Core register singleton.
static ExceptionRegister g_ExceptionRegister; static ExceptionRegister g_ExceptionRegister;
#pragma region Exception Handler Implementation #pragma region Exception Handler Implementation
@ -505,10 +523,14 @@ namespace YYCC::ExceptionHelper {
// write crash coredump // write crash coredump
UExceptionCoreDump(coredump_path, info); UExceptionCoreDump(coredump_path, info);
// call user callback
ExceptionCallback user_callback = g_ExceptionRegister.GetUserCallback();
if (user_callback != nullptr)
user_callback(log_path, coredump_path);
} }
// stop process // stop process
g_ExceptionRegister.StartProcessing(); g_ExceptionRegister.StopProcessing();
end_proc: end_proc:
// if backup proc can be run, run it // if backup proc can be run, run it
@ -523,8 +545,8 @@ namespace YYCC::ExceptionHelper {
#pragma endregion #pragma endregion
void Register() { void Register(ExceptionCallback callback) {
g_ExceptionRegister.Register(); g_ExceptionRegister.Register(callback);
} }
void Unregister() { void Unregister() {

View File

@ -11,6 +11,24 @@
*/ */
namespace YYCC::ExceptionHelper { namespace YYCC::ExceptionHelper {
/**
* @brief The callback function prototype which will be called when unhandled exception happened after registering.
* @details
* During registering unhandled exception handler,
* caller can optionally provide a function pointer matching this prorotype to register.
* Then it will be called if unhandled exception hanppened.
*
* This callback will provide 2 readonly arguments.
* First is the path to error log file.
* Second is the path to core dump file.
* These pathes may be empty if internal handler fail to create them.
*
* This callback is convenient for programmer using an explicit way to tell user an exception happened.
* Because in default, handler will only write error log to \c stderr and file.
* It will be totally invisible on a GUI application.
*/
using ExceptionCallback = void(*)(const yycc_u8string& log_path, const yycc_u8string& coredump_path);
/** /**
* @brief Register unhandled exception handler * @brief Register unhandled exception handler
* @details * @details
@ -23,7 +41,7 @@ namespace YYCC::ExceptionHelper {
* *
* This function usually is called at the start of program. * This function usually is called at the start of program.
*/ */
void Register(); void Register(ExceptionCallback callback = nullptr);
/** /**
* @brief Unregister unhandled exception handler * @brief Unregister unhandled exception handler
* @details * @details

View File

@ -338,7 +338,15 @@ namespace YYCCTestbench {
static void ExceptionTestbench() { static void ExceptionTestbench() {
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
YYCC::ExceptionHelper::Register(); YYCC::ExceptionHelper::Register([](const YYCC::yycc_u8string& log_path, const YYCC::yycc_u8string& coredump_path) -> void {
MessageBoxW(
NULL,
YYCC::EncodingHelper::UTF8ToWchar(
YYCC::StringHelper::Printf(YYCC_U8("Log generated:\nLog path: %s\nCore dump path: %s"), log_path.c_str(), coredump_path.c_str())
).c_str(),
L"Fatal Error", MB_OK + MB_ICONERROR
);
});
// Perform a div zero exception. // Perform a div zero exception.
int i = 1, j = 0; int i = 1, j = 0;
@ -376,7 +384,7 @@ namespace YYCCTestbench {
std::filesystem::path test_path; std::filesystem::path test_path;
for (const auto& strl : c_UTF8TestStrTable) { for (const auto& strl : c_UTF8TestStrTable) {
test_path /= YYCC::FsPathPatch::FromUTF8Path(strl.c_str()); test_path /= YYCC::FsPathPatch::FromUTF8Path(strl.c_str());
} }
YYCC::yycc_u8string test_slashed_path(YYCC::FsPathPatch::ToUTF8Path(test_path)); YYCC::yycc_u8string test_slashed_path(YYCC::FsPathPatch::ToUTF8Path(test_path));
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
@ -389,7 +397,7 @@ namespace YYCCTestbench {
Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::FsPathPatch")); Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::FsPathPatch"));
} }
enum class TestEnum : int8_t { enum class TestEnum : int8_t {
Test1, Test2, Test3 Test1, Test2, Test3