9#include <ascii-chat/debug/lock.h>
15#include <ascii-chat/common.h>
16#include <ascii-chat/common/buffer_sizes.h>
17#include <ascii-chat/asciichat_errno.h>
18#include <ascii-chat/platform/abstraction.h>
19#include <ascii-chat/util/fnv1a.h>
20#include <ascii-chat/util/time.h>
21#include <ascii-chat/util/path.h>
22#include <ascii-chat/uthash/uthash.h>
30#define TRACE_LOCK_DEBUG 0
35#define LOCK_TRACE(fmt, ...) fprintf(stderr, "[LOCK_TRACE] %s:%d " fmt "\n", __func__, __LINE__, ##__VA_ARGS__)
37#define LOCK_OP_TRACE(op, lock_type, file, line, func) \
38 fprintf(stderr, "[LOCK_OP] %s %s @ %s:%d in %s()\n", op, lock_type, file, line, func)
40#define LOCK_TRACE(fmt, ...) ((void)0)
41#define LOCK_OP_TRACE(op, lock_type, file, line, func) ((void)0)
48#include <sys/select.h>
65#define LOCK_HOLD_TIME_WARNING_MS 500
73#define SAFE_BUFFER_SIZE(buffer_size, offset) \
74 ((offset) < 0 || (size_t)(offset) >= (buffer_size) ? 0 : (buffer_size) - (size_t)(offset))
80lock_debug_manager_t g_lock_debug_manager = {0};
81atomic_bool g_initializing =
false;
94static lock_record_t *create_lock_record(
void *lock_address, lock_type_t lock_type,
const char *file_name,
95 int line_number,
const char *function_name) {
96 lock_record_t *record = SAFE_CALLOC(1,
sizeof(lock_record_t), lock_record_t *);
99 record->key = lock_record_key(lock_address, lock_type);
100 record->lock_address = lock_address;
101 record->lock_type = lock_type;
103 record->file_name = file_name;
104 record->line_number = line_number;
105 record->function_name = function_name;
114 record->backtrace_size =
platform_backtrace(record->backtrace_buffer, MAX_BACKTRACE_FRAMES);
115 if (record->backtrace_size > 0) {
117 if (!record->backtrace_symbols) {
118 static bool symbolize_error_logged =
false;
119 if (!symbolize_error_logged) {
120 log_warn(
"Failed to symbolize backtrace for lock record (backtrace support may be unavailable)");
121 symbolize_error_logged =
true;
127 static bool backtrace_error_logged =
false;
128 if (!backtrace_error_logged) {
129 log_debug(
"Backtrace not available for lock debugging");
130 backtrace_error_logged =
true;
141static void free_lock_record(lock_record_t *record) {
143 SET_ERRNO(ERROR_INVALID_STATE,
"Freeing NULL lock record");
148 if (record->backtrace_symbols) {
164static void collect_lock_record_callback(lock_record_t *record,
void *user_data) {
165 struct lock_collector {
169 } *collector = (
struct lock_collector *)user_data;
172 int *offset = collector->offset;
173 char *buffer = collector->buffer;
177 const char *lock_type_str;
178 switch (record->lock_type) {
179 case LOCK_TYPE_MUTEX:
180 lock_type_str =
"MUTEX";
182 case LOCK_TYPE_RWLOCK_READ:
183 lock_type_str =
"RWLOCK_READ";
185 case LOCK_TYPE_RWLOCK_WRITE:
186 lock_type_str =
"RWLOCK_WRITE";
189 lock_type_str =
"UNKNOWN";
194 collector->count, lock_type_str, record->lock_address);
196 (
unsigned long long)record->thread_id);
203 uint64_t held_ns =
time_elapsed_ns(record->acquisition_time_ns, current_time_ns);
204 char duration_str[32];
206 *offset +=
safe_snprintf(buffer + *offset, SAFE_BUFFER_SIZE(
buffer_size, *offset),
" Held for: %s\n", duration_str);
209 if (record->backtrace_size > 0) {
211 *offset += platform_format_backtrace_symbols(buffer + *offset, SAFE_BUFFER_SIZE(
buffer_size, *offset),
"Call stack",
212 symbols, record->backtrace_size, 0, 0, NULL);
227static void cleanup_lock_record_callback(lock_record_t *record,
void *user_data) {
228 uint32_t *count = (uint32_t *)user_data;
231 free_lock_record(record);
239static void print_usage_stats_callback(lock_usage_stats_t *stats,
void *user_data) {
240 uint32_t *count = (uint32_t *)user_data;
244 const char *lock_type_str;
245 switch (stats->lock_type) {
246 case LOCK_TYPE_MUTEX:
247 lock_type_str =
"MUTEX";
249 case LOCK_TYPE_RWLOCK_READ:
250 lock_type_str =
"RWLOCK_READ";
252 case LOCK_TYPE_RWLOCK_WRITE:
253 lock_type_str =
"RWLOCK_WRITE";
256 lock_type_str =
"UNKNOWN";
261 uint64_t avg_hold_time_ns = stats->total_hold_time_ns / stats->total_acquisitions;
264 char log_message[BUFFER_SIZE_LARGE];
267 offset +=
safe_snprintf(log_message + offset, SAFE_BUFFER_SIZE(
sizeof(log_message), offset),
268 "Usage #%u: %s at %s:%d in %s()\n", *count, lock_type_str,
270 offset +=
safe_snprintf(log_message + offset, SAFE_BUFFER_SIZE(
sizeof(log_message), offset),
271 " Total acquisitions: %llu\n", (
unsigned long long)stats->total_acquisitions);
272 offset +=
safe_snprintf(log_message + offset, SAFE_BUFFER_SIZE(
sizeof(log_message), offset),
273 " Total hold time: %llu.%03llu ms\n",
274 (
unsigned long long)(stats->total_hold_time_ns / NS_PER_MS_INT),
275 (
unsigned long long)((stats->total_hold_time_ns % NS_PER_MS_INT) / 1000));
277 safe_snprintf(log_message + offset, SAFE_BUFFER_SIZE(
sizeof(log_message), offset),
278 " Average hold time: %llu.%03llu ms\n", (
unsigned long long)(avg_hold_time_ns / NS_PER_MS_INT),
279 (
unsigned long long)((avg_hold_time_ns % NS_PER_MS_INT) / 1000));
281 safe_snprintf(log_message + offset, SAFE_BUFFER_SIZE(
sizeof(log_message), offset),
282 " Max hold time: %llu.%03llu ms\n", (
unsigned long long)(stats->max_hold_time_ns / NS_PER_MS_INT),
283 (
unsigned long long)((stats->max_hold_time_ns % NS_PER_MS_INT) / 1000));
285 safe_snprintf(log_message + offset, SAFE_BUFFER_SIZE(
sizeof(log_message), offset),
286 " Min hold time: %llu.%03llu ms\n", (
unsigned long long)(stats->min_hold_time_ns / NS_PER_MS_INT),
287 (
unsigned long long)((stats->min_hold_time_ns % NS_PER_MS_INT) / 1000));
288 char first_acq_str[32];
289 format_duration_ns((
double)stats->first_acquisition_ns, first_acq_str,
sizeof(first_acq_str));
290 offset +=
safe_snprintf(log_message + offset, SAFE_BUFFER_SIZE(
sizeof(log_message), offset),
291 " First acquisition: %s\n", first_acq_str);
293 char last_acq_str[32];
294 format_duration_ns((
double)stats->last_acquisition_ns, last_acq_str,
sizeof(last_acq_str));
295 offset +=
safe_snprintf(log_message + offset, SAFE_BUFFER_SIZE(
sizeof(log_message), offset),
" Last acquisition: %s",
298 log_info(
"%s", log_message);
306static void cleanup_usage_stats_callback(lock_usage_stats_t *stats,
void *user_data) {
316void print_orphaned_release_callback(lock_record_t *record,
void *user_data) {
317 uint32_t *count = (uint32_t *)user_data;
321 const char *lock_type_str;
322 switch (record->lock_type) {
323 case LOCK_TYPE_MUTEX:
324 lock_type_str =
"MUTEX";
326 case LOCK_TYPE_RWLOCK_READ:
327 lock_type_str =
"RWLOCK_READ";
329 case LOCK_TYPE_RWLOCK_WRITE:
330 lock_type_str =
"RWLOCK_WRITE";
333 lock_type_str =
"UNKNOWN";
337 log_info(
"Orphaned Release #%u: %s at %p", *count, lock_type_str, record->lock_address);
338 log_info(
" Thread ID: %llu", (
unsigned long long)record->thread_id);
340 record->function_name);
341 char release_time_str[32];
342 format_duration_ns((
double)record->acquisition_time_ns, release_time_str,
sizeof(release_time_str));
343 log_info(
" Released at: %s (nanosecond %llu)", release_time_str, (
unsigned long long)record->acquisition_time_ns);
346 if (record->backtrace_size > 0) {
353 log_info(
" Release call stack: <capture failed>");
372 char duration_str[32];
373 const char *lock_type_str;
375 char file_name[BUFFER_SIZE_SMALL];
377 char function_name[128];
379 void *backtrace_buffer[MAX_BACKTRACE_FRAMES];
381} long_held_lock_info_t;
383#define MAX_LONG_HELD_LOCKS 32
385static void check_long_held_locks(
void) {
386 if (!atomic_load(&g_lock_debug_manager.initialized)) {
389 LOCK_TRACE(
"acquiring lock_records_lock (read)");
397 long_held_lock_info_t long_held_locks[MAX_LONG_HELD_LOCKS];
398 int num_long_held = 0;
406 lock_record_t *entry, *tmp;
407 HASH_ITER(hash_handle, g_lock_debug_manager.lock_records, entry, tmp) {
408 if (num_long_held >= MAX_LONG_HELD_LOCKS) {
413 uint64_t held_ns =
time_elapsed_ns(entry->acquisition_time_ns, current_time_ns);
415 if (held_ns > 100 * NS_PER_MS_INT) {
416 long_held_lock_info_t *info = &long_held_locks[num_long_held];
419 switch (entry->lock_type) {
420 case LOCK_TYPE_MUTEX:
421 info->lock_type_str =
"MUTEX";
423 case LOCK_TYPE_RWLOCK_READ:
424 info->lock_type_str =
"RWLOCK_READ";
426 case LOCK_TYPE_RWLOCK_WRITE:
427 info->lock_type_str =
"RWLOCK_WRITE";
430 info->lock_type_str =
"UNKNOWN";
436 info->lock_address = entry->lock_address;
437 SAFE_STRNCPY(info->file_name, entry->file_name,
sizeof(info->file_name) - 1);
438 info->file_name[
sizeof(info->file_name) - 1] =
'\0';
439 info->line_number = entry->line_number;
440 SAFE_STRNCPY(info->function_name, entry->function_name,
sizeof(info->function_name) - 1);
441 info->function_name[
sizeof(info->function_name) - 1] =
'\0';
442 info->thread_id = entry->thread_id;
445 info->backtrace_size = entry->backtrace_size;
446 if (entry->backtrace_size > 0) {
447 memcpy(info->backtrace_buffer, entry->backtrace_buffer, (
size_t)entry->backtrace_size *
sizeof(
void *));
456 LOCK_TRACE(
"released lock_records_lock (read), found %d long-held locks", num_long_held);
459 for (
int i = 0; i < num_long_held; i++) {
460 long_held_lock_info_t *info = &long_held_locks[i];
461 log_warn_every(LOG_RATE_FAST,
462 "Lock held for %s (threshold: 100ms) - %s at %p\n"
463 " Acquired: %s:%d in %s()\n"
465 info->duration_str, info->lock_type_str, info->lock_address,
467 (
unsigned long long)info->thread_id);
471 for (
int i = 0; i < num_long_held; i++) {
472 long_held_lock_info_t *info = &long_held_locks[i];
473 if (info->backtrace_size > 0) {
488static void *debug_thread_func(
void *arg) {
491 log_debug(
"Lock debug thread started (use SIGUSR1 to print lock state)");
492 LOCK_TRACE(
"debug thread loop starting");
494 while (atomic_load(&g_lock_debug_manager.debug_thread_running)) {
496 check_long_held_locks();
499 if (atomic_exchange(&g_lock_debug_manager.should_print_locks,
false)) {
523 log_debug(
"Starting lock debug system initialization...");
525 if (atomic_load(&g_lock_debug_manager.initialized)) {
526 log_info(
"Lock debug system already initialized");
531 atomic_store(&g_initializing,
true);
534 g_lock_debug_manager.lock_records = NULL;
535 g_lock_debug_manager.usage_stats = NULL;
536 g_lock_debug_manager.orphaned_releases = NULL;
539 if (
rwlock_init(&g_lock_debug_manager.lock_records_lock) != 0) {
540 atomic_store(&g_initializing,
false);
541 SET_ERRNO(ERROR_THREAD,
"Failed to initialize lock_records rwlock");
545 if (
rwlock_init(&g_lock_debug_manager.usage_stats_lock) != 0) {
546 rwlock_destroy(&g_lock_debug_manager.lock_records_lock);
547 atomic_store(&g_initializing,
false);
548 SET_ERRNO(ERROR_THREAD,
"Failed to initialize usage_stats rwlock");
552 if (
rwlock_init(&g_lock_debug_manager.orphaned_releases_lock) != 0) {
553 rwlock_destroy(&g_lock_debug_manager.lock_records_lock);
554 rwlock_destroy(&g_lock_debug_manager.usage_stats_lock);
555 atomic_store(&g_initializing,
false);
556 SET_ERRNO(ERROR_THREAD,
"Failed to initialize orphaned_releases rwlock");
561 atomic_store(&g_lock_debug_manager.total_locks_acquired, 0);
562 atomic_store(&g_lock_debug_manager.total_locks_released, 0);
563 atomic_store(&g_lock_debug_manager.current_locks_held, 0);
564 atomic_store(&g_lock_debug_manager.debug_thread_running,
false);
565 atomic_store(&g_lock_debug_manager.debug_thread_created,
false);
566 atomic_store(&g_lock_debug_manager.should_print_locks,
false);
570 g_lock_debug_manager.debug_thread = NULL;
578 atomic_store(&g_initializing,
false);
579 atomic_store(&g_lock_debug_manager.initialized,
true);
589 if (!atomic_load(&g_lock_debug_manager.initialized)) {
593 if (atomic_load(&g_lock_debug_manager.debug_thread_running)) {
597 atomic_store(&g_lock_debug_manager.debug_thread_running,
true);
601 if (thread_result != 0) {
602 SET_ERRNO(ERROR_THREAD,
"Failed to create lock debug thread: %d", thread_result);
603 atomic_store(&g_lock_debug_manager.debug_thread_running,
false);
604 atomic_store(&g_lock_debug_manager.debug_thread_created,
false);
609 atomic_store(&g_lock_debug_manager.debug_thread_created,
true);
614 if (atomic_load(&g_lock_debug_manager.initialized)) {
615 atomic_store(&g_lock_debug_manager.should_print_locks,
true);
622 bool was_initialized = atomic_exchange(&g_lock_debug_manager.initialized,
false);
623 if (!was_initialized) {
629 if (atomic_load(&g_lock_debug_manager.debug_thread_running)) {
630 atomic_store(&g_lock_debug_manager.debug_thread_running,
false);
636 uint32_t lock_records_cleaned = 0;
637 lock_record_t *entry, *tmp;
638 HASH_ITER(hash_handle, g_lock_debug_manager.lock_records, entry, tmp) {
639 HASH_DELETE(hash_handle, g_lock_debug_manager.lock_records, entry);
640 cleanup_lock_record_callback(entry, &lock_records_cleaned);
642 if (lock_records_cleaned > 0) {
643 log_info(
"Cleaned up %u lock records", lock_records_cleaned);
647 rwlock_destroy(&g_lock_debug_manager.lock_records_lock);
648 g_lock_debug_manager.lock_records = NULL;
653 lock_usage_stats_t *stats_entry, *stats_tmp;
654 HASH_ITER(hash_handle, g_lock_debug_manager.usage_stats, stats_entry, stats_tmp) {
655 HASH_DELETE(hash_handle, g_lock_debug_manager.usage_stats, stats_entry);
656 cleanup_usage_stats_callback(stats_entry, NULL);
660 rwlock_destroy(&g_lock_debug_manager.usage_stats_lock);
661 g_lock_debug_manager.usage_stats = NULL;
667 uint32_t orphaned_releases_cleaned = 0;
668 lock_record_t *orphan_entry, *orphan_tmp;
669 HASH_ITER(hash_handle, g_lock_debug_manager.orphaned_releases, orphan_entry, orphan_tmp) {
670 HASH_DELETE(hash_handle, g_lock_debug_manager.orphaned_releases, orphan_entry);
671 cleanup_lock_record_callback(orphan_entry, &orphaned_releases_cleaned);
673 if (orphaned_releases_cleaned > 0) {
674 log_info(
"Cleaned up %u orphaned release records", orphaned_releases_cleaned);
678 rwlock_destroy(&g_lock_debug_manager.orphaned_releases_lock);
679 g_lock_debug_manager.orphaned_releases = NULL;
682 log_debug(
"Lock debug system cleaned up");
687 if (atomic_load(&g_lock_debug_manager.debug_thread_running)) {
688 atomic_store(&g_lock_debug_manager.debug_thread_running,
false);
693 if (g_lock_debug_manager.debug_thread != NULL) {
695 if (join_result == 0) {
699 g_lock_debug_manager.debug_thread = NULL;
705 if (atomic_load(&g_lock_debug_manager.debug_thread_created)) {
708 atomic_store(&g_lock_debug_manager.debug_thread_created,
false);
726static bool debug_should_skip_lock_tracking(
void *lock_ptr,
const char *file_name,
const char *function_name) {
727 if (!lock_ptr || !file_name || !function_name) {
732 bool initialized = atomic_load(&g_lock_debug_manager.initialized);
733 bool initializing = atomic_load(&g_initializing);
742 if (strstr(function_name,
"log_") != NULL || strstr(function_name,
"platform_") != NULL ||
743 strstr(function_name,
"create_lock_record") != NULL || strstr(function_name,
"update_usage_stats") != NULL ||
744 strstr(function_name,
"print_") != NULL || strstr(function_name,
"debug_") != NULL ||
745 strstr(function_name,
"lock_debug") != NULL || strstr(file_name,
"symbols.c") != NULL ||
746 strstr(function_name,
"ascii_thread") != NULL ||
747 strstr(function_name,
"maybe_rotate_log") != NULL ||
748 strstr(function_name,
"rotate_log") != NULL) {
759static uint32_t debug_decrement_lock_counter(
void) {
761 uint_fast32_t current = atomic_load(&g_lock_debug_manager.current_locks_held);
763 uint_fast32_t prev = atomic_fetch_sub(&g_lock_debug_manager.current_locks_held, 1);
766 atomic_fetch_add(&g_lock_debug_manager.current_locks_held, 1);
769 return (uint32_t)(prev - 1);
783static bool debug_create_and_insert_lock_record(
void *lock_address, lock_type_t lock_type,
const char *file_name,
784 int line_number,
const char *function_name) {
786 UNUSED(lock_address);
790 UNUSED(function_name);
793 lock_record_t *record = create_lock_record(lock_address, lock_type, file_name, line_number, function_name);
796 LOCK_TRACE(
"acquiring lock_records_lock (write) for %s:%d %s", file_name, line_number, function_name);
798 LOCK_TRACE(
"acquired lock_records_lock (write)");
801 if (!atomic_load(&g_lock_debug_manager.initialized)) {
803 free_lock_record(record);
808 lock_record_t *existing = NULL;
809 HASH_FIND(hash_handle, g_lock_debug_manager.lock_records, &record->key,
sizeof(record->key), existing);
814 free_lock_record(record);
819 HASH_ADD(hash_handle, g_lock_debug_manager.lock_records, key,
sizeof(record->key), record);
823 LOCK_TRACE(
"released lock_records_lock (write) - record added");
825 atomic_fetch_add(&g_lock_debug_manager.total_locks_acquired, 1);
826 atomic_fetch_add(&g_lock_debug_manager.current_locks_held, 1);
830 LOCK_TRACE(
"record allocation failed");
844static bool debug_process_tracked_unlock(
void *lock_ptr, uint32_t key,
const char *lock_type_str,
const char *file_name,
845 int line_number,
const char *function_name) {
848 UNUSED(lock_type_str);
851 UNUSED(function_name);
860 bool should_log_warning =
false;
861 char deferred_duration_str[32] = {0};
862 char deferred_file_name[BUFFER_SIZE_SMALL] = {0};
863 int deferred_line_number = 0;
864 char deferred_function_name[128] = {0};
865 void *deferred_lock_ptr = NULL;
866 const char *deferred_lock_type_str = NULL;
867 char **deferred_backtrace_symbols = NULL;
868 int deferred_backtrace_size = 0;
871 LOCK_TRACE(
"acquiring lock_records_lock (write) for unlock %s %s:%d", lock_type_str, file_name, line_number);
873 LOCK_TRACE(
"acquired lock_records_lock (write) for unlock");
875 lock_record_t *record = NULL;
876 HASH_FIND(hash_handle, g_lock_debug_manager.lock_records, &key,
sizeof(key), record);
880 uint64_t held_ns =
time_elapsed_ns(record->acquisition_time_ns, current_time_ns);
881 uint64_t held_ms = time_ns_to_ms(held_ns);
884 if (held_ms > LOCK_HOLD_TIME_WARNING_MS) {
885 should_log_warning =
true;
886 format_duration_ms((
double)held_ms, deferred_duration_str,
sizeof(deferred_duration_str));
887 SAFE_STRNCPY(deferred_file_name, file_name,
sizeof(deferred_file_name) - 1);
888 deferred_line_number = line_number;
889 SAFE_STRNCPY(deferred_function_name, function_name,
sizeof(deferred_function_name) - 1);
890 deferred_lock_ptr = lock_ptr;
891 deferred_lock_type_str = lock_type_str;
894 if (record->backtrace_size > 0 && record->backtrace_symbols) {
895 deferred_backtrace_size = record->backtrace_size;
896 deferred_backtrace_symbols = record->backtrace_symbols;
897 record->backtrace_symbols = NULL;
901 HASH_DELETE(hash_handle, g_lock_debug_manager.lock_records, record);
903 LOCK_TRACE(
"released lock_records_lock (write) - record removed");
905 free_lock_record(record);
906 atomic_fetch_add(&g_lock_debug_manager.total_locks_released, 1);
907 debug_decrement_lock_counter();
910 if (should_log_warning) {
911 log_warn(
"Lock held for %s (threshold: %d ms) at %s:%d in %s()\n"
912 " Lock type: %s, address: %p",
914 deferred_line_number, deferred_function_name, deferred_lock_type_str, deferred_lock_ptr);
917 if (deferred_backtrace_size > 0 && deferred_backtrace_symbols) {
919 deferred_backtrace_size, 0, 10, NULL);
920 free(deferred_backtrace_symbols);
923 log_warn(
"No backtrace available. Current backtrace:");
932 LOCK_TRACE(
"released lock_records_lock (write) - no record found for %s", lock_type_str);
945static void debug_process_untracked_unlock(
void *lock_ptr, uint32_t key,
const char *lock_type_str,
946 const char *file_name,
int line_number,
const char *function_name) {
947 atomic_fetch_add(&g_lock_debug_manager.total_locks_released, 1);
948 uint32_t current_held = atomic_load(&g_lock_debug_manager.current_locks_held);
949 if (current_held > 0) {
950 debug_decrement_lock_counter();
956 log_warn_every(10 * NS_PER_SEC_INT,
"Attempting to release %s lock when no locks held!", lock_type_str);
965 lock_record_t *orphan_record = SAFE_CALLOC(1,
sizeof(lock_record_t), lock_record_t *);
967 orphan_record->key = key;
968 orphan_record->lock_address = lock_ptr;
969 if (strcmp(lock_type_str,
"MUTEX") == 0) {
970 orphan_record->lock_type = LOCK_TYPE_MUTEX;
971 }
else if (strcmp(lock_type_str,
"READ") == 0) {
972 orphan_record->lock_type = LOCK_TYPE_RWLOCK_READ;
973 }
else if (strcmp(lock_type_str,
"WRITE") == 0) {
974 orphan_record->lock_type = LOCK_TYPE_RWLOCK_WRITE;
977 orphan_record->file_name = file_name;
978 orphan_record->line_number = line_number;
979 orphan_record->function_name = function_name;
980 orphan_record->acquisition_time_ns =
time_get_ns();
984 orphan_record->backtrace_size =
985 CaptureStackBackTrace(1, MAX_BACKTRACE_FRAMES, orphan_record->backtrace_buffer, NULL);
987 orphan_record->backtrace_size =
platform_backtrace(orphan_record->backtrace_buffer, MAX_BACKTRACE_FRAMES);
990 if (orphan_record->backtrace_size > 0) {
991 orphan_record->backtrace_symbols =
997 HASH_ADD(hash_handle, g_lock_debug_manager.orphaned_releases, key,
sizeof(orphan_record->key), orphan_record);
1006int debug_mutex_lock(mutex_t *mutex,
const char *file_name,
int line_number,
const char *function_name) {
1007 LOCK_OP_TRACE(
"LOCK",
"MUTEX", file_name, line_number, function_name);
1008 if (debug_should_skip_lock_tracking(mutex, file_name, function_name)) {
1019 debug_create_and_insert_lock_record(mutex, LOCK_TYPE_MUTEX, file_name, line_number, function_name);
1024int debug_mutex_trylock(mutex_t *mutex,
const char *file_name,
int line_number,
const char *function_name) {
1025 LOCK_OP_TRACE(
"TRYLOCK",
"MUTEX", file_name, line_number, function_name);
1026 if (debug_should_skip_lock_tracking(mutex, file_name, function_name)) {
1038 debug_create_and_insert_lock_record(mutex, LOCK_TYPE_MUTEX, file_name, line_number, function_name);
1043int debug_mutex_unlock(mutex_t *mutex,
const char *file_name,
int line_number,
const char *function_name) {
1044 LOCK_OP_TRACE(
"UNLOCK",
"MUTEX", file_name, line_number, function_name);
1045 if (debug_should_skip_lock_tracking(mutex, file_name, function_name)) {
1050 uint32_t key = lock_record_key(mutex, LOCK_TYPE_MUTEX);
1051 if (!debug_process_tracked_unlock(mutex, key,
"MUTEX", file_name, line_number, function_name)) {
1053 debug_process_untracked_unlock(mutex, key,
"MUTEX", file_name, line_number, function_name);
1061 LOCK_OP_TRACE(
"LOCK",
"RWLOCK_RD", file_name, line_number, function_name);
1062 if (debug_should_skip_lock_tracking(
rwlock, file_name, function_name)) {
1073 debug_create_and_insert_lock_record(
rwlock, LOCK_TYPE_RWLOCK_READ, file_name, line_number, function_name);
1079 LOCK_OP_TRACE(
"LOCK",
"RWLOCK_WR", file_name, line_number, function_name);
1080 if (debug_should_skip_lock_tracking(
rwlock, file_name, function_name)) {
1091 debug_create_and_insert_lock_record(
rwlock, LOCK_TYPE_RWLOCK_WRITE, file_name, line_number, function_name);
1097 LOCK_OP_TRACE(
"UNLOCK",
"RWLOCK_RD", file_name, line_number, function_name);
1098 if (debug_should_skip_lock_tracking(
rwlock, file_name, function_name)) {
1103 uint32_t read_key = lock_record_key(
rwlock, LOCK_TYPE_RWLOCK_READ);
1105 if (!debug_process_tracked_unlock(
rwlock, read_key,
"READ", file_name, line_number, function_name)) {
1106 debug_process_untracked_unlock(
rwlock, read_key,
"READ", file_name, line_number, function_name);
1113 LOCK_OP_TRACE(
"UNLOCK",
"RWLOCK_WR", file_name, line_number, function_name);
1114 if (debug_should_skip_lock_tracking(
rwlock, file_name, function_name)) {
1119 uint32_t write_key = lock_record_key(
rwlock, LOCK_TYPE_RWLOCK_WRITE);
1120 if (!debug_process_tracked_unlock(
rwlock, write_key,
"WRITE", file_name, line_number, function_name)) {
1121 debug_process_untracked_unlock(
rwlock, write_key,
"WRITE", file_name, line_number, function_name);
1134void lock_debug_get_stats(uint64_t *total_acquired, uint64_t *total_released, uint32_t *currently_held) {
1135 if (total_acquired) {
1136 *total_acquired = atomic_load(&g_lock_debug_manager.total_locks_acquired);
1138 if (total_released) {
1139 *total_released = atomic_load(&g_lock_debug_manager.total_locks_released);
1141 if (currently_held) {
1142 *currently_held = atomic_load(&g_lock_debug_manager.current_locks_held);
1147 bool initialized = atomic_load(&g_lock_debug_manager.initialized);
1148 bool initializing = atomic_load(&g_initializing);
1155 if (!atomic_load(&g_lock_debug_manager.initialized)) {
1156 log_warn(
"Lock debug system not initialized.");
1164 uint64_t total_acquired = atomic_load(&g_lock_debug_manager.total_locks_acquired);
1165 uint64_t total_released = atomic_load(&g_lock_debug_manager.total_locks_released);
1166 uint32_t currently_held = atomic_load(&g_lock_debug_manager.current_locks_held);
1169 char log_buffer[32768];
1173 struct lock_collector {
1177 } lock_collector = {0, log_buffer, &offset};
1179 lock_record_t *entry, *tmp;
1180 HASH_ITER(hash_handle, g_lock_debug_manager.lock_records, entry, tmp) {
1181 collect_lock_record_callback(entry, &lock_collector);
1183 uint32_t active_locks = lock_collector.count;
1189 safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
"=== LOCK DEBUG STATE ===\n");
1193 safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
"Historical Statistics:\n");
1194 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1195 " Total locks acquired: %llu\n", (
unsigned long long)total_acquired);
1196 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1197 " Total locks released: %llu\n", (
unsigned long long)total_released);
1198 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
" Currently held: %u\n",
1202 if (total_acquired >= total_released) {
1203 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1204 " Net locks (acquired - released): %lld\n", (
long long)(total_acquired - total_released));
1207 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1208 " *** ERROR: More releases (%llu) than acquires (%llu)! Difference: -%lld ***\n",
1209 (
unsigned long long)total_released, (
unsigned long long)total_acquired,
1210 (
long long)(total_released - total_acquired));
1211 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1212 " *** This indicates lock tracking was not enabled for some acquires ***\n");
1216 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1217 "\n=== Currently Active Locks ===\n");
1218 if (active_locks == 0) {
1219 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1220 " No locks currently held.\n");
1222 if (currently_held > 0) {
1223 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1224 " *** CONSISTENCY WARNING: Counter shows %u held locks but no records found! ***\n",
1227 safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1228 " *** This may indicate a crash during lock acquisition or hash table corruption. ***\n");
1231 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1232 " *** DEBUG: Hash table stats for lock_records: ***\n");
1234 size_t count = HASH_CNT(hash_handle, g_lock_debug_manager.lock_records);
1236 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1237 " *** Hash table size: %zu ***\n", count);
1239 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1240 " *** Hash table has entries but iteration didn't find them! ***\n");
1244 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
" Active locks: %u\n",
1247 if (active_locks != currently_held) {
1248 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1249 " *** CONSISTENCY WARNING: Found %u active locks but counter shows %u! ***\n",
1250 active_locks, currently_held);
1258 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1259 "\n=== Lock Usage Statistics by Code Location ===\n");
1262 uint32_t total_usage_locations = 0;
1263 lock_usage_stats_t *stats_entry, *stats_tmp;
1264 HASH_ITER(hash_handle, g_lock_debug_manager.usage_stats, stats_entry, stats_tmp) {
1265 print_usage_stats_callback(stats_entry, &total_usage_locations);
1270 if (total_usage_locations == 0) {
1271 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1272 " No lock usage statistics available.\n");
1274 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1275 " Total code locations with lock usage: %u\n", total_usage_locations);
1279 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1280 "\n=== Orphaned Releases (unlocks without corresponding locks) ===\n");
1283 uint32_t orphan_count = 0;
1284 lock_record_t *orphan_entry, *orphan_tmp;
1285 HASH_ITER(hash_handle, g_lock_debug_manager.orphaned_releases, orphan_entry, orphan_tmp) {
1289 const char *lock_type_str;
1290 switch (orphan_entry->lock_type) {
1291 case LOCK_TYPE_MUTEX:
1292 lock_type_str =
"MUTEX";
1294 case LOCK_TYPE_RWLOCK_READ:
1295 lock_type_str =
"RWLOCK_READ";
1297 case LOCK_TYPE_RWLOCK_WRITE:
1298 lock_type_str =
"RWLOCK_WRITE";
1301 lock_type_str =
"UNKNOWN";
1306 safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1307 "Orphaned Release #%u: %s at %p\n", orphan_count, lock_type_str, orphan_entry->lock_address);
1308 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
" Thread ID: %llu\n",
1309 (
unsigned long long)orphan_entry->thread_id);
1310 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1312 orphan_entry->line_number, orphan_entry->function_name);
1313 char release_time_str[32];
1314 format_duration_ns((
double)orphan_entry->acquisition_time_ns, release_time_str,
sizeof(release_time_str));
1315 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1316 " Released at: %s (nanosecond %llu)\n", release_time_str,
1317 (
unsigned long long)orphan_entry->acquisition_time_ns);
1320 if (orphan_entry->backtrace_size > 0) {
1323 platform_format_backtrace_symbols(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1324 "Release call stack", symbols, orphan_entry->backtrace_size, 0, 0, NULL);
1329 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1330 " Release call stack: <capture failed>\n");
1336 if (orphan_count == 0) {
1337 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1338 " No orphaned releases found.\n");
1340 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1341 " Total orphaned releases: %u\n", orphan_count);
1342 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1343 " *** WARNING: %u releases without corresponding locks detected! ***\n", orphan_count);
1344 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1345 " *** This indicates double unlocks or missing lock acquisitions! ***\n");
1349 offset +=
safe_snprintf(log_buffer + offset, SAFE_BUFFER_SIZE(
sizeof(log_buffer), offset),
1350 "\n=== End Lock Debug State ===\n");
1353 log_info(
"%s", log_buffer);
1360#include <ascii-chat/platform/mutex.h>
1361#include <ascii-chat/platform/rwlock.h>
1374 *total_acquired = 0;
1376 *total_released = 0;
1378 *currently_held = 0;
1389int debug_mutex_lock(mutex_t *mutex,
const char *file_name,
int line_number,
const char *function_name) {
1392 (void)function_name;
1399 (void)function_name;
1403int debug_mutex_unlock(mutex_t *mutex,
const char *file_name,
int line_number,
const char *function_name) {
1406 (void)function_name;
1413 (void)function_name;
1420 (void)function_name;
1427 (void)function_name;
1434 (void)function_name;
int buffer_size
Size of circular buffer.
int lock_debug_init(void)
void lock_debug_trigger_print(void)
int debug_rwlock_rdlock(rwlock_t *rwlock, const char *file_name, int line_number, const char *function_name)
int debug_rwlock_wrlock(rwlock_t *rwlock, const char *file_name, int line_number, const char *function_name)
void lock_debug_cleanup_thread(void)
void lock_debug_get_stats(uint64_t *total_acquired, uint64_t *total_released, uint32_t *currently_held)
void lock_debug_destroy(void)
int debug_mutex_unlock(mutex_t *mutex, const char *file_name, int line_number, const char *function_name)
int debug_mutex_lock(mutex_t *mutex, const char *file_name, int line_number, const char *function_name)
int lock_debug_start_thread(void)
void lock_debug_print_state(void)
int debug_rwlock_wrunlock(rwlock_t *rwlock, const char *file_name, int line_number, const char *function_name)
bool lock_debug_is_initialized(void)
int debug_mutex_trylock(mutex_t *mutex, const char *file_name, int line_number, const char *function_name)
int debug_rwlock_rdunlock(rwlock_t *rwlock, const char *file_name, int line_number, const char *function_name)
const char * extract_project_relative_path(const char *file)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
void platform_print_backtrace_symbols(const char *label, char **symbols, int count, int skip_frames, int max_frames, backtrace_frame_filter_t filter)
Print pre-resolved backtrace symbols with colored terminal output and plain log file output.
int mutex_lock_impl(mutex_t *mutex)
int rwlock_wrunlock_impl(rwlock_t *rwlock)
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
int rwlock_wrlock_impl(rwlock_t *rwlock)
int mutex_trylock_impl(mutex_t *mutex)
int rwlock_rdunlock_impl(rwlock_t *rwlock)
int rwlock_init(rwlock_t *rwlock)
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
int rwlock_rdlock_impl(rwlock_t *rwlock)
uint64_t asciichat_thread_current_id(void)
int mutex_unlock_impl(mutex_t *mutex)
rwlock_t rwlock
Read-write lock for thread-safe access (uthash requires external locking)
uint64_t time_get_ns(void)
int format_duration_ns(double nanoseconds, char *buffer, size_t buffer_size)
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
int format_duration_ms(double milliseconds, char *buffer, size_t buffer_size)
int platform_backtrace(void **buffer, int size)
void platform_backtrace_symbols_destroy(char **symbols)
void platform_print_backtrace(int skip_frames)
char ** platform_backtrace_symbols(void *const *buffer, int size)