Add engine fatal error types handling

Add general out-of-memory handling
Add safety memory buffer for crash or out of memory handling
Refactor Globals exit/error state to be in Engine class
This commit is contained in:
Wojtek Figat
2025-01-24 20:07:12 +01:00
parent fa2f2e3104
commit cf40facefe
20 changed files with 166 additions and 159 deletions

View File

@@ -32,6 +32,7 @@
#endif
#define PLATFORM_TYPE PlatformType::Android
#define PLATFORM_CACHE_LINE_SIZE 64
#define PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE (64ull * 1024) // 64 kB
#define USE_MONO_AOT_MODE MONO_AOT_MODE_NONE

View File

@@ -49,6 +49,7 @@ float PlatformBase::CustomDpiScale = 1.0f;
Array<User*, FixedAllocation<8>> PlatformBase::Users;
Delegate<User*> PlatformBase::UserAdded;
Delegate<User*> PlatformBase::UserRemoved;
void* OutOfMemoryBuffer = nullptr;
const Char* ToString(NetworkConnectionType value)
{
@@ -150,6 +151,12 @@ bool PlatformBase::Init()
srand((unsigned int)Platform::GetTimeCycles());
// Preallocate safety dynamic buffer to be released before Out Of Memory reporting to ensure code can properly execute
#ifndef PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE
#define PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE (1ull * 1024 * 1024) // 1 MB
#endif
OutOfMemoryBuffer = Allocator::Allocate(PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE);
return false;
}
@@ -188,6 +195,8 @@ void PlatformBase::BeforeExit()
void PlatformBase::Exit()
{
Allocator::Free(OutOfMemoryBuffer);
OutOfMemoryBuffer = nullptr;
}
#if COMPILE_WITH_PROFILER
@@ -257,25 +266,35 @@ bool PlatformBase::Is64BitApp()
#endif
}
void PlatformBase::Fatal(const Char* msg, void* context)
void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType error)
{
// Check if is already during fatal state
if (Globals::FatalErrorOccurred)
if (Engine::FatalError != FatalErrorType::None)
{
// Just send one more error to the log and back
LOG(Error, "Error after fatal error: {0}", msg);
return;
}
// Free OOM safety buffer
Allocator::Free(OutOfMemoryBuffer);
OutOfMemoryBuffer = nullptr;
// Set flags
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
Globals::FatalErrorOccurred = true;
Globals::IsRequestingExit = true;
Globals::ExitCode = -1;
Globals::ExitCode = -Math::Max((int32)error, 1);
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
Engine::IsRequestingExit = true;
Engine::ExitCode = -Math::Max((int32)error, 1);
Engine::FatalError = error;
Engine::RequestingExit();
// Collect crash info (platform-dependant implementation that might collect stack trace and/or create memory dump)
{
// Log separation for crash info
LOG_FLUSH();
Log::Logger::WriteFloor();
LOG(Error, "");
LOG(Error, "Critical error! Reason: {0}", msg);
@@ -358,11 +377,11 @@ void PlatformBase::Fatal(const Char* msg, void* context)
// Only main thread can call exit directly
if (IsInMainThread())
{
Engine::Exit(-1);
Engine::Exit(Engine::ExitCode, error);
}
}
void PlatformBase::Error(const Char* msg)
void PlatformBase::Error(const StringView& msg)
{
#if PLATFORM_HAS_HEADLESS_MODE
if (CommandLine::Options.Headless.IsTrue())
@@ -372,7 +391,7 @@ void PlatformBase::Error(const Char* msg)
ansi += PLATFORM_LINE_TERMINATOR;
printf("Error: %s\n", ansi.Get());
#else
std::cout << "Error: " << msg << std::endl;
std::cout << "Error: " << *msg << std::endl;
#endif
}
else
@@ -382,12 +401,12 @@ void PlatformBase::Error(const Char* msg)
}
}
void PlatformBase::Warning(const Char* msg)
void PlatformBase::Warning(const StringView& msg)
{
#if PLATFORM_HAS_HEADLESS_MODE
if (CommandLine::Options.Headless.IsTrue())
{
std::cout << "Warning: " << msg << std::endl;
std::cout << "Warning: " << *msg << std::endl;
}
else
#endif
@@ -396,12 +415,12 @@ void PlatformBase::Warning(const Char* msg)
}
}
void PlatformBase::Info(const Char* msg)
void PlatformBase::Info(const StringView& msg)
{
#if PLATFORM_HAS_HEADLESS_MODE
if (CommandLine::Options.Headless.IsTrue())
{
std::cout << "Info: " << msg << std::endl;
std::cout << "Info: " << *msg << std::endl;
}
else
#endif
@@ -410,24 +429,9 @@ void PlatformBase::Info(const Char* msg)
}
}
void PlatformBase::Fatal(const StringView& msg)
void PlatformBase::Fatal(const StringView& msg, FatalErrorType error)
{
Fatal(*msg);
}
void PlatformBase::Error(const StringView& msg)
{
Error(*msg);
}
void PlatformBase::Warning(const StringView& msg)
{
Warning(*msg);
}
void PlatformBase::Info(const StringView& msg)
{
Info(*msg);
Fatal(msg, nullptr, error);
}
void PlatformBase::Log(const StringView& msg)
@@ -443,28 +447,43 @@ void PlatformBase::Crash(int32 line, const char* file)
{
const StringAsUTF16<256> fileUTF16(file);
const String msg = String::Format(TEXT("Fatal crash!\nFile: {0}\nLine: {1}"), fileUTF16.Get(), line);
LOG_STR(Fatal, msg);
LOG_STR(Error, msg);
Fatal(msg, nullptr, FatalErrorType::Assertion);
}
void PlatformBase::OutOfMemory(int32 line, const char* file)
{
const StringAsUTF16<256> fileUTF16(file);
const String msg = String::Format(TEXT("Out of memory error!\nFile: {0}\nLine: {1}"), fileUTF16.Get(), line);
LOG_STR(Fatal, msg);
fmt_flax::allocator allocator;
fmt_flax::memory_buffer buffer(allocator);
static_assert(fmt::inline_buffer_size > 300, "Update stack buffer to prevent dynamic memory allocation on Out Of Memory.");
if (file)
{
const StringAsUTF16<256> fileUTF16(file);
fmt_flax::format(buffer, TEXT("Out of memory error!\nFile: {0}\nLine: {1}"), fileUTF16.Get(), line);
}
else
{
fmt_flax::format(buffer, TEXT("Out of memory error!"));
}
const StringView msg(buffer.data(), (int32)buffer.size());
LOG_STR(Error, msg);
Fatal(msg, nullptr, FatalErrorType::OutOfMemory);
}
void PlatformBase::MissingCode(int32 line, const char* file, const char* info)
{
const StringAsUTF16<256> fileUTF16(file);
const String msg = String::Format(TEXT("TODO: {0}\nFile: {1}\nLine: {2}"), String(info), fileUTF16.Get(), line);
LOG_STR(Fatal, msg);
LOG_STR(Error, msg);
Fatal(msg, nullptr, FatalErrorType::Assertion);
}
void PlatformBase::Assert(const char* message, const char* file, int line)
{
const StringAsUTF16<256> fileUTF16(file);
const String msg = String::Format(TEXT("Assertion failed!\nFile: {0}\nLine: {1}\n\nExpression: {2}"), fileUTF16.Get(), line, String(message));
LOG_STR(Fatal, msg);
LOG_STR(Error, msg);
Fatal(msg, nullptr, FatalErrorType::Assertion);
}
void PlatformBase::CheckFailed(const char* message, const char* file, int line)

