Show main thread errors in UI.

This commit is contained in:
Musa Mahmood 2025-12-06 21:03:57 -05:00
parent 7013ca673f
commit 7755ef9225
9 changed files with 154 additions and 47 deletions

View File

@ -21,6 +21,7 @@
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#undef ERROR // why...
#undef NO_ERROR // ugh...
#else
#error "This configuration is NOT supported. Only Windows with MSVC is currently supported."
#endif
@ -324,5 +325,6 @@ struct Mutex_Lock_Guard {
#define for_each_index(_idx_, _until_) for (s64 _idx_ = 0; _idx_ < _until_; ++_idx_)
// For-loops for ArrayView<> compatible types
#define for_each(_idx_, _array_) for (s64 _idx_ = 0; _idx_ < (_array_).count; ++_idx_)
#define for_each_reverse(_idx_, _array_) for (s64 _idx_ = (_array_).count-1; _idx_ >= 0; _idx_--)
// #define for_each_starting_at(_it_, _array_, _start_) for (s64 _it_ = _start_; _it_ < (_array_).count; _it_ += 1)

View File

@ -14,9 +14,9 @@ struct Thread_Context {
Logger logger = {nullptr, nullptr};
String_Builder* log_builder;
// Stack_Trace* stack_trace;
Array<Thread*> child_threads; // maybe should be linked-list?
Thread* thread_that_created_me = nullptr; // so we can remove from above array
Thread_Context* parent_thread_context = nullptr; // so we can remove from above array
string thread_name;

View File

@ -1,6 +1,8 @@
// #NOTE: To keep things simple, all allocations for Error should be via GPAllocator.
// We really allocate two things: the Error struct and the error string copy.
#define NO_ERROR nullptr
enum class ErrorClass: s32 {
NONE = 0, // should not be used, just to avoid a default value being assigned.
WARNING = 1,
@ -26,6 +28,7 @@ struct Error {
char* error_severity (ErrorClass severity) {
switch (severity) {
case ErrorClass::NONE: {
return "[NONE]";
} break;
case ErrorClass::WARNING: {
return "[WARNING]";
@ -40,7 +43,7 @@ char* error_severity (ErrorClass severity) {
return "";
}
void push_error (Error* new_error);
void push_error (Thread_Context* tctx, Error* new_error);
string to_string (Error* error) {
return { error->count, error->data };
@ -54,6 +57,9 @@ string to_string (Error* error) {
#define log_warning(fmt, ...) \
Log_Error_2(__FILE__, __FUNCTION__, __LINE__, ErrorClass::WARNING, fmt, ##__VA_ARGS__)
#define log_none(fmt, ...) \
Log_Error_2(__FILE__, __FUNCTION__, __LINE__, ErrorClass::NONE, fmt, ##__VA_ARGS__)
Error* new_error (ErrorClass severity, string error_string) {
Error* error = New<Error>();
@ -69,11 +75,12 @@ Error* new_error (ErrorClass severity, string error_string) {
//
void Log_Error_2 (string file_path, string function_name, s32 line_number, ErrorClass severity, string fmt, ...) {
Assert(thread_context() != nullptr);
push_arena(thread_context()->error_arena);
auto tctx = thread_context();
Assert(tctx != nullptr);
push_arena(tctx->error_arena);
String_Builder* sb = new_string_builder(Arena_Reserve::Size_64K);
// #TODO: prepend severity, other information
print_to_builder(sb, "%s ", error_severity(severity));
va_list args;
@ -86,7 +93,7 @@ void Log_Error_2 (string file_path, string function_name, s32 line_number, Error
string error_string = builder_to_string(sb);
Error* error = new_error(severity, error_string);
// Additional information
error->thread_id = thread_context()->thread_idx;
error->thread_id = tctx->thread_idx;
error->source_line = line_number;
error->file_path = copy_string(file_path);
error->function_name = copy_string(function_name);
@ -94,21 +101,22 @@ void Log_Error_2 (string file_path, string function_name, s32 line_number, Error
error->previous_error = nullptr;
error->next_error = nullptr;
push_error(error);
push_error(tctx, error);
}
void push_error (Error* new_error) {
void push_error (Thread_Context* tctx, Error* new_error) {
Assert(tctx == thread_context()); // Not a real assert, just wondering if we'll ever call this with a non-local context?
Assert(new_error != nullptr);
if (new_error == nullptr) return;
Error* current_error = thread_context()->current_error;
Error* current_error = tctx->current_error;
if (current_error) {
current_error->next_error = new_error;
new_error->previous_error = current_error;
} else {
thread_context()->first_error = new_error;
tctx->first_error = new_error;
new_error->previous_error = nullptr;
}
thread_context()->current_error = new_error;
tctx->current_error = new_error;
switch (new_error->severity) {
case ErrorClass::NONE:
@ -122,19 +130,32 @@ void push_error (Error* new_error) {
}
}
void clear_errors () { // Reset pointers and reset error_arena
arena_reset(thread_context()->error_arena); // maybe overwrite memory?
thread_context()->first_error = nullptr;
thread_context()->current_error = nullptr;
// push error to a context without back-linking to previous context
void push_error_no_context (Thread_Context* tctx, Error* new_error) {
Assert(new_error != nullptr);
if (new_error == nullptr) return;
Error* current_error = tctx->current_error;
if (current_error) {
current_error->next_error = new_error;
} else {
tctx->first_error = new_error;
}
tctx->current_error = new_error;
}
void clear_error (Error* error) {
void clear_errors (Thread_Context* tctx) { // Reset pointers and reset error_arena
arena_reset(tctx->error_arena); // maybe overwrite memory?
tctx->first_error = nullptr;
tctx->current_error = nullptr;
}
void clear_error (Thread_Context* tctx, Error* error) {
// If we want to clear a specific error (simply remove from the list)
Assert(error != nullptr);
bool is_current_error = (thread_context()->current_error == error);
bool is_first_error = (thread_context()->first_error == error);
bool is_current_error = (tctx->current_error == error);
bool is_first_error = (tctx->first_error == error);
Error* current_error = thread_context()->first_error;
Error* current_error = tctx->first_error;
if (current_error == nullptr) return; // no errors in linked list.
while (current_error != error && current_error != nullptr) {
@ -145,23 +166,52 @@ void clear_error (Error* error) {
Error* the_previous_error = current_error->previous_error;
Error* the_next_error = current_error->next_error;
// Remove current_node from linked list:
the_previous_error->next_error = the_next_error;
the_next_error->previous_error = the_previous_error;
if (the_previous_error) {
the_previous_error->next_error = the_next_error;
}
if (the_next_error) {
the_next_error->previous_error = the_previous_error;
}
if (is_first_error && is_current_error) { // It matches the only item in the list, just empty the list:
clear_errors();
clear_errors(tctx);
return;
}
if (is_first_error) {
// the_next_error becomes new first error.
thread_context()->first_error = the_next_error;
tctx->first_error = the_next_error;
}
if (is_current_error) {
// the current_error becomes the previous error
thread_context()->current_error = the_previous_error;
tctx->current_error = the_previous_error;
}
}
void push_errors_to_parent_thread (Thread_Context* tctx) {
if (!tctx->first_error) return;
Assert(tctx->parent_thread_context);
while (tctx->first_error) {
push_error_no_context(tctx->parent_thread_context, tctx->first_error);
clear_error(tctx, tctx->first_error);
}
}
ArrayView<Error*> get_all_errors (Thread_Context* tctx) {
Array<Error*> error_array;
// call with temp() recommended.
Error* current_error = tctx->first_error;
while (current_error) {
array_add(error_array, current_error);
current_error = current_error->next_error;
}
for_each(t, tctx->child_threads) {
// #TODO: also recurse through child threads?
}
return error_array;
}
// Will need to use __FILE__ and __LINE__ macros

View File

@ -97,7 +97,8 @@ Allocator allocator (ExpandableArena* arena_ex) {
// #TODO: currently this keeps the final arena's memory. Fix this!
void arena_reset_to (ExpandableArena* arena_ex, Arena* last_arena, u8* starting_point) {
// going backwards from end of arena list
for (s64 i = arena_ex->next_arenas.count-1; i > 0; i -= 1) {
// for (s64 i = arena_ex->next_arenas.count-1; i >= 0; i -= 1) {
for_each_reverse(i, arena_ex->next_arenas) {
Arena* arena = arena_ex->next_arenas[i];
if (arena == last_arena) { // return to starting_point
arena_ex->current = arena;

View File

@ -284,4 +284,29 @@ string to_lower_copy (string s_orig) {
}
#define format_cstring(fmt, ...) \
(char*)format_string(fmt, ##__VA_ARGS__).data
(char*)format_string(fmt, ##__VA_ARGS__).data
bool is_any (u8 c, string chars) {
for_each(i, chars) {
if (chars.data[i] == c) return true;
}
return false;
}
string trim_right (string s, string chars, bool replace_with_zeros) {
s64 count = s.count;
for_each_reverse(i, s) {
if (is_any(s.data[i], chars)) {
if (replace_with_zeros) {
s.data[i] = 0;
}
count -= 1;
} else {
break;
}
}
return string_view(s, 0, count);
}

View File

@ -91,6 +91,9 @@ string format_string_no_context (char* format, ...);
string to_lower_copy (string s_orig);
string DEFAULT_SPACES = "\r\t\n";
string trim_right (string s, string chars=DEFAULT_SPACES, bool replace_with_zeros=true);
// #TODO #Parsing stuff:
// is_white_space(char: u8)
// advance

View File

@ -276,22 +276,29 @@ internal bool thread_init (Thread* thread, Thread_Proc proc, string thread_name=
thread->context->error_arena = next_arena(Arena_Reserve::Size_64M);
thread->context->logger = {default_logger_proc, &default_logger};
thread->context->parent_thread_context = thread_context();
thread->os_thread.windows_thread = windows_thread;
thread->os_thread.windows_thread_id = windows_thread_id;
thread->proc = proc;
thread->index = this_thread_index;
thread_context()->child_threads.allocator = allocator(thread_context()->arena);
array_add(thread_context()->child_threads, thread);
return true;
}
internal void thread_deinit (Thread* thread, bool zero_thread=false) {
// Move errors from thread to parent thread
push_errors_to_parent_thread(thread->context);
if (thread->os_thread.windows_thread) {
CloseHandle(thread->os_thread.windows_thread);
}
thread->os_thread.windows_thread = nullptr;
// #TODO: before releasing arena, force-delete extra pages?
array_reset(*thread->context->log_builder);
free_string_builder(thread->context->log_builder);
release_arena(thread->context->error_arena);
@ -382,12 +389,12 @@ internal string get_error_string (OS_Error_Code error_code) {
LocalFree(lpMsgBuf);
return result; // trim_right(result, "\r\n");
return trim_right(result);
}
internal void log_error_code_and_string () { // #TODO: replace with call to log_error_with_code
OS_Error_Code error_code = GetLastError();
log_error(" > GetLastError code: %d, %s\n", error_code, get_error_string(error_code).data);
log_error(" > GetLastError code: %d, %s", error_code, get_error_string(error_code).data);
}
internal bool file_is_valid (File file) {
@ -437,7 +444,7 @@ internal void file_close (File* file) {
internal bool file_read (File file, u8* data, s64 bytes_to_read_count, s64* bytes_read_count) {
// ignore bytes_read_count if null.
if (data == nullptr) {
log_error("file_read called with null destination pointer.\n");
log_error("file_read called with null destination pointer.");
log_error_code_and_string();
if (bytes_read_count) (*bytes_read_count) = 0;
return false;
@ -1040,7 +1047,7 @@ bool Win32_Discover_Drives () {
log(" - volume name: %s", drive->volume_name.data);
log(" - file_system: %s", wide_to_utf8(file_system_name).data);
} else {
log_error("GetVolumeInformationW failed!");
log_error("GetVolumeInformationW failed! (drive label: %s)", drive_label.data);
log_error_code_and_string();
drive->is_present = false;
}

View File

@ -143,11 +143,11 @@ bool NTFS_read_internal (NTFS_MFT_Internal* mft, void* buffer, u64 from, u64 cou
// #TODO: Release resources if we face an early return!
// #TODO: Maybe this doesn't need to return a value? Return an Error* instead.
Dense_FS* NTFS_MFT_read_raw (OS_Drive* drive) {
Error* NTFS_MFT_read_raw (OS_Drive* drive) {
auto start_time = GetUnixTimestamp();
Assert(drive != nullptr);
if (drive == nullptr) { return nullptr; }
if (drive == nullptr) { return nullptr; }
string drive_path = drive->label;
@ -298,7 +298,7 @@ Dense_FS* NTFS_MFT_read_raw (OS_Drive* drive) {
drive->bytes_accessed = mft->bytes_accessed;
drive->time_to_enumerate = (f32)(GetUnixTimestamp() - start_time);
return drive->data;
return NO_ERROR;
}
struct NTFS_Enumeration_Task {
@ -306,6 +306,8 @@ struct NTFS_Enumeration_Task {
ArrayView<OS_Drive*> drives;
// Should be part of OS_Drive!
Error* error;
};
s64 ntfs_enumeration_thread_proc (Thread* thread) {
@ -314,8 +316,9 @@ s64 ntfs_enumeration_thread_proc (Thread* thread) {
log("[ntfs_enumeration_thread_proc] (Thread index: %lld) Task pointer: %p", thread->index, task);
for_each(d, task->drives) {
auto result = NTFS_MFT_read_raw(task->drives[d]);
if (result == nullptr) return 1;
task->error = NTFS_MFT_read_raw(task->drives[d]);
// What we actually want to do here is push all our errors to return to the main thread.
if (task->error) return 1;
}
return 0;

View File

@ -83,15 +83,11 @@ void Ex1_Control_Panel () { using namespace ImGui;
SameLine();
Text("Enumerated in %.2f seconds", drive->time_to_enumerate);
}
SameLine();
if (Button(format_cstring("Read NTFS MFT Raw##%s", drive->label.data))) {
push_arena(thread_context()->arena);
// auto_release(thread_context()->arena);
auto dfs = NTFS_MFT_read_raw(drive);
if (dfs == nullptr) {
log("[NTFS_MFT_read_raw] operation failed");
}
}
// SameLine();
// if (Button(format_cstring("Read NTFS MFT Raw##%s", drive->label.data))) {
// push_arena(thread_context()->arena);
// Error* error = NTFS_MFT_read_raw(drive);
// }
}
if (drives.count > 0) {
@ -113,6 +109,7 @@ void Ex1_Control_Panel () { using namespace ImGui;
// if drive count exceeds the number of threads, we need to group them so each thread
// can enumerate multiple drives.
// We need to distribute the drives across our available threads:
// #TODO #is_present - drive_count should only include if `drive->is_present`.
Array<ArrayView<OS_Drive*>> drive_split;
s32 thread_count = (s32)ex1_ntfs.threads.count;
drive_split.allocator = GPAllocator();
@ -171,6 +168,9 @@ void Ex1_Control_Panel () { using namespace ImGui;
auto task = thread_task(NTFS_Enumeration_Task);
release_arena(task->pool);
// #TODO Before #deiniting thread we SHOULD copy the errors from the
// thread before it's deleted forever.
thread_deinit(ex1_ntfs.threads_in_flight[t], true);
array_unordered_remove_by_index(ex1_ntfs.threads_in_flight, t);
t -= 1; // check this element index again!
@ -212,6 +212,22 @@ void ImGui_Debug_Panel () { using namespace ImGui;
Text(t);
}
SeparatorText("Child Threads");
SeparatorText("Errors");
ArrayView<Error*> errors = get_all_errors(thread_context());
for_each(e, errors) {
auto button_label = format_cstring("Clear##%d", e);
if (Button(button_label)) {
clear_error(thread_context(), errors[e]);
continue;
}
SameLine();
Text(" [%d] %s", e, to_string(errors[e]).data);
}
Spacing();
if (Button("Push some error")) {
log_warning("This is a warning.");
log_error("... and this is an error.");
}
End();
}