From 18a37024da596b36cf56225005f7cc12c414a735 Mon Sep 17 00:00:00 2001 From: doylet Date: Sun, 11 Feb 2024 16:14:00 +1100 Subject: [PATCH] Add new ErrorSink API to collate error from API calls --- dqn.h | 30 ++++++++++-- dqn_allocator.cpp | 6 +++ dqn_allocator.h | 1 + dqn_base.cpp | 102 +++++++++++++++++++++++++++++++++++++++-- dqn_base.h | 30 ++++++++++++ dqn_docs.cpp | 51 ++++++++++++++++++++- dqn_os.cpp | 55 +++++++++------------- dqn_os.h | 37 ++++++++------- dqn_os_win32.cpp | 97 ++++++++++++++++++++------------------- dqn_thread_context.cpp | 16 ++++++- dqn_thread_context.h | 8 +++- dqn_unit_tests.cpp | 10 ++-- 12 files changed, 327 insertions(+), 116 deletions(-) diff --git a/dqn.h b/dqn.h index 57f6fd0..71983d9 100644 --- a/dqn.h +++ b/dqn.h @@ -24,6 +24,23 @@ // first-class APIs, a 64bit MMU and in general non-pessimized APIs that aren't // constrained by the language specification and operate closer to the OS). // +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$$$\ $$$$$$$$\ $$$$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ +// $$ __$$\ $$ _____|\__$$ __|\__$$ __|\_$$ _|$$$\ $$ |$$ __$$\ +// $$ / \__|$$ | $$ | $$ | $$ | $$$$\ $$ |$$ / \__| +// $$ |$$$$\ $$$$$\ $$ | $$ | $$ | $$ $$\$$ |$$ |$$$$\ +// $$ |\_$$ |$$ __| $$ | $$ | $$ | $$ \$$$$ |$$ |\_$$ | +// $$ | $$ |$$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | +// \$$$$$$ |$$$$$$$$\ $$ | $$ | $$$$$$\ $$ | \$$ |\$$$$$$ | +// \______/ \________| \__| \__| \______|\__| \__| \______/ +// +// Getting started -- Compiling with the library and library documentation +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// -- Compiling -- +// // Define DQN_IMPLEMENTATION macro in one and only one translation unit to // enable the implementation of this library, for example: // @@ -47,9 +64,16 @@ // Compiles the library with all optional APIs turned on except the previously // mentioned APIs. // -// Below is a table of contents that describes what you can find in each header -// of this library and additional macros that can be defined to customise the -// behaviour of this library. +// -- Library documentation -- +// +// The header files in this library are intentionally extremely minimal and +// concise as to provide a dense reference of the APIs available without +// drowning out the library interface with code comments like many other +// documentation systems. +// +// Instead, documentation is laid out in dqn_docs.cpp in alphabetical order +// which provides self-contained examples in one contiguous top-down block of +// source code with comments. // //////////////////////////////////////////////////////////////////////////////////////////////////// // diff --git a/dqn_allocator.cpp b/dqn_allocator.cpp index 8733bdd..9b05744 100644 --- a/dqn_allocator.cpp +++ b/dqn_allocator.cpp @@ -220,6 +220,12 @@ DQN_API void Dqn_Arena_Pop(Dqn_Arena *arena, uint64_t amount) Dqn_Arena_PopTo(arena, pop_to); } +DQN_API uint64_t Dqn_Arena_Pos(Dqn_Arena *arena) +{ + uint64_t result = (arena && arena->curr) ? arena->curr->reserve_sum + arena->curr->used : 0; + return result; +} + DQN_API void Dqn_Arena_Clear(Dqn_Arena *arena) { Dqn_Arena_PopTo(arena, 0); diff --git a/dqn_allocator.h b/dqn_allocator.h index 4453186..13c5988 100644 --- a/dqn_allocator.h +++ b/dqn_allocator.h @@ -145,6 +145,7 @@ DQN_API void * Dqn_Arena_AllocContiguous (Dqn_Arena *arena, DQN_API void * Dqn_Arena_Copy (Dqn_Arena *arena, void const *data, uint64_t size, uint8_t align); DQN_API void Dqn_Arena_PopTo (Dqn_Arena *arena, uint64_t init_used); DQN_API void Dqn_Arena_Pop (Dqn_Arena *arena, uint64_t amount); +DQN_API uint64_t Dqn_Arena_Pos (Dqn_Arena *arena); DQN_API void Dqn_Arena_Clear (Dqn_Arena *arena); DQN_API Dqn_ArenaTempMem Dqn_Arena_TempMemBegin (Dqn_Arena *arena); DQN_API void Dqn_Arena_TempMemEnd (Dqn_ArenaTempMem mem); diff --git a/dqn_base.cpp b/dqn_base.cpp index 1d4cc6c..2f7ac55 100644 --- a/dqn_base.cpp +++ b/dqn_base.cpp @@ -346,10 +346,10 @@ DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_Str8 type, int log_type, void *user_d // NOTE: Open log file for appending if requested ////////////////////////// Dqn_TicketMutex_Begin(&lib->log_file_mutex); - if (lib->log_to_file && !lib->log_file.handle && lib->log_file.error_size == 0) { + if (lib->log_to_file && !lib->log_file.handle && !lib->log_file.error) { Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); Dqn_Str8 log_path = Dqn_OS_PathConvertF(scratch.arena, "%.*s/dqn.log", DQN_STR_FMT(lib->exe_dir)); - lib->log_file = Dqn_OS_OpenFile(log_path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_AppendOnly); + lib->log_file = Dqn_OS_OpenFile(log_path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_AppendOnly, nullptr); } Dqn_TicketMutex_End(&lib->log_file_mutex); @@ -361,8 +361,8 @@ DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_Str8 type, int log_type, void *user_d Dqn_Print_StdLn(Dqn_PrintStd_Out, log_line); Dqn_TicketMutex_Begin(&lib->log_file_mutex); - Dqn_OS_WriteFile(&lib->log_file, log_line); - Dqn_OS_WriteFile(&lib->log_file, DQN_STR8("\n")); + Dqn_OS_WriteFile(&lib->log_file, log_line, nullptr); + Dqn_OS_WriteFile(&lib->log_file, DQN_STR8("\n"), nullptr); Dqn_TicketMutex_End(&lib->log_file_mutex); } @@ -405,3 +405,97 @@ DQN_API void Dqn_Log_TypeFCallSite(Dqn_LogType type, Dqn_CallSite call_site, DQN Dqn_Log_TypeFVCallSite(type, call_site, fmt, args); va_end(args); } + +// NOTE: [$ERRS] Dqn_ErrorSink ///////////////////////////////////////////////////////////////////// +DQN_API Dqn_ErrorSink *Dqn_ErrorSink_Begin() +{ + Dqn_ThreadContext *thread_context = Dqn_ThreadContext_Get(); + Dqn_ErrorSink *result = &thread_context->error_sink; + Dqn_ErrorSinkNode *node = Dqn_Arena_New(result->arena, Dqn_ErrorSinkNode, Dqn_ZeroMem_Yes); + node->next = result->stack; + node->arena_pos = Dqn_Arena_Pos(result->arena); + result->stack = node; + return result; +} + +DQN_API Dqn_ErrorSinkNode Dqn_ErrorSink_End(Dqn_Arena *arena, Dqn_ErrorSink *error) +{ + Dqn_ErrorSinkNode result = {}; + if (!error) + return result; + + Dqn_ErrorSinkNode *node = error->stack; + if (!node) + return result; + + DQN_ASSERTF(arena != error->arena, + "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost"); + result = *error->stack; + result.next = nullptr; + result.msg = Dqn_Str8_Copy(arena, result.msg); + error->stack = error->stack->next; + Dqn_Arena_PopTo(error->arena, result.arena_pos); + return result; +} + +DQN_API bool Dqn_ErrorSink_EndAndLogErrorFV(Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args) +{ + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_ErrorSinkNode node = Dqn_ErrorSink_End(scratch.arena, error); + if (node.error) { + Dqn_Str8 line = Dqn_Str8_InitFV(scratch.arena, fmt, args); + if (Dqn_Str8_HasData(line)) { + Dqn_Log_TypeFCallSite(Dqn_LogType_Error, node.call_site, "%.*s. %.*s", DQN_STR_FMT(line), DQN_STR_FMT(node.msg)); + } else { + Dqn_Log_TypeFCallSite(Dqn_LogType_Error, node.call_site, "%.*s", DQN_STR_FMT(node.msg)); + } + } + bool result = node.error; + return result; +} + +DQN_API bool Dqn_ErrorSink_EndAndLogErrorF(Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = Dqn_ErrorSink_EndAndLogErrorFV(error, fmt, args); + va_end(args); + return result; +} + +DQN_API void Dqn_ErrorSink_EndAndExitIfErrorFV(Dqn_ErrorSink *error, uint32_t exit_code, DQN_FMT_ATTRIB char const *fmt, va_list args) +{ + if (Dqn_ErrorSink_EndAndLogErrorFV(error, fmt, args)) + Dqn_OS_Exit(exit_code); +} + +DQN_API void Dqn_ErrorSink_EndAndExitIfErrorF(Dqn_ErrorSink *error, uint32_t exit_code, DQN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_ErrorSink_EndAndExitIfErrorFV(error, exit_code, fmt, args); + va_end(args); +} + +DQN_API void Dqn_ErrorSink_MakeFV_(Dqn_ErrorSink *error, uint32_t error_code, DQN_FMT_ATTRIB char const *fmt, va_list args) +{ + if (error) { + Dqn_ErrorSinkNode *node = error->stack; + DQN_ASSERTF(node, "Error sink must be begun by calling 'Begin' before using this function."); + if (!node->error) { // NOTE: Only preserve the first error + node->msg = Dqn_Str8_InitFV(error->arena, fmt, args); + node->error_code = error_code; + node->error = true; + node->call_site = Dqn_ThreadContext_Get()->call_site; + } + } +} + +DQN_API void Dqn_ErrorSink_MakeF_(Dqn_ErrorSink *error, uint32_t error_code, DQN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_ErrorSink_MakeFV_(error, error_code, fmt, args); + va_end(args); +} + diff --git a/dqn_base.h b/dqn_base.h index 5aec175..38c6c28 100644 --- a/dqn_base.h +++ b/dqn_base.h @@ -385,6 +385,23 @@ struct Dqn_CallSite }; #define DQN_CALL_SITE Dqn_CallSite{DQN_STR8(__FILE__), DQN_STR8(__func__), __LINE__} +// NOTE: [$ERRS] Dqn_ErrorSink ///////////////////////////////////////////////////////////////////// +struct Dqn_ErrorSinkNode +{ + bool error; + int32_t error_code; + Dqn_Str8 msg; + Dqn_CallSite call_site; + Dqn_ErrorSinkNode *next; + uint64_t arena_pos; +}; + +struct Dqn_ErrorSink +{ + struct Dqn_Arena *arena; + Dqn_ErrorSinkNode *stack; +}; + // NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// // NOTE: Dqn_Atomic_Add/Exchange return the previous value store in the target #if defined(DQN_COMPILER_MSVC) || defined(DQN_COMPILER_CLANG_CL) @@ -595,6 +612,19 @@ DQN_API void Dqn_Log_TypeFCallSite DQN_API void Dqn_Log_FVCallSite (Dqn_Str8 type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, va_list va); DQN_API void Dqn_Log_FCallSite (Dqn_Str8 type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, ...); +// NOTE: [$ERRS] Dqn_ErrorSink ///////////////////////////////////////////////////////////////////// +DQN_API Dqn_ErrorSink * Dqn_ErrorSink_Begin (); +DQN_API Dqn_ErrorSinkNode Dqn_ErrorSink_End (Dqn_Arena *arena, Dqn_ErrorSink *error); +DQN_API bool Dqn_ErrorSink_EndAndLogErrorFV (Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API bool Dqn_ErrorSink_EndAndLogErrorF (Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API void Dqn_ErrorSink_EndAndExitIfErrorF (Dqn_ErrorSink *error, uint32_t exit_code, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API void Dqn_ErrorSink_EndAndExitIfErrorFV (Dqn_ErrorSink *error, uint32_t exit_code, DQN_FMT_ATTRIB char const *fmt, va_list args); + +#define Dqn_ErrorSink_MakeFV(error, error_code, fmt, args) do { Dqn_ThreadContext_SaveCallSite; Dqn_ErrorSink_MakeFV_(error, error_code, fmt, args); } while (0) +#define Dqn_ErrorSink_MakeF(error, error_code, fmt, ...) do { Dqn_ThreadContext_SaveCallSite; Dqn_ErrorSink_MakeF_(error, error_code, fmt, ## __VA_ARGS__); } while (0) +DQN_API void Dqn_ErrorSink_MakeFV_ (Dqn_ErrorSink *error, uint32_t error_code, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API void Dqn_ErrorSink_MakeF_ (Dqn_ErrorSink *error, uint32_t error_code, DQN_FMT_ATTRIB char const *fmt, ...); + // NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// DQN_FORCE_INLINE uint64_t Dqn_Atomic_SetValue64(uint64_t volatile *target, uint64_t value) { diff --git a/dqn_docs.cpp b/dqn_docs.cpp index 6d50ff4..219862e 100644 --- a/dqn_docs.cpp +++ b/dqn_docs.cpp @@ -236,6 +236,52 @@ void Dqn_Docs_Demo() // failure, or the requested size is smaller than the current number of // elements in the map to resize. + // NOTE: Dqn_ErrorSink ///////////////////////////////////////////////////////////////////////// + // + // A thread-local data structure that collects all errors emitted by APIs + // into one unified structure. This library has 2 core tenets when handling + // errors + // + // 1. Pipelining of errors + // Errors emitted over the course of several API calls are accumulated + // into a thread-local sink which save the error code and message + // of the first error encountered. + // + // 2. Error proof APIs + // Functions that produce errors must return objects/handles that are + // marked to trigger no-ops used in subsequent functions dependent on it. + // + // Together this allows end-users of APIs to chain calls and defer error + // checking until the end of a sequence of actions. Consider the following + // example demonstrating the 2 approaches. + + // (A) Conventional error checking patterns using return/sentinel values + #if 0 + FileHandle *file = OpenFile("/path/to/file"); + if (!file) + // Error handling! + if (!WriteFile(file, "abc")) + // Error handling! + CloseFile(file); + #endif + + // (B) Error handling using pipelining and and error proof APIs + if (0) { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_ErrorSink *error = Dqn_ErrorSink_Begin(); + Dqn_OSFile file = Dqn_OS_OpenFile(DQN_STR8("/path/to/file"), Dqn_OSFileOpen_OpenIfExist, Dqn_OSFileAccess_ReadWrite, error); + Dqn_OS_WriteFile(&file, DQN_STR8("abc"), error); + Dqn_OS_CloseFile(&file); + + Dqn_ErrorSinkNode error_node = Dqn_ErrorSink_End(scratch.arena, error); + if (error_node.error) { + // Do error handling! + Dqn_Log_ErrorF("%.*s", DQN_STR_FMT(error_node.msg)); + } + } + + // TODO(doyle): Integrate more into the codebase and provide a concrete example. + // NOTE: Dqn_FStr8_Max ///////////////////////////////////////////////////////////////////////// // // Return the maximum capacity of the string, e.g. the 'N' template @@ -370,7 +416,10 @@ void Dqn_Docs_Demo() // If 'tmp_path' is written to successfuly, the file will be copied over into // 'path'. if (0) { - Dqn_OS_WriteAllSafe(/*path*/ DQN_STR8("C:/Home/my.txt"), /*buffer*/ DQN_STR8("Hello world")); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_ErrorSink *error = Dqn_ErrorSink_Begin(); + Dqn_OS_WriteAllSafe(/*path*/ DQN_STR8("C:/Home/my.txt"), /*buffer*/ DQN_STR8("Hello world"), error); + Dqn_ErrorSink_EndAndLogErrorF(error, ""); } // NOTE: Dqn_OS_EstimateTSCPerSecond /////////////////////////////////////////////////////////// diff --git a/dqn_os.cpp b/dqn_os.cpp index 7b28260..557e92f 100644 --- a/dqn_os.cpp +++ b/dqn_os.cpp @@ -148,95 +148,84 @@ DQN_API uint64_t Dqn_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_f #if !defined(DQN_NO_OS_FILE_API) // NOTE: [$FILE] Dqn_OSPathInfo/File /////////////////////////////////////////////////////////////// -DQN_API bool Dqn_OS_WriteFile(Dqn_OSFile *file, Dqn_Str8 buffer) +DQN_API bool Dqn_OS_WriteFile(Dqn_OSFile *file, Dqn_Str8 buffer, Dqn_ErrorSink *error) { - bool result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size); + bool result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size, error); return result; } -DQN_API bool Dqn_OS_WriteFileFV(Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, va_list args) +DQN_API bool Dqn_OS_WriteFileFV(Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args) { bool result = false; if (!file || !fmt) return result; Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args); - result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size); + result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size, error); return result; } -DQN_API bool Dqn_OS_WriteFileF(Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, ...) +DQN_API bool Dqn_OS_WriteFileF(Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...) { va_list args; va_start(args, fmt); - bool result = Dqn_OS_WriteFileFV(file, fmt, args); + bool result = Dqn_OS_WriteFileFV(file, error, fmt, args); va_end(args); return result; } // NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// -DQN_API bool Dqn_OS_WriteAll(Dqn_Str8 path, Dqn_Str8 buffer) +DQN_API bool Dqn_OS_WriteAll(Dqn_Str8 path, Dqn_Str8 buffer, Dqn_ErrorSink *error) { - Dqn_OSFile file = Dqn_OS_OpenFile(path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_Write); - bool result = Dqn_OS_WriteFile(&file, buffer); + Dqn_OSFile file = Dqn_OS_OpenFile(path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_Write, error); + bool result = Dqn_OS_WriteFile(&file, buffer, error); Dqn_OS_CloseFile(&file); return result; } -DQN_API bool Dqn_OS_WriteAllFV(Dqn_Str8 file_path, DQN_FMT_ATTRIB char const *fmt, va_list args) +DQN_API bool Dqn_OS_WriteAllFV(Dqn_Str8 file_path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args) { Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); - Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args); - bool result = Dqn_OS_WriteAll(file_path, buffer); + Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args); + bool result = Dqn_OS_WriteAll(file_path, buffer, error); return result; } -DQN_API bool Dqn_OS_WriteAllF(Dqn_Str8 file_path, DQN_FMT_ATTRIB char const *fmt, ...) +DQN_API bool Dqn_OS_WriteAllF(Dqn_Str8 file_path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...) { va_list args; va_start(args, fmt); - bool result = Dqn_OS_WriteAllFV(file_path, fmt, args); + bool result = Dqn_OS_WriteAllFV(file_path, error, fmt, args); va_end(args); return result; } -DQN_API bool Dqn_OS_WriteAllSafe(Dqn_Str8 path, Dqn_Str8 buffer) +DQN_API bool Dqn_OS_WriteAllSafe(Dqn_Str8 path, Dqn_Str8 buffer, Dqn_ErrorSink *error) { Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); Dqn_Str8 tmp_path = Dqn_Str8_InitF(scratch.arena, "%.*s.tmp", DQN_STR_FMT(path)); - if (!Dqn_OS_WriteAll(tmp_path, buffer)) { - Dqn_Log_ErrorF("Failed to write to temporary file [path=%.*s]", DQN_STR_FMT(tmp_path)); + if (!Dqn_OS_WriteAll(tmp_path, buffer, error)) return false; - } - - if (!Dqn_OS_FileCopy(tmp_path, path, true /*overwrite*/)) { - Dqn_Log_ErrorF("Failed to overwrite file at '%.*s' with temporary file '%.*s' to complete the safe-write", - DQN_STR_FMT(tmp_path), - DQN_STR_FMT(path)); + if (!Dqn_OS_FileCopy(tmp_path, path, true /*overwrite*/, error)) return false; - } - - if (!Dqn_OS_PathDelete(tmp_path)) { - Dqn_Log_ErrorF("Failed to delete the temporary file at '%.*s' to clean-up the safe-write", DQN_STR_FMT(tmp_path)); + if (!Dqn_OS_PathDelete(tmp_path)) return false; - } - return true; } -DQN_API bool Dqn_OS_WriteAllSafeFV(Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, va_list args) +DQN_API bool Dqn_OS_WriteAllSafeFV(Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args) { Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args); - bool result = Dqn_OS_WriteAllSafe(path, buffer); + bool result = Dqn_OS_WriteAllSafe(path, buffer, error); return result; } -DQN_API bool Dqn_OS_WriteAllSafeF(Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, ...) +DQN_API bool Dqn_OS_WriteAllSafeF(Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...) { va_list args; va_start(args, fmt); - bool result = Dqn_OS_WriteAllSafeFV(path, fmt, args); + bool result = Dqn_OS_WriteAllSafeFV(path, error, fmt, args); return result; } #endif // !defined(DQN_NO_OS_FILE_API) diff --git a/dqn_os.h b/dqn_os.h index a039572..225bb07 100644 --- a/dqn_os.h +++ b/dqn_os.h @@ -120,9 +120,8 @@ struct Dqn_OSPathInfo // NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// struct Dqn_OSFile { - void *handle; - char error[512]; - uint16_t error_size; + bool error; + void *handle; }; enum Dqn_OSFileOpen @@ -305,27 +304,27 @@ DQN_API uint64_t Dqn_OS_EstimateTSCPerSecond(uint64_t duration_ DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo (Dqn_Str8 path); DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path); DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path); -DQN_API bool Dqn_OS_FileCopy (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite); -DQN_API bool Dqn_OS_FileMove (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite); -DQN_API bool Dqn_OS_DirExists (Dqn_Str8 path); -DQN_API bool Dqn_OS_DirMake (Dqn_Str8 path); +DQN_API bool Dqn_OS_FileCopy (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_FileMove (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_DirExists (Dqn_Str8 path, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_DirMake (Dqn_Str8 path, Dqn_ErrorSink *error); // NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// -DQN_API Dqn_OSFile Dqn_OS_OpenFile (Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access); -DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *data, Dqn_usize size); -DQN_API bool Dqn_OS_WriteFile (Dqn_OSFile *file, Dqn_Str8 buffer); -DQN_API bool Dqn_OS_WriteFileFV (Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, va_list args); -DQN_API bool Dqn_OS_WriteFileF (Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API Dqn_OSFile Dqn_OS_OpenFile (Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *data, Dqn_usize size, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_WriteFile (Dqn_OSFile *file, Dqn_Str8 buffer, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_WriteFileFV (Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API bool Dqn_OS_WriteFileF (Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...); DQN_API void Dqn_OS_CloseFile (Dqn_OSFile *file); // NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// -DQN_API Dqn_Str8 Dqn_OS_ReadAll (Dqn_Str8 path, Dqn_Arena *arena); -DQN_API bool Dqn_OS_WriteAll (Dqn_Str8 path, Dqn_Str8 buffer); -DQN_API bool Dqn_OS_WriteAllFV (Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, va_list args); -DQN_API bool Dqn_OS_WriteAllF (Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API bool Dqn_OS_WriteAllSafe (Dqn_Str8 path, Dqn_Str8 buffer); -DQN_API bool Dqn_OS_WriteAllSafeFV(Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, va_list args); -DQN_API bool Dqn_OS_WriteAllSafeF (Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API Dqn_Str8 Dqn_OS_ReadAll (Dqn_Str8 path, Dqn_Arena *arena, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_WriteAll (Dqn_Str8 path, Dqn_Str8 buffer, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_WriteAllFV (Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API bool Dqn_OS_WriteAllF (Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API bool Dqn_OS_WriteAllSafe (Dqn_Str8 path, Dqn_Str8 buffer, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_WriteAllSafeFV(Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API bool Dqn_OS_WriteAllSafeF (Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...); #endif // !defined(DQN_NO_OS_FILE_API) // NOTE: File system paths ///////////////////////////////////////////////////////////////////////// diff --git a/dqn_os_win32.cpp b/dqn_os_win32.cpp index 40d0d59..08a1c56 100644 --- a/dqn_os_win32.cpp +++ b/dqn_os_win32.cpp @@ -254,7 +254,7 @@ DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path) return result; } -DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) +DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error) { bool result = false; Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); @@ -262,19 +262,22 @@ DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) Dqn_Str16 dest16 = Dqn_Win_Str8ToStr16(scratch.arena, dest); int fail_if_exists = overwrite == false; - result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0; + result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0; if (!result) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Failed to copy the file\n\nSource: %.*s\nDestination: %.*s\n\nWindows reported: %.*s", - DQN_STR_FMT(src), - DQN_STR_FMT(dest), - DQN_STR_FMT(error.msg)); + Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena); + Dqn_ErrorSink_MakeF(error, + win_error.code, + "Failed to copy file '%.*s' to '%.*s': (%u) %.*s", + DQN_STR_FMT(src), + DQN_STR_FMT(dest), + win_error.code, + DQN_STR_FMT(win_error.msg)); } return result; } -DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) +DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error) { bool result = false; Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); @@ -288,11 +291,14 @@ DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) result = MoveFileExW(src16.data, dest16.data, flags) != 0; if (!result) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Failed to move the file\n\nSource: %.*s\nDestination: %.*s\n\nWindows reported: %.*s", - DQN_STR_FMT(src), - DQN_STR_FMT(dest), - DQN_STR_FMT(error.msg)); + Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena); + Dqn_ErrorSink_MakeF(error, + win_error.code, + "Failed to move file '%.*s' to '%.*s': (%u) %.*s", + DQN_STR_FMT(src), + DQN_STR_FMT(dest), + win_error.code, + DQN_STR_FMT(win_error.msg)); } return result; } @@ -385,7 +391,7 @@ DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path) } // NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// -DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access) +DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access, Dqn_ErrorSink *error) { Dqn_OSFile result = {}; if (!Dqn_Str8_HasData(path) || path.size <= 0) @@ -429,13 +435,9 @@ DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint /*HANDLE hTemplateFile*/ nullptr); if (handle == INVALID_HANDLE_VALUE) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - result.error_size = - DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate(result.error, - DQN_ARRAY_UCOUNT(result.error), - "Open file failed: %.*s for \"%.*s\"", - DQN_STR_FMT(error.msg), - DQN_STR_FMT(path)); + Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena); + result.error = true; + Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to open file at '%.*s': '%.*s'", DQN_STR_FMT(path), DQN_STR_FMT(win_error.msg)); return result; } @@ -443,9 +445,9 @@ DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint return result; } -DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_usize size) +DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_usize size, Dqn_ErrorSink *error) { - if (!file || !file->handle || !buffer || size <= 0 || file->error_size) + if (!file || !file->handle || file->error || !buffer || size <= 0) return false; bool result = true; @@ -458,28 +460,24 @@ DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_us } if (!result) { - Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - file->error_size = - DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate(file->error, - DQN_ARRAY_UCOUNT(file->error), - "Write file failed (%u): %.*s", - error.code, - DQN_STR_FMT(error.msg)); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena); + Dqn_Str8 buffer_size_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, size, Dqn_U64ByteSizeType_Auto); + Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to write buffer (%.*s) to file handle: %.*s", DQN_STR_FMT(buffer_size_str8), DQN_STR_FMT(win_error.msg)); } return result; } DQN_API void Dqn_OS_CloseFile(Dqn_OSFile *file) { - if (!file || !file->handle || file->error_size) + if (!file || !file->handle || file->error) return; CloseHandle(file->handle); *file = {}; } // NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// -DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena) +DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena, Dqn_ErrorSink *error) { Dqn_Str8 result = {}; if (!arena) @@ -499,8 +497,8 @@ DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena) /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_READONLY, /*HANDLE hTemplateFile*/ nullptr); if (file_handle == INVALID_HANDLE_VALUE) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Failed to open file '%.*s' for reading: (%u) %.*s", DQN_STR_FMT(path), error.code, DQN_STR_FMT(error.msg)); + Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena); + Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to open file at '%.*s' for reading: %.*s", DQN_STR_FMT(path), DQN_STR_FMT(win_error.msg)); return result; } DQN_DEFER { CloseHandle(file_handle); }; @@ -508,14 +506,14 @@ DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena) // NOTE: Query the file size /////////////////////////////////////////////////////////////////// LARGE_INTEGER win_file_size; if (!GetFileSizeEx(file_handle, &win_file_size)) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Failed to query file size [file=%.*s, reason=%.*s]", DQN_STR_FMT(path), DQN_STR_FMT(error.msg)); + Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena); + Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to query file size for reading '%.*s': %.*s", DQN_STR_FMT(path), DQN_STR_FMT(win_error.msg)); return result; } unsigned long const bytes_desired = DQN_CAST(unsigned long)win_file_size.QuadPart; - if (!DQN_CHECKF(bytes_desired == win_file_size.QuadPart, - "Current implementation doesn't support >4GiB, implement Win32 overlapped IO")) { + if (!DQN_CHECK(bytes_desired == win_file_size.QuadPart)) { + Dqn_ErrorSink_MakeF(error, 1 /*error_code*/, "Current implementation doesn't support reading >4GiB file at '%.*s', implement Win32 overlapped IO", DQN_STR_FMT(path)); return result; } @@ -529,19 +527,22 @@ DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena) /*LPOVERLAPPED lpOverlapped*/ nullptr); if (read_result == 0) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Failed to load file '%.*s' (via 'ReadFile'): (%u) %.*s", DQN_STR_FMT(path), error.code, DQN_STR_FMT(error.msg)); + Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena); + Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to read bytes from file '%.*s': (%u) %.*s", DQN_STR_FMT(path), win_error.code, DQN_STR_FMT(win_error.msg)); return result; } if (bytes_read != bytes_desired) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Loaded file '%.*s' (via 'ReadFile') did not read the desired number of bytes %u, we read %u: (%u) %.*s", - DQN_STR_FMT(path), - bytes_desired, - bytes_read, - error.code, - DQN_STR_FMT(error.msg)); + Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena); + Dqn_ErrorSink_MakeF( + error, + win_error.code, + "Failed to read the desired number of bytes from file '%.*s', we read %uB but we expected %uB: (%u) %.*s", + DQN_STR_FMT(path), + bytes_read, + bytes_desired, + win_error.code, + DQN_STR_FMT(win_error.msg)); return result; } diff --git a/dqn_thread_context.cpp b/dqn_thread_context.cpp index a695db1..81c21af 100644 --- a/dqn_thread_context.cpp +++ b/dqn_thread_context.cpp @@ -55,7 +55,7 @@ DQN_API Dqn_ThreadContext *Dqn_ThreadContext_Get() Dqn_ArenaCatalog *catalog = &g_dqn_library->arena_catalog; DQN_HARD_ASSERTF(g_dqn_library && g_dqn_library->lib_init, "Library must be initialised by calling Dqn_Library_Init()"); - // NOTE: Setup scratch arenas + // NOTE: Setup scratch arenas ////////////////////////////////////////////////////////////////// DQN_FOR_UINDEX (index, DQN_ARRAY_UCOUNT(result->scratch_arenas)) { // NOTE: We allocate arenas so that they all come from the memory @@ -85,6 +85,20 @@ DQN_API Dqn_ThreadContext *Dqn_ThreadContext_Get() result->scratch_arenas[index] = catalog_item->arena; } } + + // NOTE: Setup error sink ////////////////////////////////////////////////////////////////////// + { + Dqn_FStr8<128> label = Dqn_FStr8_InitF<128>("T%05u Error Sink", Dqn_OS_ThreadID()); + Dqn_ArenaCatalogItem *catalog_item = Dqn_ArenaCatalog_Find(catalog, Dqn_FStr8_ToStr8(&label)); + if (catalog_item == &catalog->sentinel) { + result->error_sink_arena = Dqn_ArenaCatalog_AllocLabelCopy(catalog, 0, 0, Dqn_ArenaFlag_AllocCanLeak, Dqn_FStr8_ToStr8(&label)); + } else { + // NOTE: Reuse the arena + result->error_sink_arena = catalog_item->arena; + } + result->error_sink.arena = result->error_sink_arena; + } + return result; } diff --git a/dqn_thread_context.h b/dqn_thread_context.h index 5e801ea..f8c5669 100644 --- a/dqn_thread_context.h +++ b/dqn_thread_context.h @@ -24,8 +24,11 @@ struct Dqn_ThreadContext { - Dqn_b32 init; - Dqn_Arena *scratch_arenas[2]; + Dqn_b32 init; + Dqn_Arena *scratch_arenas[2]; + Dqn_Arena *error_sink_arena; + Dqn_CallSite call_site; + Dqn_ErrorSink error_sink; }; struct Dqn_Scratch @@ -40,4 +43,5 @@ struct Dqn_Scratch DQN_API bool Dqn_ThreadContext_IsInit(); DQN_API Dqn_ThreadContext *Dqn_ThreadContext_Get(); +#define Dqn_ThreadContext_SaveCallSite do { Dqn_ThreadContext_Get()->call_site = DQN_CALL_SITE; } while (0) DQN_API Dqn_Scratch Dqn_Scratch_Get(void const *conflict_arena); diff --git a/dqn_unit_tests.cpp b/dqn_unit_tests.cpp index 430b957..f5f1b0e 100644 --- a/dqn_unit_tests.cpp +++ b/dqn_unit_tests.cpp @@ -710,28 +710,28 @@ static Dqn_UTest Dqn_Test_Fs() DQN_UTEST_TEST("File write, read, copy, move and delete") { // NOTE: Write step Dqn_Str8 const SRC_FILE = DQN_STR8("dqn_test_file"); - Dqn_b32 write_result = Dqn_OS_WriteAll(SRC_FILE, DQN_STR8("test")); + Dqn_b32 write_result = Dqn_OS_WriteAll(SRC_FILE, DQN_STR8("test"), nullptr); DQN_UTEST_ASSERT(&test, write_result); DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(SRC_FILE)); // NOTE: Read step Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); - Dqn_Str8 read_file = Dqn_OS_ReadAll(SRC_FILE, scratch.arena); + Dqn_Str8 read_file = Dqn_OS_ReadAll(SRC_FILE, scratch.arena, nullptr); DQN_UTEST_ASSERTF(&test, Dqn_Str8_HasData(read_file), "Failed to load file"); DQN_UTEST_ASSERTF(&test, read_file.size == 4, "File read wrong amount of bytes"); DQN_UTEST_ASSERTF(&test, Dqn_Str8_Eq(read_file, DQN_STR8("test")), "read(%zu): %.*s", read_file.size, DQN_STR_FMT(read_file)); // NOTE: Copy step Dqn_Str8 const COPY_FILE = DQN_STR8("dqn_test_file_copy"); - Dqn_b32 copy_result = Dqn_OS_FileCopy(SRC_FILE, COPY_FILE, true /*overwrite*/); + Dqn_b32 copy_result = Dqn_OS_FileCopy(SRC_FILE, COPY_FILE, true /*overwrite*/, nullptr); DQN_UTEST_ASSERT(&test, copy_result); DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(COPY_FILE)); // NOTE: Move step Dqn_Str8 const MOVE_FILE = DQN_STR8("dqn_test_file_move"); - Dqn_b32 move_result = Dqn_OS_FileMove(COPY_FILE, MOVE_FILE, true /*overwrite*/); + Dqn_b32 move_result = Dqn_OS_FileMove(COPY_FILE, MOVE_FILE, true /*overwrite*/, nullptr); DQN_UTEST_ASSERT(&test, move_result); - DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(MOVE_FILE)); + DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(MOVE_FILE)); DQN_UTEST_ASSERTF(&test, Dqn_OS_FileExists(COPY_FILE) == false, "Moving a file should remove the original"); // NOTE: Delete step