ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
sync.c
Go to the documentation of this file.
1
18#include <ascii-chat/platform/mutex.h> // Must come after cond.h since cond.h includes it
22#include <ascii-chat/log/log.h>
24#include <stdio.h>
25#include <string.h>
26#include <pthread.h>
27#include <inttypes.h>
28#include <ascii-chat/atomic.h>
29
30// ============================================================================
31// Helper Functions (Debug builds only)
32// ============================================================================
33
34#ifndef NDEBUG
35
42static void format_elapsed(uint64_t elapsed_ns, char *buffer, size_t size) {
43 time_pretty(elapsed_ns, -1, buffer, size);
44}
45
53static int format_mutex_timing(const mutex_t *mutex, char *buffer, size_t size) {
54 if (!mutex)
55 return 0;
56
57 // If mutex was never locked, return empty
58 if (mutex->last_lock_time_ns == 0) {
59 return 0;
60 }
61
62 int offset = 0;
63 uint64_t now_ns = time_get_ns();
64 char lock_str[64] = "";
65 char unlock_str[64] = "";
66 char held_str[256] = "";
67
68 if (mutex->last_lock_time_ns > 0 && mutex->last_lock_time_ns <= now_ns) {
69 char elapsed_str[64];
70 format_elapsed(now_ns - mutex->last_lock_time_ns, elapsed_str, sizeof(elapsed_str));
71 snprintf(lock_str, sizeof(lock_str), "lock=%s", elapsed_str);
72 }
73
74 if (mutex->last_unlock_time_ns > 0 && mutex->last_unlock_time_ns <= now_ns) {
75 char elapsed_str[64];
76 format_elapsed(now_ns - mutex->last_unlock_time_ns, elapsed_str, sizeof(elapsed_str));
77 snprintf(unlock_str, sizeof(unlock_str), "unlock=%s", elapsed_str);
78 }
79
80 if (mutex->currently_held_by_key != 0) {
81 char thread_name[256];
82 NAMED_GET_BY_PTR(mutex->currently_held_by_key, thread_name, sizeof(thread_name));
83 snprintf(held_str, sizeof(held_str), "[LOCKED_BY=%s]", thread_name);
84 } else {
85 snprintf(held_str, sizeof(held_str), "[FREE]");
86 }
87
88 offset += snprintf(buffer + offset, size - offset, "%s %s %s", lock_str, unlock_str, held_str);
89
90 return offset;
91}
92
100static int format_rwlock_timing(const rwlock_t *rwlock, char *buffer, size_t size) {
101 if (!rwlock)
102 return 0;
103
104 // If rwlock was never locked, return empty
106 return 0;
107 }
108
109 int offset = 0;
110 uint64_t now_ns = time_get_ns();
111 char rdlock_str[64] = "";
112 char wrlock_str[64] = "";
113 char unlock_str[64] = "";
114 char write_held_str[256] = "";
115 char read_held_str[256] = "";
116 char status_str[128] = "";
117
118 if (rwlock->last_rdlock_time_ns > 0 && rwlock->last_rdlock_time_ns <= now_ns) {
119 char elapsed_str[64];
120 format_elapsed(now_ns - rwlock->last_rdlock_time_ns, elapsed_str, sizeof(elapsed_str));
121 snprintf(rdlock_str, sizeof(rdlock_str), "rdlock=%s", elapsed_str);
122 }
123
124 if (rwlock->last_wrlock_time_ns > 0 && rwlock->last_wrlock_time_ns <= now_ns) {
125 char elapsed_str[64];
126 format_elapsed(now_ns - rwlock->last_wrlock_time_ns, elapsed_str, sizeof(elapsed_str));
127 snprintf(wrlock_str, sizeof(wrlock_str), "wrlock=%s", elapsed_str);
128 }
129
130 if (rwlock->last_unlock_time_ns > 0 && rwlock->last_unlock_time_ns <= now_ns) {
131 char elapsed_str[64];
132 format_elapsed(now_ns - rwlock->last_unlock_time_ns, elapsed_str, sizeof(elapsed_str));
133 snprintf(unlock_str, sizeof(unlock_str), "unlock=%s", elapsed_str);
134 }
135
136 if (rwlock->write_held_by_key != 0) {
137 char thread_name[256];
138 NAMED_GET_BY_PTR(rwlock->write_held_by_key, thread_name, sizeof(thread_name));
139 snprintf(write_held_str, sizeof(write_held_str), "[WRITE_LOCKED_BY=%s]", thread_name);
140 }
141
143 if (read_count > 0) {
144 snprintf(read_held_str, sizeof(read_held_str), "[READ_LOCKED=%" PRIu64 "]", read_count);
145 }
146
147 if (rwlock->write_held_by_key == 0 && read_count == 0) {
148 snprintf(status_str, sizeof(status_str), "[FREE]");
149 }
150
151 offset += snprintf(buffer + offset, size - offset, "%s %s %s %s %s %s", rdlock_str, wrlock_str, unlock_str,
152 write_held_str, read_held_str, status_str);
153
154 return offset;
155}
156
164static int format_cond_timing(const cond_t *cond, char *buffer, size_t size) {
165 if (!cond)
166 return 0;
167
168 // If cond was never waited on, return empty
169 if (cond->last_wait_time_ns == 0) {
170 return 0;
171 }
172
173 int offset = 0;
174 uint64_t now_ns = time_get_ns();
175 char wait_str[64] = "";
176 char signal_str[64] = "";
177 char broadcast_str[64] = "";
178 char waiting_str[256] = "";
179 char status_str[128] = "";
180
181 if (cond->last_wait_time_ns > 0 && cond->last_wait_time_ns <= now_ns) {
182 char elapsed_str[64];
183 format_elapsed(now_ns - cond->last_wait_time_ns, elapsed_str, sizeof(elapsed_str));
184 snprintf(wait_str, sizeof(wait_str), "wait=%s", elapsed_str);
185 }
186
187 if (cond->last_signal_time_ns > 0 && cond->last_signal_time_ns <= now_ns) {
188 char elapsed_str[64];
189 format_elapsed(now_ns - cond->last_signal_time_ns, elapsed_str, sizeof(elapsed_str));
190 snprintf(signal_str, sizeof(signal_str), "signal=%s", elapsed_str);
191 }
192
193 if (cond->last_broadcast_time_ns > 0 && cond->last_broadcast_time_ns <= now_ns) {
194 char elapsed_str[64];
195 format_elapsed(now_ns - cond->last_broadcast_time_ns, elapsed_str, sizeof(elapsed_str));
196 snprintf(broadcast_str, sizeof(broadcast_str), "broadcast=%s", elapsed_str);
197 }
198
199 uint64_t waiting_count = atomic_load_u64(&cond->waiting_count);
200 if (waiting_count > 0) {
201 char thread_name[256];
202 NAMED_GET_BY_PTR(cond->last_waiting_key, thread_name, sizeof(thread_name));
203 snprintf(waiting_str, sizeof(waiting_str), "[WAITING=%" PRIu64 " threads, last=%s]", waiting_count, thread_name);
204 } else {
205 snprintf(status_str, sizeof(status_str), "[IDLE]");
206 }
207
208 offset += snprintf(buffer + offset, size - offset, "%s %s %s %s %s", wait_str, signal_str, broadcast_str, waiting_str,
209 status_str);
210
211 return offset;
212}
213
214// ============================================================================
215// Iterator Callbacks for named.c
216// ============================================================================
217
218typedef struct {
219 char *buffer;
221 size_t offset;
223
224static void mutex_iter_callback(uintptr_t key, const char *name, void *user_data) {
225 sync_buffer_t *buf = (sync_buffer_t *)user_data;
226 if (!buf)
227 return;
228
229 const char *type = named_get_type(key);
230 if (!type || strcmp(type, "mutex") != 0) {
231 return;
232 }
233
234 if (!key) {
235 return;
236 }
237
238 const mutex_t *mutex = (const mutex_t *)key;
239 char timing_str[256] = {0};
240 format_mutex_timing(mutex, timing_str, sizeof(timing_str));
241
242 // Only append if mutex has been used
243 if (timing_str[0]) {
244 buf->offset +=
245 snprintf(buf->buffer + buf->offset, buf->buffer_size - buf->offset, " Mutex %s: %s\n", name, timing_str);
246 }
247}
248
249static void rwlock_iter_callback(uintptr_t key, const char *name, void *user_data) {
250 sync_buffer_t *buf = (sync_buffer_t *)user_data;
251 if (!buf)
252 return;
253
254 const char *type = named_get_type(key);
255 if (!type || strcmp(type, "rwlock") != 0) {
256 return;
257 }
258
259 if (!key) {
260 return;
261 }
262
263 const rwlock_t *rwlock = (const rwlock_t *)key;
264 char timing_str[512] = {0};
265 format_rwlock_timing(rwlock, timing_str, sizeof(timing_str));
266
267 // Only append if rwlock has been used
268 if (timing_str[0]) {
269 buf->offset +=
270 snprintf(buf->buffer + buf->offset, buf->buffer_size - buf->offset, " RWLock %s: %s\n", name, timing_str);
271 }
272}
273
274static void cond_iter_callback(uintptr_t key, const char *name, void *user_data) {
275 sync_buffer_t *buf = (sync_buffer_t *)user_data;
276 if (!buf)
277 return;
278
279 const char *type = named_get_type(key);
280 if (!type || strcmp(type, "cond") != 0) {
281 return;
282 }
283
284 const cond_t *cond = (const cond_t *)key;
285 char timing_str[512] = {0};
286 format_cond_timing(cond, timing_str, sizeof(timing_str));
287
288 // Only append if condition variable has been used
289 if (timing_str[0]) {
290 buf->offset +=
291 snprintf(buf->buffer + buf->offset, buf->buffer_size - buf->offset, " Cond %s: %s\n", name, timing_str);
292 }
293}
294
295static void atomic_t_iter_callback(uintptr_t key, const char *name, void *user_data) {
296 sync_buffer_t *buf = (sync_buffer_t *)user_data;
297 if (!buf)
298 return;
299
300 const char *type = named_get_type(key);
301 if (!type || strcmp(type, "atomic") != 0) {
302 return;
303 }
304
305 const atomic_t *atomic = (const atomic_t *)key;
306 char timing_str[512] = {0};
307 int bytes = debug_atomic_format_timing(atomic, timing_str, sizeof(timing_str));
308
309 // Only append if atomic has been used
310 if (bytes > 0) {
311 buf->offset +=
312 snprintf(buf->buffer + buf->offset, buf->buffer_size - buf->offset, " Atomic %s: %s\n", name, timing_str);
313 }
314}
315
316static void atomic_ptr_iter_callback(uintptr_t key, const char *name, void *user_data) {
317 sync_buffer_t *buf = (sync_buffer_t *)user_data;
318 if (!buf)
319 return;
320
321 const char *type = named_get_type(key);
322 if (!type || strcmp(type, "atomic_ptr") != 0) {
323 return;
324 }
325
326 const atomic_ptr_t *atomic = (const atomic_ptr_t *)key;
327 char timing_str[512] = {0};
328 int bytes = debug_atomic_ptr_format_timing(atomic, timing_str, sizeof(timing_str));
329
330 // Only append if atomic has been used
331 if (bytes > 0) {
332 buf->offset +=
333 snprintf(buf->buffer + buf->offset, buf->buffer_size - buf->offset, " AtomicPtr %s: %s\n", name, timing_str);
334 }
335}
336
337// ============================================================================
338// Lock Stack Printing
339// ============================================================================
340
344static void debug_sync_print_lock_stacks(char *buffer, size_t buffer_size, size_t *offset) {
345 mutex_stack_entry_t **all_stacks = NULL;
346 int *stack_counts = NULL;
347 int thread_count = 0;
348
349 if (mutex_stack_get_all_threads(&all_stacks, &stack_counts, &thread_count) != 0) {
350 return;
351 }
352
353 if (thread_count == 0) {
354 mutex_stack_free_all_threads(all_stacks, stack_counts, thread_count);
355 return;
356 }
357
358 *offset += snprintf(buffer + *offset, buffer_size - *offset, "\nThread Lock Stacks:\n");
359
360 for (int i = 0; i < thread_count; i++) {
361 int depth = stack_counts[i];
362 if (depth == 0)
363 continue;
364
365 *offset += snprintf(buffer + *offset, buffer_size - *offset, " Thread %d: %d lock(s)\n", i, depth);
366
367 for (int j = 0; j < depth; j++) {
368 const mutex_stack_entry_t *entry = &all_stacks[i][j];
369 const char *state_str = (entry->state == MUTEX_STACK_STATE_LOCKED) ? "LOCKED" : "PENDING";
370
371 uint64_t elapsed = time_get_ns() - entry->timestamp_ns;
372 char elapsed_str[64];
373 time_pretty(elapsed, -1, elapsed_str, sizeof(elapsed_str));
374
375 *offset += snprintf(buffer + *offset, buffer_size - *offset, " [%d] mutex @ %p (%s) %s", j,
376 (void *)entry->mutex_key, state_str, elapsed_str);
377 }
378 }
379
380 mutex_stack_free_all_threads(all_stacks, stack_counts, thread_count);
381}
382
383// ============================================================================
384// Public API Implementation (Debug builds only)
385// ============================================================================
386
388// Use a single large buffer for all sync state output
389#define SYNC_BUFFER_SIZE 65536 // Increased from 8192 to handle many syncs
390 log_debug("[debug_sync_print_state] ENTRY");
391
392 char *buffer = SAFE_MALLOC(SYNC_BUFFER_SIZE, char *);
393 if (!buffer) {
394 log_debug("[debug_sync_print_state] Failed to allocate buffer");
395 return;
396 }
397
398 sync_buffer_t buf = {.buffer = buffer, .buffer_size = SYNC_BUFFER_SIZE, .offset = 0};
399
400 // Iterate through all registered syncs
401 log_debug("[debug_sync_print_state] Iterating mutexes");
402 named_registry_for_each(mutex_iter_callback, &buf);
403 log_debug("[debug_sync_print_state] Iterating rwlocks");
404 named_registry_for_each(rwlock_iter_callback, &buf);
405 log_debug("[debug_sync_print_state] Iterating conds");
406 named_registry_for_each(cond_iter_callback, &buf);
407
408 named_registry_for_each(atomic_t_iter_callback, &buf);
409 named_registry_for_each(atomic_ptr_iter_callback, &buf);
410
411 // Print lock stacks for deadlock analysis
412 log_debug("[debug_sync_print_state] Getting lock stacks");
413 debug_sync_print_lock_stacks(buf.buffer, buf.buffer_size, &buf.offset);
414
415 // Log everything in one call
416 log_debug("[debug_sync_print_state] Buffer size: %zu bytes", buf.offset);
417 if (buf.offset > 0) {
418 log_info("SYNC_STATE:\n%s", buf.buffer);
419 } else {
420 log_info("SYNC_STATE: (empty)");
421 }
422
423 SAFE_FREE(buffer);
424#undef SYNC_BUFFER_SIZE
425}
426
427// ============================================================================
428// Condition Variable Deadlock Detection
429// ============================================================================
430
431// ============================================================================
432// Scheduled Debug State Printing (runs on separate thread)
433// ============================================================================
434
435typedef enum {
436 DEBUG_REQUEST_STATE, // Print sync state
437 DEBUG_REQUEST_BACKTRACE, // Print backtrace
438 DEBUG_REQUEST_MEMORY_REPORT, // Print memory report
440
441typedef struct {
444 atomic_t should_run; // Atomic flag set by main thread
445 atomic_t should_exit; // Atomic flag for shutdown
446 atomic_t signal_triggered; // Flag set by SIGUSR1 handler
447 uint64_t memory_report_interval_ns; // Interval for periodic memory reports (0 = disabled)
448 uint64_t last_memory_report_time_ns; // Timestamp of last memory report
449 mutex_t mutex; // Protects access to flags during locked operations
450 cond_t cond; // Wakes thread when signal arrives
451 bool initialized; // Tracks if mutex/cond are initialized
452 bool handled_sync_state_time; // Track if --sync-state option was already processed
453 bool handled_backtrace_time; // Track if --backtrace option was already processed
454 bool handled_memory_report; // Track if --memory-report option was already processed
456
457static debug_state_request_t g_debug_state_request = {
459 .delay_ns = 0,
460 .should_run = {.impl = 0},
461 .should_exit = {.impl = 0},
462 .signal_triggered = {.impl = 0},
463 .memory_report_interval_ns = 0,
464 .last_memory_report_time_ns = 0,
465 .initialized = false,
466 .handled_sync_state_time = false,
467 .handled_backtrace_time = false,
468 .handled_memory_report = false,
469};
470static asciichat_thread_t g_debug_thread;
471static uint64_t g_debug_main_thread_id = 0; // Main thread ID for memory reporting
472static atomic_t g_cleanup_in_progress = {0}; // Flag to prevent deadlock checks during shutdown
473
484static void *debug_print_thread_fn(void *arg) {
485 (void)arg;
486
487 while (!atomic_load_bool(&g_debug_state_request.should_exit)) {
488 // Handle delayed printing
489 if (atomic_load_bool(&g_debug_state_request.should_run) && g_debug_state_request.delay_ns > 0) {
490 platform_sleep_ns(g_debug_state_request.delay_ns);
491 g_debug_state_request.delay_ns = 0;
492 }
493
494 // Handle both scheduled and signal-triggered printing
495 mutex_lock(&g_debug_state_request.mutex);
496 bool should_run = atomic_load_bool(&g_debug_state_request.should_run);
497 bool signal_triggered = atomic_load_bool(&g_debug_state_request.signal_triggered);
498 bool should_exit = atomic_load_bool(&g_debug_state_request.should_exit);
499
500 if ((should_run || signal_triggered) && !should_exit) {
501 debug_request_type_t request_type = g_debug_state_request.request_type;
502 mutex_unlock(&g_debug_state_request.mutex);
503
504 // Print based on request type
505 if (request_type == DEBUG_REQUEST_STATE) {
507 } else if (request_type == DEBUG_REQUEST_BACKTRACE) {
508 backtrace_t bt = {0};
510 backtrace_print("Backtrace", &bt, 0, 0, NULL);
511 backtrace_t_free(&bt);
512 }
513
514 mutex_lock(&g_debug_state_request.mutex);
515 atomic_store_bool(&g_debug_state_request.should_run, false);
516 atomic_store_bool(&g_debug_state_request.signal_triggered, false);
517 should_exit = atomic_load_bool(&g_debug_state_request.should_exit);
518 }
519
520 // Wait for work or signal, with 100ms timeout to check should_exit
521 if (!should_exit) {
522 cond_timedwait(&g_debug_state_request.cond, &g_debug_state_request.mutex, 100000000); // 100ms
523 }
524 mutex_unlock(&g_debug_state_request.mutex);
525
526 // Periodic deadlock detection (runs every 100ms during wait timeout)
529
530#ifndef NDEBUG
531 // Periodic memory report (if enabled and DEBUG_MEMORY is configured)
532#ifdef DEBUG_MEMORY
533 if (g_debug_state_request.memory_report_interval_ns > 0) {
534 uint64_t now = time_get_ns();
535 uint64_t last_time = g_debug_state_request.last_memory_report_time_ns;
536
537 // Check if enough time has passed (or first report)
538 if (last_time == 0 || (now - last_time >= g_debug_state_request.memory_report_interval_ns)) {
540 g_debug_state_request.last_memory_report_time_ns = now;
541 }
542 }
543#endif
544
545 options_t *opts = options_get();
546 if (!opts) {
547 continue;
548 }
549
550 // Handle --debug-state (debug builds only)
551 // Schedule sync state printing after specified delay (execute only once, when option is first detected)
552 // Uses non-blocking scheduled printing instead of sleep to avoid blocking the debug thread
553 if (!g_debug_state_request.handled_sync_state_time && IS_OPTION_EXPLICIT(debug_sync_state_time, opts) &&
554 opts->debug_sync_state_time > 0.0) {
555 g_debug_state_request.handled_sync_state_time = true;
556 log_info("Will print sync state after %f seconds", opts->debug_sync_state_time);
559 }
560
561 // Handle --backtrace (debug builds only)
562 // Schedule backtrace printing after specified delay (execute only once, when option is first detected)
563 // Uses non-blocking scheduled printing instead of sleep to avoid blocking the debug thread
564 if (!g_debug_state_request.handled_backtrace_time && IS_OPTION_EXPLICIT(debug_backtrace_time, opts) &&
565 opts->debug_backtrace_time > 0.0) {
566 g_debug_state_request.handled_backtrace_time = true;
567 log_info("Will print backtrace after %f seconds", opts->debug_backtrace_time);
570 }
571
572 // Handle --memory-report (debug builds only)
573 // Enable periodic memory reporting at specified interval (execute only once, when option is first detected)
574 if (!g_debug_state_request.handled_memory_report && IS_OPTION_EXPLICIT(debug_memory_report_interval, opts) &&
575 opts->debug_memory_report_interval > 0.0) {
576 g_debug_state_request.handled_memory_report = true;
577 log_info("Enabling memory reports every %f seconds", opts->debug_memory_report_interval);
580 }
581#else
582 options_t *opts = options_get();
583 if (!opts) {
584 continue;
585 }
586#endif
587 }
588
589 return NULL;
590}
591
596 // Clean up current thread's mutex stack explicitly
598}
599
605 mutex_lock(&g_debug_state_request.mutex);
606 g_debug_state_request.request_type = DEBUG_REQUEST_STATE;
607 g_debug_state_request.delay_ns = delay_ns;
608 atomic_store_bool(&g_debug_state_request.should_run, true);
609 cond_signal(&g_debug_state_request.cond);
610 mutex_unlock(&g_debug_state_request.mutex);
611}
612
618 mutex_lock(&g_debug_state_request.mutex);
619 g_debug_state_request.request_type = DEBUG_REQUEST_BACKTRACE;
620 g_debug_state_request.delay_ns = delay_ns;
621 atomic_store_bool(&g_debug_state_request.should_run, true);
622 cond_signal(&g_debug_state_request.cond);
623 mutex_unlock(&g_debug_state_request.mutex);
624}
625
631 g_debug_state_request.memory_report_interval_ns = interval_ns;
632 g_debug_state_request.last_memory_report_time_ns = 0; // Reset timer
633}
634
635// ============================================================================
636// Debug Sync API - Thread management
637// ============================================================================
638
640 // Save main thread ID for memory reporting (call very early)
641 g_debug_main_thread_id = asciichat_thread_current_id();
642}
643
645 // debug_sync_set_main_thread_id() should have already been called
646 return 0;
647}
648
650 return g_debug_main_thread_id;
651}
652
654 return atomic_load_bool(&g_cleanup_in_progress);
655}
656
658 // Initialize mutex and condition variable for signal wakeup
659 if (!g_debug_state_request.initialized) {
660 mutex_init(&g_debug_state_request.mutex, "debug_sync_state");
661 cond_init(&g_debug_state_request.cond, "debug_sync_signal");
662 g_debug_state_request.initialized = true;
663 }
664
665 atomic_store_bool(&g_debug_state_request.should_exit, false);
666 int err = asciichat_thread_create(&g_debug_thread, "debug_sync", debug_print_thread_fn, NULL);
667 return err;
668}
669
673
675 log_debug("[DEBUG_SYNC_CLEANUP] Starting cleanup");
676
677 // Only join if thread was actually created
678 if (!g_debug_state_request.initialized) {
679 log_debug("[DEBUG_SYNC_CLEANUP] Thread not initialized, returning");
680 return;
681 }
682
683 // Set cleanup flag to prevent deadlock checks from accessing freed memory
684 // Do this after checking initialization so we don't set it unnecessarily
685 atomic_store_bool(&g_cleanup_in_progress, true);
686 log_debug("[DEBUG_SYNC_CLEANUP] Thread was initialized, proceeding with cleanup");
687
688 log_debug("[DEBUG_SYNC_CLEANUP] Setting initialized to false");
689 g_debug_state_request.initialized = false; // Prevent double-join
690
691 // Signal the thread to wake up immediately instead of waiting for 100ms timeout
692 log_debug("[DEBUG_SYNC_CLEANUP] Signaling thread to exit");
693 atomic_store_bool(&g_debug_state_request.should_exit, true);
694 cond_signal(&g_debug_state_request.cond);
695 log_debug("[DEBUG_SYNC_CLEANUP] Signal sent, about to join thread");
696
697 // Use a timeout join to ensure we don't deadlock, but still unregister the thread
698 // The debug thread should exit quickly after should_exit is set above
699 int join_result = asciichat_thread_join_timeout(&g_debug_thread, NULL, 1000000000ULL); // 1 second timeout
700 if (join_result == 0) {
701 log_debug("[DEBUG_SYNC_CLEANUP] Thread joined successfully");
702 } else if (join_result == -2) {
703 log_debug("[DEBUG_SYNC_CLEANUP] Thread join timed out (thread may still be running)");
704 // Don't unregister if timeout - thread is still alive
705 } else {
706 log_debug("[DEBUG_SYNC_CLEANUP] Thread join failed with error %d", join_result);
707 }
708}
709
711 // Set flag to trigger printing on debug thread (from SIGUSR1 handler).
712 // We don't call debug_sync_print_state() directly here to avoid logging
713 // in signal handler context, which could deadlock with logging mutexes.
714 // Uses atomic_store for thread-safe flag setting from signal handler.
715 //
716 // Signal the condition variable to wake up the debug thread immediately
717 // (without waiting for the 100ms timeout).
718 atomic_store_bool(&g_debug_state_request.signal_triggered, true);
719 cond_signal(&g_debug_state_request.cond);
720}
721
722void debug_sync_get_stats(uint64_t *total_acquired, uint64_t *total_released, uint32_t *currently_held) {
723 if (total_acquired)
724 *total_acquired = 0;
725 if (total_released)
726 *total_released = 0;
727 if (currently_held)
728 *currently_held = 0;
729}
730
731#endif // NDEBUG (close debug-only code section)
732
733// ============================================================================
734// Debug Lock Operation Stubs - pass-through to implementations
735// ============================================================================
736
737int debug_sync_mutex_lock(mutex_t *mutex, const char *file_name, int line_number, const char *function_name) {
738 (void)file_name;
739 (void)line_number;
740 (void)function_name;
741 return mutex_lock_impl(mutex);
742}
743
744int debug_sync_mutex_trylock(mutex_t *mutex, const char *file_name, int line_number, const char *function_name) {
745 (void)file_name;
746 (void)line_number;
747 (void)function_name;
749}
750
751int debug_sync_mutex_unlock(mutex_t *mutex, const char *file_name, int line_number, const char *function_name) {
752 (void)file_name;
753 (void)line_number;
754 (void)function_name;
755 return mutex_unlock_impl(mutex);
756}
757
758int debug_sync_rwlock_rdlock(rwlock_t *lock, const char *file_name, int line_number, const char *function_name) {
759 (void)file_name;
760 (void)line_number;
761 (void)function_name;
762 return rwlock_rdlock_impl(lock);
763}
764
765int debug_sync_rwlock_rdunlock(rwlock_t *lock, const char *file_name, int line_number, const char *function_name) {
766 (void)file_name;
767 (void)line_number;
768 (void)function_name;
769 return rwlock_rdunlock_impl(lock);
770}
771
772int debug_sync_rwlock_wrlock(rwlock_t *lock, const char *file_name, int line_number, const char *function_name) {
773 (void)file_name;
774 (void)line_number;
775 (void)function_name;
776 return rwlock_wrlock_impl(lock);
777}
778
779int debug_sync_rwlock_wrunlock(rwlock_t *lock, const char *file_name, int line_number, const char *function_name) {
780 (void)file_name;
781 (void)line_number;
782 (void)function_name;
783 return rwlock_wrunlock_impl(lock);
784}
785
786int debug_sync_cond_wait(cond_t *cond, mutex_t *mutex, const char *file_name, int line_number,
787 const char *function_name) {
788 // Note: pthread_cond_wait() atomically releases and re-acquires the mutex,
789 // but this happens at the kernel level. Debug tracking can't monitor this atomic
790 // operation properly, so we skip cond_on_wait() to avoid false deadlock reports.
791 (void)file_name;
792 (void)line_number;
793 (void)function_name;
794 return cond_wait_impl(cond, mutex);
795}
796
797int debug_sync_cond_timedwait(cond_t *cond, mutex_t *mutex, uint64_t timeout_ns, const char *file_name, int line_number,
798 const char *function_name) {
799 // Same as debug_sync_cond_wait(): skip tracking the atomic unlock/relock.
800 (void)file_name;
801 (void)line_number;
802 (void)function_name;
803 return cond_timedwait_impl(cond, mutex, timeout_ns);
804}
805
806int debug_sync_cond_signal(cond_t *cond, const char *file_name, int line_number, const char *function_name) {
807 (void)file_name;
808 (void)line_number;
809 (void)function_name;
810 return cond_signal(cond);
811}
812
813int debug_sync_cond_broadcast(cond_t *cond, const char *file_name, int line_number, const char *function_name) {
814 (void)file_name;
815 (void)line_number;
816 (void)function_name;
817 return cond_broadcast(cond);
818}
819
821 return true;
822}
823
824// ============================================================================
825// Release Build Stubs (NDEBUG)
826// ============================================================================
827
828#ifdef NDEBUG
829
830void debug_sync_print_state(void) {
831 // No-op in release builds
832}
833
834void debug_sync_cleanup_thread(void) {
835 // No-op in release builds
836}
837
838#endif
mutex_t mutex
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
โš›๏ธ Atomic operations abstraction layer with debug tracking
Cross-platform condition variable interface for ascii-chat.
int debug_atomic_ptr_format_timing(const atomic_ptr_t *atomic, char *buffer, size_t size)
Format timing info for an atomic_ptr_t and write to buffer.
int debug_atomic_format_timing(const atomic_t *atomic, char *buffer, size_t size)
Format timing info for an atomic and write to buffer.
Debug tracking for atomic operations.
void backtrace_t_free(backtrace_t *bt)
Free backtrace symbols.
void backtrace_capture_and_symbolize(backtrace_t *bt)
Capture and symbolize in one call.
void backtrace_print(const char *label, const backtrace_t *bt, int skip_frames, int max_frames, backtrace_frame_filter_t filter)
Print backtrace with dual output: colored stderr + plain log file.
๐Ÿ“ Backtrace capture, symbolization, and formatting
void debug_memory_report(void)
๐Ÿ” Memory debugging helpers for tracking allocations in debug builds
void mutex_stack_cleanup_current_thread(void)
Cleanup TLS stack for current thread Explicitly frees the thread-local mutex stack....
int mutex_stack_get_all_threads(mutex_stack_entry_t ***out_stacks, int **out_stack_counts, int *out_thread_count)
Get all threads' lock stacks for deadlock analysis.
void mutex_stack_free_all_threads(mutex_stack_entry_t **stacks, int *stack_counts, int thread_count)
Free memory allocated by mutex_stack_get_all_threads()
void mutex_stack_detect_deadlocks(void)
Detect circular wait deadlocks using DFS-based cycle detection.
Per-thread mutex lock stack for deadlock detection.
@ MUTEX_STACK_STATE_LOCKED
Definition debug/mutex.h:24
Named object registry for debugging โ€” log identifiable resource names.
#define IS_OPTION_EXPLICIT(name, opts)
Definition explicit.h:57
int buffer_size
Size of circular buffer.
Definition grep.c:90
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned long long uint64_t
Definition common.h:59
void named_registry_for_each(named_iter_callback_t callback, void *user_data)
Iterate through all registered entries.
#define NAMED_GET_BY_PTR(key, buffer, size)
Get name of a pointer/key or format as address fallback.
const char * named_get_type(uintptr_t key)
Look up the registered type for a resource.
void debug_sync_print_state_delayed(uint64_t delay_ns)
Schedule delayed debug state printing on debug thread.
Definition sync.c:604
void debug_sync_set_memory_report_interval(uint64_t interval_ns)
Set periodic memory report interval.
Definition sync.c:630
void debug_sync_set_main_thread_id(void)
Initialize debug synchronization system.
Definition sync.c:639
void debug_sync_check_cond_deadlocks(void)
Check all condition variables for deadlocks.
void debug_sync_print_backtrace_delayed(uint64_t delay_ns)
Schedule delayed backtrace printing on debug thread.
Definition sync.c:617
void debug_sync_print_state(void)
Print all synchronization primitive states at once.
Definition sync.c:387
void debug_sync_trigger_print(void)
Trigger sync state print immediately (synchronous)
Definition sync.c:710
void debug_sync_destroy(void)
Destroy debug synchronization system.
Definition sync.c:670
uint64_t debug_sync_get_main_thread_id(void)
Get the main thread ID for memory reporting.
Definition sync.c:649
int debug_sync_start_thread(void)
Start background debug thread for scheduled operations.
Definition sync.c:657
void debug_sync_final_cleanup(void)
Final cleanup of all debug allocations at shutdown.
Definition sync.c:595
void debug_sync_cleanup_thread(void)
Stop and clean up background debug thread.
Definition sync.c:674
void debug_sync_get_stats(uint64_t *total_acquired, uint64_t *total_released, uint32_t *currently_held)
Get synchronization statistics.
Definition sync.c:722
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define NS_PER_SEC_INT
Definition time.h:157
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as pretty duration with spaces and configurable precision.
Definition util/time.c:424
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
int debug_sync_rwlock_rdunlock(rwlock_t *lock, const char *file_name, int line_number, const char *function_name)
Definition sync.c:765
int mutex_lock_impl(mutex_t *mutex)
Lock a mutex (implementation function)
Definition threading.c:27
int debug_sync_mutex_lock(mutex_t *mutex, const char *file_name, int line_number, const char *function_name)
Definition sync.c:737
int asciichat_thread_join_timeout(asciichat_thread_t *thread, void **retval, uint64_t timeout_ns)
Wait for a thread to complete with timeout.
int rwlock_rdunlock_impl(rwlock_t *rwlock)
Release a read lock (implementation function)
Definition threading.c:78
int debug_sync_rwlock_wrunlock(rwlock_t *lock, const char *file_name, int line_number, const char *function_name)
Definition sync.c:779
int debug_sync_cond_wait(cond_t *cond, mutex_t *mutex, const char *file_name, int line_number, const char *function_name)
Definition sync.c:786
int debug_sync_rwlock_wrlock(rwlock_t *lock, const char *file_name, int line_number, const char *function_name)
Definition sync.c:772
uintptr_t write_held_by_key
Registry key of thread holding write lock (0 if not held)
Definition rwlock.h:68
void platform_sleep_ns(uint64_t ns)
Platform-safe sleep function with nanosecond precision.
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
int cond_broadcast(cond_t *cond)
Broadcast to a condition variable (wake all waiting threads)
int debug_sync_mutex_trylock(mutex_t *mutex, const char *file_name, int line_number, const char *function_name)
Definition sync.c:744
int rwlock_rdlock_impl(rwlock_t *rwlock)
Acquire a read lock (implementation function)
Definition threading.c:70
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
int mutex_trylock_impl(mutex_t *mutex)
Try to lock a mutex without blocking (implementation function)
Definition threading.c:32
uint64_t last_unlock_time_ns
Timestamp of last unlock (nanoseconds)
Definition rwlock.h:67
int rwlock_wrunlock_impl(rwlock_t *rwlock)
Release a write lock (implementation function)
Definition threading.c:82
int rwlock_wrlock_impl(rwlock_t *rwlock)
Acquire a write lock (implementation function)
Definition threading.c:74
int cond_signal(cond_t *cond)
Signal a condition variable (wake one waiting thread)
int cond_init(cond_t *cond, const char *name)
Initialize a condition variable with a name.
bool debug_sync_is_initialized(void)
Definition sync.c:820
int debug_sync_rwlock_rdlock(rwlock_t *lock, const char *file_name, int line_number, const char *function_name)
Definition sync.c:758
int debug_sync_mutex_unlock(mutex_t *mutex, const char *file_name, int line_number, const char *function_name)
Definition sync.c:751
uint64_t last_wrlock_time_ns
Timestamp of last write lock acquisition (nanoseconds)
Definition rwlock.h:66
uint64_t last_rdlock_time_ns
Timestamp of last read lock acquisition (nanoseconds)
Definition rwlock.h:65
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
int cond_wait_impl(cond_t *cond, mutex_t *mutex)
Wait on a condition variable (blocking) - implementation function.
int cond_timedwait_impl(cond_t *cond, mutex_t *mutex, uint64_t timeout_ns)
Wait on a condition variable with timeout - implementation function.
#define cond_timedwait(cond, mutex, timeout_ns)
Wait on a condition variable with timeout (with debug tracking in debug builds)
Definition cond.h:300
int debug_sync_cond_timedwait(cond_t *cond, mutex_t *mutex, uint64_t timeout_ns, const char *file_name, int line_number, const char *function_name)
Definition sync.c:797
uint64_t asciichat_thread_current_id(void)
Get the current thread's unique numeric ID.
Definition threading.c:92
int mutex_unlock_impl(mutex_t *mutex)
Unlock a mutex (implementation function)
Definition threading.c:37
atomic_t read_lock_count
Number of threads holding read locks (thread-safe atomic)
Definition rwlock.h:69
โš™๏ธ Unified options parsing system for ascii-chat with builder pattern and lock-free access
#define asciichat_thread_create(thread_ptr, attr, start_routine, arg)
void * asciichat_thread_t
๐Ÿ“ Logging API with multiple log levels and terminal output control
bool should_exit(void)
Definition misc.c:131
๐Ÿ“‚ Path Manipulation Utilities
Cross-platform mutex interface for ascii-chat.
Cross-platform read-write lock interface for ascii-chat.
Atomic pointer wrapper.
Definition atomic.h:106
Atomic value wrapper for integral/boolean types.
Definition atomic.h:76
Condition variable type (POSIX: pthread_cond_t with debug tracking)
Definition cond.h:63
uint64_t last_wait_time_ns
Timestamp of last wait (nanoseconds)
Definition cond.h:70
uint64_t last_broadcast_time_ns
Timestamp of last broadcast (nanoseconds)
Definition cond.h:69
uint64_t last_signal_time_ns
Timestamp of last signal (nanoseconds)
Definition cond.h:68
uintptr_t last_waiting_key
Registry key of most recent waiter.
Definition cond.h:71
atomic_t waiting_count
Number of threads currently waiting (functional - needed for all builds)
Definition cond.h:66
uint64_t last_memory_report_time_ns
Definition sync.c:448
atomic_t signal_triggered
Definition sync.c:446
atomic_t should_run
Definition sync.c:444
bool handled_backtrace_time
Definition sync.c:453
atomic_t should_exit
Definition sync.c:445
uint64_t delay_ns
Definition sync.c:443
bool handled_sync_state_time
Definition sync.c:452
debug_request_type_t request_type
Definition sync.c:442
uint64_t memory_report_interval_ns
Definition sync.c:447
bool handled_memory_report
Definition sync.c:454
Definition debug/mutex.h:30
mutex_stack_state_t state
Definition debug/mutex.h:33
uint64_t timestamp_ns
Definition debug/mutex.h:34
uintptr_t mutex_key
Definition debug/mutex.h:31
Mutex type (POSIX: pthread_mutex_t with debug tracking)
uintptr_t currently_held_by_key
Registry key of thread holding the lock (0 if free)
uint64_t last_lock_time_ns
Timestamp of last lock acquisition (nanoseconds)
uint64_t last_unlock_time_ns
Timestamp of last unlock (nanoseconds)
Consolidated options structure.
double debug_memory_report_interval
Interval in seconds for periodic memory reports (debug builds only)
double debug_sync_state_time
Time parameter for โ€“sync-state option (debug builds only)
double debug_backtrace_time
Time parameter for โ€“backtrace option (debug builds only)
Read-write lock type (POSIX: pthread_rwlock_t with debug tracking)
Definition rwlock.h:61
char * buffer
Definition sync.c:219
size_t buffer_size
Definition sync.c:220
size_t offset
Definition sync.c:221
#define SYNC_BUFFER_SIZE
bool debug_sync_is_cleanup_in_progress(void)
Definition sync.c:653
debug_request_type_t
Definition sync.c:435
@ DEBUG_REQUEST_STATE
Definition sync.c:436
@ DEBUG_REQUEST_MEMORY_REPORT
Definition sync.c:438
@ DEBUG_REQUEST_BACKTRACE
Definition sync.c:437
int debug_sync_init(void)
Definition sync.c:644
int debug_sync_cond_signal(cond_t *cond, const char *file_name, int line_number, const char *function_name)
Definition sync.c:806
int debug_sync_cond_broadcast(cond_t *cond, const char *file_name, int line_number, const char *function_name)
Definition sync.c:813
๐Ÿ”’ Synchronization primitive debugging (mutexes, rwlocks, condition variables)
โฑ๏ธ High-precision timing utilities using sokol_time.h and uthash
rwlock_t rwlock
Read-write lock for thread-safe access (uthash requires external locking)
Definition util/time.c:34