View File

@@ -125,6 +125,23 @@ enum class ThreadPriority
extern FLAXENGINE_API const Char* ToString(ThreadPriority value);
/// <summary>
/// Possible fatal error types that cause engine exit.
/// </summary>
API_ENUM() enum class FatalErrorType
{
// No fatal error set.
None,
// Not defined or custom error.
Unknown,
// Runtime exception caught by the handler (eg. stack overflow, invalid memory address access).
Exception,
// Data assertion failed (eg. invalid value or code usage).
Assertion,
// Program run out of memory to allocate.
OutOfMemory,
};
API_INJECT_CODE(cpp, "#include \"Engine/Platform/Platform.h\"");
/// <summary>
@@ -457,32 +474,15 @@ public:
/// </summary>
/// <param name="msg">The message content.</param>
/// <param name="context">The platform-dependent context for the stack trace collecting (eg. platform exception info).</param>
static void Fatal(const Char* msg, void* context = nullptr);
/// <param name="error">The fatal error type.</param>
API_FUNCTION() static void Fatal(const StringView& msg, void* context, FatalErrorType error = FatalErrorType::Unknown);
/// <summary>
/// Shows the error message to the user.
/// </summary>
/// <param name="msg">The message content.</param>
static void Error(const Char* msg);
/// <summary>
/// Shows the warning message to the user.
/// </summary>
/// <param name="msg">The message content.</param>
static void Warning(const Char* msg);
/// <summary>
/// Shows the information message to the user.
/// </summary>
/// <param name="msg">The message content.</param>
static void Info(const Char* msg);
public:
/// <summary>
/// Shows the fatal error message to the user.
/// </summary>
/// <param name="msg">The message content.</param>
API_FUNCTION() static void Fatal(const StringView& msg);
/// <param name="error">The fatal error type.</param>
API_FUNCTION() static void Fatal(const StringView& msg, FatalErrorType error = FatalErrorType::Unknown);
/// <summary>
/// Shows the error message to the user.
@@ -527,7 +527,7 @@ public:
/// </summary>
/// <param name="line">The source line.</param>
/// <param name="file">The source file.</param>
NO_RETURN static void OutOfMemory(int32 line, const char* file);
NO_RETURN static void OutOfMemory(int32 line = -1, const char* file = nullptr);
/// <summary>
/// Performs a fatal crash due to code not being implemented.

