30#include <sys/select.h>
47#define LOCK_HOLD_TIME_WARNING_MS 500
55#define SAFE_BUFFER_SIZE(buffer_size, offset) \
56 ((offset) < 0 || (size_t)(offset) >= (buffer_size) ? 0 : (buffer_size) - (size_t)(offset))
62lock_debug_manager_t g_lock_debug_manager = {0};
63atomic_bool g_initializing =
false;
67static struct termios g_original_termios;
68static bool g_termios_saved =
false;
80static lock_record_t *create_lock_record(
void *lock_address, lock_type_t lock_type,
const char *file_name,
81 int line_number,
const char *function_name) {
82 lock_record_t *record =
SAFE_CALLOC(1,
sizeof(lock_record_t), lock_record_t *);
85 record->key = lock_record_key(lock_address, lock_type);
86 record->lock_address = lock_address;
87 record->lock_type = lock_type;
89 record->file_name = file_name;
90 record->line_number = line_number;
91 record->function_name = function_name;
94 if (clock_gettime(CLOCK_MONOTONIC, &record->acquisition_time) != 0) {
104 record->backtrace_size =
platform_backtrace(record->backtrace_buffer, MAX_BACKTRACE_FRAMES);
105 if (record->backtrace_size > 0) {
107 if (!record->backtrace_symbols) {
108 static bool symbolize_error_logged =
false;
109 if (!symbolize_error_logged) {
110 log_warn(
"Failed to symbolize backtrace for lock record (backtrace support may be unavailable)");
111 symbolize_error_logged =
true;
117 static bool backtrace_error_logged =
false;
118 if (!backtrace_error_logged) {
119 log_debug(
"Backtrace not available for lock debugging");
120 backtrace_error_logged =
true;
131static void free_lock_record(lock_record_t *record) {
138 if (record->backtrace_symbols) {
154static void collect_lock_record_callback(lock_record_t *record,
void *user_data) {
155 struct lock_collector {
159 } *collector = (
struct lock_collector *)user_data;
162 int *offset = collector->offset;
163 char *buffer = collector->buffer;
164 size_t buffer_size = 32768;
167 const char *lock_type_str;
168 switch (record->lock_type) {
169 case LOCK_TYPE_MUTEX:
170 lock_type_str =
"MUTEX";
172 case LOCK_TYPE_RWLOCK_READ:
173 lock_type_str =
"RWLOCK_READ";
175 case LOCK_TYPE_RWLOCK_WRITE:
176 lock_type_str =
"RWLOCK_WRITE";
179 lock_type_str =
"UNKNOWN";
183 *offset += snprintf(buffer + *offset,
SAFE_BUFFER_SIZE(buffer_size, *offset),
"Lock #%u: %s at %p\n",
184 collector->count, lock_type_str, record->lock_address);
185 *offset += snprintf(buffer + *offset,
SAFE_BUFFER_SIZE(buffer_size, *offset),
" Thread ID: %llu\n",
186 (
unsigned long long)record->thread_id);
187 *offset += snprintf(buffer + *offset,
SAFE_BUFFER_SIZE(buffer_size, *offset),
" Acquired: %s:%d in %s()\n",
188 record->file_name, record->line_number, record->function_name);
191 struct timespec current_time;
192 if (clock_gettime(CLOCK_MONOTONIC, ¤t_time) == 0) {
193 long long held_sec = current_time.tv_sec - record->acquisition_time.tv_sec;
194 long held_nsec = current_time.tv_nsec - record->acquisition_time.tv_nsec;
199 held_nsec += 1000000000;
202 *offset += snprintf(buffer + *offset,
SAFE_BUFFER_SIZE(buffer_size, *offset),
" Held for: %lld.%09ld seconds\n",
203 held_sec, held_nsec);
205 *offset += snprintf(buffer + *offset,
SAFE_BUFFER_SIZE(buffer_size, *offset),
206 " Acquired at: %lld.%09ld seconds (monotonic)\n", (
long long)record->acquisition_time.tv_sec,
207 record->acquisition_time.tv_nsec);
211 if (record->backtrace_size > 0) {
214 symbols, record->backtrace_size, 0, 0, NULL);
219 *offset += snprintf(buffer + *offset,
SAFE_BUFFER_SIZE(buffer_size, *offset),
" Call stack: <capture failed>\n");
228static void cleanup_lock_record_callback(lock_record_t *record,
void *user_data) {
232 free_lock_record(record);
240static void print_usage_stats_callback(lock_usage_stats_t *stats,
void *user_data) {
245 const char *lock_type_str;
246 switch (stats->lock_type) {
247 case LOCK_TYPE_MUTEX:
248 lock_type_str =
"MUTEX";
250 case LOCK_TYPE_RWLOCK_READ:
251 lock_type_str =
"RWLOCK_READ";
253 case LOCK_TYPE_RWLOCK_WRITE:
254 lock_type_str =
"RWLOCK_WRITE";
257 lock_type_str =
"UNKNOWN";
262 uint64_t avg_hold_time_ns = stats->total_hold_time_ns / stats->total_acquisitions;
265 char log_message[1024];
269 "Usage #%u: %s at %s:%d in %s()\n", *count, lock_type_str, stats->file_name,
270 stats->line_number, stats->function_name);
272 " Total acquisitions: %llu\n", (
unsigned long long)stats->total_acquisitions);
275 " Total hold time: %llu.%03llu ms\n", (
unsigned long long)(stats->total_hold_time_ns / 1000000),
276 (
unsigned long long)((stats->total_hold_time_ns % 1000000) / 1000));
278 " Average hold time: %llu.%03llu ms\n", (
unsigned long long)(avg_hold_time_ns / 1000000),
279 (
unsigned long long)((avg_hold_time_ns % 1000000) / 1000));
281 " Max hold time: %llu.%03llu ms\n", (
unsigned long long)(stats->max_hold_time_ns / 1000000),
282 (
unsigned long long)((stats->max_hold_time_ns % 1000000) / 1000));
284 " Min hold time: %llu.%03llu ms\n", (
unsigned long long)(stats->min_hold_time_ns / 1000000),
285 (
unsigned long long)((stats->min_hold_time_ns % 1000000) / 1000));
287 " First acquisition: %lld.%09ld\n", (
long long)stats->first_acquisition.tv_sec,
288 stats->first_acquisition.tv_nsec);
290 " Last acquisition: %lld.%09ld", (
long long)stats->last_acquisition.tv_sec,
291 stats->last_acquisition.tv_nsec);
301static void cleanup_usage_stats_callback(lock_usage_stats_t *stats,
void *user_data) {
311void print_orphaned_release_callback(lock_record_t *record,
void *user_data) {
316 const char *lock_type_str;
317 switch (record->lock_type) {
318 case LOCK_TYPE_MUTEX:
319 lock_type_str =
"MUTEX";
321 case LOCK_TYPE_RWLOCK_READ:
322 lock_type_str =
"RWLOCK_READ";
324 case LOCK_TYPE_RWLOCK_WRITE:
325 lock_type_str =
"RWLOCK_WRITE";
328 lock_type_str =
"UNKNOWN";
332 log_info(
"Orphaned Release #%u: %s at %p", *count, lock_type_str, record->lock_address);
333 log_info(
" Thread ID: %llu", (
unsigned long long)record->thread_id);
334 log_info(
" Released: %s:%d in %s()", record->file_name, record->line_number, record->function_name);
335 log_info(
" Released at: %lld.%09ld seconds (monotonic)", (
long long)record->acquisition_time.tv_sec,
336 record->acquisition_time.tv_nsec);
339 if (record->backtrace_size > 0) {
346 log_info(
" Release call stack: <capture failed>");
363static void check_long_held_locks(
void) {
364 if (!atomic_load(&g_lock_debug_manager.initialized)) {
371 struct timespec current_time;
372 if (clock_gettime(CLOCK_MONOTONIC, ¤t_time) != 0) {
378 bool found_long_held_lock =
false;
379 lock_record_t *entry, *tmp;
380 HASH_ITER(hash_handle, g_lock_debug_manager.lock_records, entry, tmp) {
382 long long held_sec = current_time.tv_sec - entry->acquisition_time.tv_sec;
383 long held_nsec = current_time.tv_nsec - entry->acquisition_time.tv_nsec;
388 held_nsec += 1000000000;
392 double held_ns = (held_sec * 1000000000.0) + held_nsec;
395 const double WARNING_THRESHOLD_NS = 100000000.0;
396 if (held_ns > WARNING_THRESHOLD_NS) {
397 found_long_held_lock =
true;
400 const char *lock_type_str;
401 switch (entry->lock_type) {
402 case LOCK_TYPE_MUTEX:
403 lock_type_str =
"MUTEX";
405 case LOCK_TYPE_RWLOCK_READ:
406 lock_type_str =
"RWLOCK_READ";
408 case LOCK_TYPE_RWLOCK_WRITE:
409 lock_type_str =
"RWLOCK_WRITE";
412 lock_type_str =
"UNKNOWN";
417 char duration_str[32];
422 "Lock held for %s (threshold: 100ms) - %s at %p\n"
423 " Acquired: %s:%d in %s()\n"
425 duration_str, lock_type_str, entry->lock_address, entry->file_name, entry->line_number,
426 entry->function_name, (
unsigned long long)entry->thread_id);
433 if (found_long_held_lock) {
443static void *debug_thread_func(
void *arg) {
449 if (tcgetattr(STDIN_FILENO, &g_original_termios) == 0) {
450 g_termios_saved =
true;
451 raw = g_original_termios;
452 raw.c_lflag &= ~((tcflag_t)(ICANON | ECHO));
455 if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) != 0) {
456 log_warn(
"Failed to set terminal to raw mode for lock debug");
457 g_termios_saved =
false;
460 log_warn(
"Failed to get terminal attributes for lock debug");
464 log_debug(
"Lock debug thread started - press '?' to print lock state");
466 while (atomic_load(&g_lock_debug_manager.debug_thread_running)) {
468 check_long_held_locks();
471 if (atomic_exchange(&g_lock_debug_manager.should_print_locks,
false)) {
472 lock_debug_print_state();
480 lock_debug_print_state();
489 struct timeval timeout;
492 FD_SET(STDIN_FILENO, &readfds);
494 timeout.tv_usec = 100000;
496 int result = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &timeout);
497 if (result > 0 && FD_ISSET(STDIN_FILENO, &readfds)) {
499 if (read(STDIN_FILENO, input, 1) == 1) {
500 if (input[0] ==
'?') {
501 lock_debug_print_state();
512 if (g_termios_saved) {
513 tcsetattr(STDIN_FILENO, TCSANOW, &g_original_termios);
514 g_termios_saved =
false;
526int lock_debug_init(
void) {
527 log_info(
"Starting lock debug system initialization...");
529 if (atomic_load(&g_lock_debug_manager.initialized)) {
530 log_info(
"Lock debug system already initialized");
535 atomic_store(&g_initializing,
true);
538 g_lock_debug_manager.lock_records = NULL;
539 g_lock_debug_manager.usage_stats = NULL;
540 g_lock_debug_manager.orphaned_releases = NULL;
543 if (
rwlock_init(&g_lock_debug_manager.lock_records_lock) != 0) {
544 atomic_store(&g_initializing,
false);
549 if (
rwlock_init(&g_lock_debug_manager.usage_stats_lock) != 0) {
551 atomic_store(&g_initializing,
false);
556 if (
rwlock_init(&g_lock_debug_manager.orphaned_releases_lock) != 0) {
559 atomic_store(&g_initializing,
false);
565 atomic_store(&g_lock_debug_manager.total_locks_acquired, 0);
566 atomic_store(&g_lock_debug_manager.total_locks_released, 0);
567 atomic_store(&g_lock_debug_manager.current_locks_held, 0);
568 atomic_store(&g_lock_debug_manager.debug_thread_running,
false);
569 atomic_store(&g_lock_debug_manager.debug_thread_created,
false);
570 atomic_store(&g_lock_debug_manager.should_print_locks,
false);
574 g_lock_debug_manager.debug_thread = NULL;
582 atomic_store(&g_initializing,
false);
583 atomic_store(&g_lock_debug_manager.initialized,
true);
592int lock_debug_start_thread(
void) {
593 if (!atomic_load(&g_lock_debug_manager.initialized)) {
597 if (atomic_load(&g_lock_debug_manager.debug_thread_running)) {
601 atomic_store(&g_lock_debug_manager.debug_thread_running,
true);
605 if (thread_result != 0) {
607 atomic_store(&g_lock_debug_manager.debug_thread_running,
false);
608 atomic_store(&g_lock_debug_manager.debug_thread_created,
false);
613 atomic_store(&g_lock_debug_manager.debug_thread_created,
true);
617void lock_debug_trigger_print(
void) {
618 if (atomic_load(&g_lock_debug_manager.initialized)) {
619 atomic_store(&g_lock_debug_manager.should_print_locks,
true);
623void lock_debug_cleanup(
void) {
626 bool was_initialized = atomic_exchange(&g_lock_debug_manager.initialized,
false);
627 if (!was_initialized) {
633 if (atomic_load(&g_lock_debug_manager.debug_thread_running)) {
634 atomic_store(&g_lock_debug_manager.debug_thread_running,
false);
641 lock_record_t *entry, *tmp;
642 HASH_ITER(hash_handle, g_lock_debug_manager.lock_records, entry, tmp) {
643 HASH_DELETE(hash_handle, g_lock_debug_manager.lock_records, entry);
644 cleanup_lock_record_callback(entry, &lock_records_cleaned);
646 if (lock_records_cleaned > 0) {
647 log_info(
"Cleaned up %u lock records", lock_records_cleaned);
652 g_lock_debug_manager.lock_records = NULL;
657 lock_usage_stats_t *stats_entry, *stats_tmp;
658 HASH_ITER(hash_handle, g_lock_debug_manager.usage_stats, stats_entry, stats_tmp) {
659 HASH_DELETE(hash_handle, g_lock_debug_manager.usage_stats, stats_entry);
660 cleanup_usage_stats_callback(stats_entry, NULL);
665 g_lock_debug_manager.usage_stats = NULL;
671 uint32_t orphaned_releases_cleaned = 0;
672 lock_record_t *orphan_entry, *orphan_tmp;
673 HASH_ITER(hash_handle, g_lock_debug_manager.orphaned_releases, orphan_entry, orphan_tmp) {
674 HASH_DELETE(hash_handle, g_lock_debug_manager.orphaned_releases, orphan_entry);
675 cleanup_lock_record_callback(orphan_entry, &orphaned_releases_cleaned);
677 if (orphaned_releases_cleaned > 0) {
678 log_info(
"Cleaned up %u orphaned release records", orphaned_releases_cleaned);
683 g_lock_debug_manager.orphaned_releases = NULL;
686 log_info(
"Lock debug system cleaned up");
689void lock_debug_cleanup_thread(
void) {
691 if (atomic_load(&g_lock_debug_manager.debug_thread_running)) {
692 atomic_store(&g_lock_debug_manager.debug_thread_running,
false);
697 if (g_lock_debug_manager.debug_thread != NULL) {
699 if (join_result == 0) {
703 g_lock_debug_manager.debug_thread = NULL;
709 if (atomic_load(&g_lock_debug_manager.debug_thread_created)) {
712 atomic_store(&g_lock_debug_manager.debug_thread_created,
false);
716 if (g_termios_saved) {
717 tcsetattr(STDIN_FILENO, TCSANOW, &g_original_termios);
718 g_termios_saved =
false;
734static bool debug_should_skip_lock_tracking(
void *lock_ptr,
const char *file_name,
const char *function_name) {
735 if (!lock_ptr || !file_name || !function_name) {
740 bool initialized = atomic_load(&g_lock_debug_manager.initialized);
741 bool initializing = atomic_load(&g_initializing);
750 if (strstr(function_name,
"log_") != NULL || strstr(function_name,
"platform_") != NULL ||
751 strstr(function_name,
"create_lock_record") != NULL || strstr(function_name,
"update_usage_stats") != NULL ||
752 strstr(function_name,
"print_") != NULL || strstr(function_name,
"debug_") != NULL ||
753 strstr(function_name,
"lock_debug") != NULL || strstr(file_name,
"symbols.c") != NULL ||
754 strstr(function_name,
"ascii_thread") != NULL) {
765static uint32_t debug_decrement_lock_counter(
void) {
767 uint_fast32_t current = atomic_load(&g_lock_debug_manager.current_locks_held);
769 uint_fast32_t prev = atomic_fetch_sub(&g_lock_debug_manager.current_locks_held, 1);
772 atomic_fetch_add(&g_lock_debug_manager.current_locks_held, 1);
789static bool debug_create_and_insert_lock_record(
void *lock_address, lock_type_t lock_type,
const char *file_name,
790 int line_number,
const char *function_name) {
799 lock_record_t *record = create_lock_record(lock_address, lock_type, file_name, line_number, function_name);
805 if (!atomic_load(&g_lock_debug_manager.initialized)) {
807 free_lock_record(record);
812 lock_record_t *existing = NULL;
813 HASH_FIND(hash_handle, g_lock_debug_manager.lock_records, &record->key,
sizeof(record->key), existing);
818 free_lock_record(record);
823 HASH_ADD(hash_handle, g_lock_debug_manager.lock_records, key,
sizeof(record->key), record);
828 atomic_fetch_add(&g_lock_debug_manager.total_locks_acquired, 1);
829 atomic_fetch_add(&g_lock_debug_manager.current_locks_held, 1);
846static bool debug_process_tracked_unlock(
void *lock_ptr,
uint32_t key,
const char *lock_type_str,
const char *file_name,
847 int line_number,
const char *function_name) {
859 lock_record_t *record = NULL;
860 HASH_FIND(hash_handle, g_lock_debug_manager.lock_records, &key,
sizeof(key), record);
863 struct timespec current_time;
864 long long held_ms = 0;
865 if (clock_gettime(CLOCK_MONOTONIC, ¤t_time) == 0) {
866 long long held_sec = current_time.tv_sec - record->acquisition_time.tv_sec;
867 long held_nsec = current_time.tv_nsec - record->acquisition_time.tv_nsec;
872 held_nsec += 1000000000;
875 held_ms = (held_sec * 1000) + (held_nsec / 1000000);
878 if (held_ms > LOCK_HOLD_TIME_WARNING_MS) {
879 char duration_str[32];
881 log_warn(
"Lock held for %s (threshold: %d ms) at %s:%d in %s()\n"
882 " Lock type: %s, address: %p",
883 duration_str, LOCK_HOLD_TIME_WARNING_MS, file_name, line_number, function_name, lock_type_str,
887 if (record->backtrace_size > 0 && record->backtrace_symbols) {
889 record->backtrace_size, 0, 10, NULL);
892 log_warn(
"No backtrace available. Current backtrace:");
898 HASH_DELETE(hash_handle, g_lock_debug_manager.lock_records, record);
901 free_lock_record(record);
902 atomic_fetch_add(&g_lock_debug_manager.total_locks_released, 1);
903 debug_decrement_lock_counter();
920static void debug_process_untracked_unlock(
void *lock_ptr,
uint32_t key,
const char *lock_type_str,
921 const char *file_name,
int line_number,
const char *function_name) {
923 uint64_t released = atomic_fetch_add(&g_lock_debug_manager.total_locks_released, 1) + 1;
925 atomic_fetch_add(&g_lock_debug_manager.total_locks_released, 1);
927 uint32_t current_held = atomic_load(&g_lock_debug_manager.current_locks_held);
931 if (current_held > 0) {
933 held = debug_decrement_lock_counter();
935 debug_decrement_lock_counter();
945 lock_type_str, lock_ptr, key, file_name, line_number, function_name, (
unsigned long long)released, held);
953 lock_record_t *orphan_record =
SAFE_CALLOC(1,
sizeof(lock_record_t), lock_record_t *);
955 orphan_record->key = key;
956 orphan_record->lock_address = lock_ptr;
957 if (strcmp(lock_type_str,
"MUTEX") == 0) {
958 orphan_record->lock_type = LOCK_TYPE_MUTEX;
959 }
else if (strcmp(lock_type_str,
"READ") == 0) {
960 orphan_record->lock_type = LOCK_TYPE_RWLOCK_READ;
961 }
else if (strcmp(lock_type_str,
"WRITE") == 0) {
962 orphan_record->lock_type = LOCK_TYPE_RWLOCK_WRITE;
965 orphan_record->file_name = file_name;
966 orphan_record->line_number = line_number;
967 orphan_record->function_name = function_name;
968 (void)clock_gettime(CLOCK_MONOTONIC, &orphan_record->acquisition_time);
972 orphan_record->backtrace_size =
973 CaptureStackBackTrace(1, MAX_BACKTRACE_FRAMES, orphan_record->backtrace_buffer, NULL);
975 orphan_record->backtrace_size =
platform_backtrace(orphan_record->backtrace_buffer, MAX_BACKTRACE_FRAMES);
978 if (orphan_record->backtrace_size > 0) {
979 orphan_record->backtrace_symbols =
985 HASH_ADD(hash_handle, g_lock_debug_manager.orphaned_releases, key,
sizeof(orphan_record->key), orphan_record);
995 if (debug_should_skip_lock_tracking(mutex, file_name, function_name)) {
1006 debug_create_and_insert_lock_record(mutex, LOCK_TYPE_MUTEX, file_name, line_number, function_name);
1012 if (debug_should_skip_lock_tracking(mutex, file_name, function_name)) {
1024 debug_create_and_insert_lock_record(mutex, LOCK_TYPE_MUTEX, file_name, line_number, function_name);
1030 if (debug_should_skip_lock_tracking(mutex, file_name, function_name)) {
1035 uint32_t key = lock_record_key(mutex, LOCK_TYPE_MUTEX);
1036 if (!debug_process_tracked_unlock(mutex, key,
"MUTEX", file_name, line_number, function_name)) {
1038 uint32_t current_held = atomic_load(&g_lock_debug_manager.current_locks_held);
1040 if (current_held > 0) {
1042 debug_process_untracked_unlock(mutex, key,
"MUTEX", file_name, line_number, function_name);
1053 if (debug_should_skip_lock_tracking(
rwlock, file_name, function_name)) {
1064 debug_create_and_insert_lock_record(
rwlock, LOCK_TYPE_RWLOCK_READ, file_name, line_number, function_name);
1070 if (debug_should_skip_lock_tracking(
rwlock, file_name, function_name)) {
1081 debug_create_and_insert_lock_record(
rwlock, LOCK_TYPE_RWLOCK_WRITE, file_name, line_number, function_name);
1087 if (debug_should_skip_lock_tracking(
rwlock, file_name, function_name)) {
1092 uint32_t read_key = lock_record_key(
rwlock, LOCK_TYPE_RWLOCK_READ);
1094 if (!debug_process_tracked_unlock(
rwlock, read_key,
"READ", file_name, line_number, function_name)) {
1095 debug_process_untracked_unlock(
rwlock, read_key,
"READ", file_name, line_number, function_name);
1102 if (debug_should_skip_lock_tracking(
rwlock, file_name, function_name)) {
1107 uint32_t write_key = lock_record_key(
rwlock, LOCK_TYPE_RWLOCK_WRITE);
1108 if (!debug_process_tracked_unlock(
rwlock, write_key,
"WRITE", file_name, line_number, function_name)) {
1109 debug_process_untracked_unlock(
rwlock, write_key,
"WRITE", file_name, line_number, function_name);
1123 if (total_acquired) {
1124 *total_acquired = atomic_load(&g_lock_debug_manager.total_locks_acquired);
1126 if (total_released) {
1127 *total_released = atomic_load(&g_lock_debug_manager.total_locks_released);
1129 if (currently_held) {
1130 *currently_held = atomic_load(&g_lock_debug_manager.current_locks_held);
1135 bool initialized = atomic_load(&g_lock_debug_manager.initialized);
1136 bool initializing = atomic_load(&g_initializing);
1142void lock_debug_print_state(
void) {
1143 if (!atomic_load(&g_lock_debug_manager.initialized)) {
1144 log_warn(
"Lock debug system not initialized.");
1152 uint64_t total_acquired = atomic_load(&g_lock_debug_manager.total_locks_acquired);
1153 uint64_t total_released = atomic_load(&g_lock_debug_manager.total_locks_released);
1154 uint32_t currently_held = atomic_load(&g_lock_debug_manager.current_locks_held);
1157 char log_buffer[32768];
1161 struct lock_collector {
1165 } lock_collector = {0, log_buffer, &offset};
1167 lock_record_t *entry, *tmp;
1168 HASH_ITER(hash_handle, g_lock_debug_manager.lock_records, entry, tmp) {
1169 collect_lock_record_callback(entry, &lock_collector);
1171 uint32_t active_locks = lock_collector.count;
1176 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
"=== LOCK DEBUG STATE ===\n");
1179 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
"Historical Statistics:\n");
1180 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1181 " Total locks acquired: %llu\n", (
unsigned long long)total_acquired);
1182 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1183 " Total locks released: %llu\n", (
unsigned long long)total_released);
1184 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
" Currently held: %u\n",
1188 if (total_acquired >= total_released) {
1189 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1190 " Net locks (acquired - released): %lld\n", (
long long)(total_acquired - total_released));
1193 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1194 " *** ERROR: More releases (%llu) than acquires (%llu)! Difference: -%lld ***\n",
1195 (
unsigned long long)total_released, (
unsigned long long)total_acquired,
1196 (
long long)(total_released - total_acquired));
1197 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1198 " *** This indicates lock tracking was not enabled for some acquires ***\n");
1203 snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
"\n=== Currently Active Locks ===\n");
1204 if (active_locks == 0) {
1206 snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
" No locks currently held.\n");
1208 if (currently_held > 0) {
1209 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1210 " *** CONSISTENCY WARNING: Counter shows %u held locks but no records found! ***\n",
1212 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1213 " *** This may indicate a crash during lock acquisition or hash table corruption. ***\n");
1216 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1217 " *** DEBUG: Hash table stats for lock_records: ***\n");
1219 size_t count = HASH_CNT(hash_handle, g_lock_debug_manager.lock_records);
1221 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1222 " *** Hash table size: %zu ***\n", count);
1224 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1225 " *** Hash table has entries but iteration didn't find them! ***\n");
1229 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
" Active locks: %u\n",
1232 if (active_locks != currently_held) {
1233 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1234 " *** CONSISTENCY WARNING: Found %u active locks but counter shows %u! ***\n", active_locks,
1243 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1244 "\n=== Lock Usage Statistics by Code Location ===\n");
1247 uint32_t total_usage_locations = 0;
1248 lock_usage_stats_t *stats_entry, *stats_tmp;
1249 HASH_ITER(hash_handle, g_lock_debug_manager.usage_stats, stats_entry, stats_tmp) {
1250 print_usage_stats_callback(stats_entry, &total_usage_locations);
1255 if (total_usage_locations == 0) {
1256 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1257 " No lock usage statistics available.\n");
1259 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1260 " Total code locations with lock usage: %u\n", total_usage_locations);
1264 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1265 "\n=== Orphaned Releases (unlocks without corresponding locks) ===\n");
1269 lock_record_t *orphan_entry, *orphan_tmp;
1270 HASH_ITER(hash_handle, g_lock_debug_manager.orphaned_releases, orphan_entry, orphan_tmp) {
1274 const char *lock_type_str;
1275 switch (orphan_entry->lock_type) {
1276 case LOCK_TYPE_MUTEX:
1277 lock_type_str =
"MUTEX";
1279 case LOCK_TYPE_RWLOCK_READ:
1280 lock_type_str =
"RWLOCK_READ";
1282 case LOCK_TYPE_RWLOCK_WRITE:
1283 lock_type_str =
"RWLOCK_WRITE";
1286 lock_type_str =
"UNKNOWN";
1290 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1291 "Orphaned Release #%u: %s at %p\n", orphan_count, lock_type_str, orphan_entry->lock_address);
1292 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
" Thread ID: %llu\n",
1293 (
unsigned long long)orphan_entry->thread_id);
1294 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
" Released: %s:%d in %s()\n",
1295 orphan_entry->file_name, orphan_entry->line_number, orphan_entry->function_name);
1296 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1297 " Released at: %lld.%09ld seconds (monotonic)\n",
1298 (
long long)orphan_entry->acquisition_time.tv_sec, orphan_entry->acquisition_time.tv_nsec);
1301 if (orphan_entry->backtrace_size > 0) {
1305 "Release call stack", symbols, orphan_entry->backtrace_size, 0, 0, NULL);
1310 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1311 " Release call stack: <capture failed>\n");
1317 if (orphan_count == 0) {
1319 snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
" No orphaned releases found.\n");
1321 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1322 " Total orphaned releases: %u\n", orphan_count);
1323 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1324 " *** WARNING: %u releases without corresponding locks detected! ***\n", orphan_count);
1325 offset += snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1326 " *** This indicates double unlocks or missing lock acquisitions! ***\n");
1331 snprintf(log_buffer + offset,
SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
"\n=== End Lock Debug State ===\n");
🔌 Cross-platform abstraction layer umbrella header for ascii-chat
⚠️‼️ Error and/or exit() when things go bad.
#️⃣ FNV-1a Hash Function Implementation
#define SAFE_CALLOC(count, size, cast)
#define SAFE_BUFFER_SIZE(buffer_size, offset)
Safe buffer size calculation for snprintf.
unsigned long long uint64_t
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
uint64_t asciichat_thread_current_id(void)
#define LOG_RATE_FAST
Log rate limit: 1 second (1,000,000 microseconds)
#define log_warn(...)
Log a WARN message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
int format_duration_ns(double nanoseconds, char *buffer, size_t buffer_size)
Format nanoseconds as human-readable duration string.
int format_duration_ms(double milliseconds, char *buffer, size_t buffer_size)
Format milliseconds as human-readable duration string.
🔒 Lock debugging and deadlock detection system for ascii-chat
rwlock_t rwlock
Read-write lock for thread-safe access (uthash requires external locking)
⏱️ High-precision timing utilities using sokol_time.h and uthash
#️⃣ Wrapper for uthash.h that ensures common.h is included first