Show main thread errors in UI.
This commit is contained in:
parent
7013ca673f
commit
7755ef9225
@ -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)
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
36
src/Ex1.cpp
36
src/Ex1.cpp
@ -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();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user