View File

@@ -30,6 +30,8 @@ void* UnixPlatform::Allocate(uint64 size, uint64 alignment)
// Calculate the offset and store it behind aligned pointer
*((offset_t*)ptr - 1) = (offset_t)((uintptr_t)ptr - (uintptr_t)p);
}
else
OutOfMemory();
#if COMPILE_WITH_PROFILER
OnMemoryAlloc(ptr, size);
#endif

View File

@@ -259,6 +259,8 @@ void Win32Platform::Prefetch(void const* ptr)
void* Win32Platform::Allocate(uint64 size, uint64 alignment)
{
void* ptr = _aligned_malloc((size_t)size, (size_t)alignment);
if (!ptr)
OutOfMemory();
#if COMPILE_WITH_PROFILER
OnMemoryAlloc(ptr, size);
#endif

View File

@@ -282,7 +282,7 @@ long __stdcall WindowsPlatform::SehExceptionHandler(EXCEPTION_POINTERS* ep)
}
// Skip if engine already crashed
if (Globals::FatalErrorOccurred)
if (Engine::FatalError != FatalErrorType::None)
return EXCEPTION_CONTINUE_SEARCH;
// Get exception info
@@ -326,7 +326,7 @@ long __stdcall WindowsPlatform::SehExceptionHandler(EXCEPTION_POINTERS* ep)
}
// Crash engine
Platform::Fatal(errorMsg.Get(), ep);
Platform::Fatal(errorMsg.Get(), ep, FatalErrorType::Exception);
return EXCEPTION_CONTINUE_SEARCH;
}

View File

@@ -13,6 +13,7 @@
#define PLATFORM_ARCH ArchitectureType::ARM64
#define PLATFORM_CACHE_LINE_SIZE 128
#define PLATFORM_DEBUG_BREAK __builtin_trap()
#define PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE (64ull * 1024) // 64 kB
// Use AOT for Mono
#define USE_MONO_AOT 1