ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
mutex.c File Reference

Per-thread mutex lock stack for deadlock detection. More...

Go to the source code of this file.

Data Structures

struct  thread_lock_stack_t
struct  thread_registry_entry_t
 thread lock stack keeps the values that move through the subsystem: depth, guard, telemetry. Some members are pointers into caller- or subsystem-owned memory, so lifetime follows the API that populated them rather than the struct copy itself. More...
struct  deadlock_state_t
struct  cond_deadlock_snapshot_t
 deadlock state is intentionally a small, concrete record. Fields such as count give callers the identity and current value they need. It is normally borrowed from, or owned by, the surrounding subsystem—not independently managed. More...
struct  cond_deadlock_entry_t
struct  cond_deadlock_batch_t
 When code needs to work with cond deadlock entry, it keeps state here instead of scattering it across globals. Initialization establishes the handles and sizes; teardown must release any allocation, lock, thread, or callback context recorded in those fields. More...

Macros

#define DEBUG_MUTEX_CYCLE_MSG_BUFFER_SIZE   4096
#define DEBUG_MUTEX_THREAD_NAME_BUFFER_SIZE   256
#define DEBUG_MUTEX_MUTEX_NAME_BUFFER_SIZE   256
#define DEBUG_MUTEX_HELD_BY_NAME_BUFFER_SIZE   256
#define DEBUG_MUTEX_STUCK_BUFFER_SIZE   64
#define DEBUG_MUTEX_COND_BUFFER_SIZE   1024
#define DEBUG_MUTEX_NAME_BUFFER_SIZE   256
#define MUTEX_STACK_MAX_DEPTH   64
#define MAX_THREADS   256
#define MAX_CYCLE_MUTEXES   16
#define MAX_CYCLE_LEN   64
 DFS-based cycle detection in the waits-for graph Returns cycle start index if found, -1 otherwise Fills cycle_path with indices of threads in the cycle (if found).
#define COND_DEADLOCK_THRESHOLD_NS   (5ULL * 1000000000ULL)

Functions

void mutex_stack_push_pending (uintptr_t mutex_key, const char *mutex_name)
 Push a mutex onto the current thread's lock stack (PENDING state).
void mutex_stack_push_locked (uintptr_t mutex_key, const char *mutex_name)
void mutex_stack_mark_locked (uintptr_t mutex_key)
 Mark the top of the current thread's lock stack as LOCKED (transitions PENDING -> LOCKED).
void mutex_stack_pop (uintptr_t mutex_key)
 Pop the top mutex from the current thread's lock stack (unlock).
int mutex_stack_get_current (mutex_stack_entry_t *out_entries, int max_entries)
 Get the current thread's lock 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().
bool mutex_stack_try_snapshot (mutex_wait_snapshot_t *waits, size_t capacity, size_t *count, bool *limited)
void mutex_stack_detect_deadlocks (void)
 Detect circular wait deadlocks using DFS-based cycle detection.
bool debug_sync_is_cleanup_in_progress (void)
void debug_sync_check_cond_deadlocks (void)
 The useful part of cond deadlock batch is in entries, count: together they describe what is pending, who owns it, and how much data is valid. Keep the object with its parent operation and use the parent cleanup path.
int mutex_stack_init (void)
 Initialize mutex stack system.
void mutex_stack_cleanup_current_thread (void)
 Cleanup TLS stack for current thread Explicitly frees the thread-local mutex stack. Used to prevent leaks when TLS destructors might not run reliably (e.g., before TLS key deletion).
void mutex_stack_cleanup (void)
 Cleanup mutex stack system.

Detailed Description

Per-thread mutex lock stack for deadlock detection.

Date
February 2026

Tracks which mutexes are held and pending per thread. Detects circular wait patterns (classic deadlock condition).

Definition in file mutex.c.

Macro Definition Documentation

◆ COND_DEADLOCK_THRESHOLD_NS

#define COND_DEADLOCK_THRESHOLD_NS   (5ULL * 1000000000ULL)

Definition at line 755 of file mutex.c.

Referenced by debug_sync_check_cond_deadlocks().

◆ DEBUG_MUTEX_COND_BUFFER_SIZE

#define DEBUG_MUTEX_COND_BUFFER_SIZE   1024

Definition at line 8 of file mutex.c.

◆ DEBUG_MUTEX_CYCLE_MSG_BUFFER_SIZE

#define DEBUG_MUTEX_CYCLE_MSG_BUFFER_SIZE   4096

Definition at line 3 of file mutex.c.

Referenced by mutex_stack_detect_deadlocks().

◆ DEBUG_MUTEX_HELD_BY_NAME_BUFFER_SIZE

#define DEBUG_MUTEX_HELD_BY_NAME_BUFFER_SIZE   256

Definition at line 6 of file mutex.c.

Referenced by mutex_stack_detect_deadlocks().

◆ DEBUG_MUTEX_MUTEX_NAME_BUFFER_SIZE

#define DEBUG_MUTEX_MUTEX_NAME_BUFFER_SIZE   256

Definition at line 5 of file mutex.c.

Referenced by mutex_stack_detect_deadlocks().

◆ DEBUG_MUTEX_NAME_BUFFER_SIZE

#define DEBUG_MUTEX_NAME_BUFFER_SIZE   256

Definition at line 9 of file mutex.c.

◆ DEBUG_MUTEX_STUCK_BUFFER_SIZE

#define DEBUG_MUTEX_STUCK_BUFFER_SIZE   64

Definition at line 7 of file mutex.c.

◆ DEBUG_MUTEX_THREAD_NAME_BUFFER_SIZE

#define DEBUG_MUTEX_THREAD_NAME_BUFFER_SIZE   256

Definition at line 4 of file mutex.c.

Referenced by mutex_stack_detect_deadlocks().

◆ MAX_CYCLE_LEN

#define MAX_CYCLE_LEN   64

DFS-based cycle detection in the waits-for graph Returns cycle start index if found, -1 otherwise Fills cycle_path with indices of threads in the cycle (if found).

Definition at line 455 of file mutex.c.

Referenced by mutex_stack_detect_deadlocks(), and mutex_stack_try_snapshot().

◆ MAX_CYCLE_MUTEXES

#define MAX_CYCLE_MUTEXES   16

Definition at line 99 of file mutex.c.

Referenced by mutex_stack_detect_deadlocks().

◆ MAX_THREADS

#define MAX_THREADS   256

Definition at line 62 of file mutex.c.

Referenced by mutex_stack_detect_deadlocks(), and mutex_stack_try_snapshot().

◆ MUTEX_STACK_MAX_DEPTH

#define MUTEX_STACK_MAX_DEPTH   64

Definition at line 40 of file mutex.c.

Referenced by mutex_stack_get_all_threads(), and mutex_stack_try_snapshot().

Function Documentation

◆ debug_sync_is_cleanup_in_progress()

bool debug_sync_is_cleanup_in_progress ( void )
extern

◆ mutex_stack_cleanup()

void mutex_stack_cleanup ( void )

Cleanup mutex stack system.

Definition at line 980 of file mutex.c.

980 {
981 // Signal shutdown to prevent new allocations from threads still running
982 atomic_store_bool(&g_shutting_down, true);
983
984 // Call only after tracked workers stop. Windows FLS key deletion invokes
985 // destructors; POSIX key deletion does not. Delete first so those callbacks
986 // retire their entries before we free any remaining allocations.
987 if (atomic_load_bool(&g_tls_initialized)) {
988 platform_tls_key_delete(g_tls_mutex_stack);
989 atomic_store_bool(&g_tls_initialized, false);
990 }
991 registry_lock();
992 int count = atomic_load_int(&g_thread_registry_count);
993 for (int i = 0; i < count; ++i) {
994 free(g_thread_registry[i].stack);
995 g_thread_registry[i].stack = NULL;
996 }
997 registry_unlock();
998
999 // Clear registry on cleanup using atomic operations
1000 atomic_store_int(&g_thread_registry_count, 0);
1001}

Referenced by asciichat_shared_destroy().

◆ mutex_stack_cleanup_current_thread()

void mutex_stack_cleanup_current_thread ( void )

Cleanup TLS stack for current thread Explicitly frees the thread-local mutex stack. Used to prevent leaks when TLS destructors might not run reliably (e.g., before TLS key deletion).

Definition at line 947 of file mutex.c.

947 {
948 // Explicitly free the current thread's TLS stack
949 // This is used to prevent leaks when TLS destructors might not run reliably
950 // (e.g., debug threads exiting before mutex_stack_cleanup() deletes the TLS key)
951
952 if (!atomic_load_bool(&g_tls_initialized)) {
953 return; // TLS not initialized, nothing to clean up
954 }
955
956 thread_lock_stack_t *stack = (thread_lock_stack_t *)platform_tls_get(g_tls_mutex_stack);
957 if (!stack) {
958 return; // No stack allocated for this thread
959 }
960
961 // Clear from TLS
962 platform_tls_set(g_tls_mutex_stack, NULL);
963
964 // Update registry if this thread is registered
965 thread_id_t current_thread = thread_self();
966 registry_lock();
967 int count = atomic_load_int(&g_thread_registry_count);
968 for (int i = 0; i < count; i++) {
969 if (thread_equal(g_thread_registry[i].thread_id, current_thread) && g_thread_registry[i].stack == stack) {
970 g_thread_registry[i].stack = NULL; // Mark as freed in registry
971 break;
972 }
973 }
974
975 // Free the stack (raw free to match raw malloc above)
976 free(stack);
977 registry_unlock();
978}

◆ mutex_stack_detect_deadlocks()

void mutex_stack_detect_deadlocks ( void )

Detect circular wait deadlocks using DFS-based cycle detection.

Detect and log circular wait deadlocks.

Detects both same-thread and multi-thread deadlock patterns of any length:

Same-thread deadlock:

  • Thread tries to acquire a mutex it already holds (recursive lock on non-recursive mutex)

Multi-thread circular wait (2-way, 3-way, N-way):

  • Uses DFS to detect cycles in the "waits-for" graph
  • Reports all threads involved in the cycle

Definition at line 630 of file mutex.c.

630 {
631 // Fixed scratch avoids allocating one list per thread on every 100 ms scan.
632 // Skip a busy snapshot rather than interfering with the threads being observed.
633 static thread_lock_stack_t snapshots[MAX_THREADS];
634 static thread_id_t ids[MAX_THREADS];
635 static atomic_flag sampling = ATOMIC_FLAG_INIT;
636 if (atomic_flag_test_and_set(&sampling))
637 return;
638 uint64_t expected = 0;
639 if (!atomic_compare_exchange_strong(&g_registry_guard.impl, &expected, 1)) {
640 atomic_flag_clear(&sampling);
641 return;
642 }
643 int thread_count = atomic_load_int_impl(&g_thread_registry_count);
644 bool complete = true;
645 for (int i = 0; i < thread_count; ++i) {
646 thread_lock_stack_t *src = g_thread_registry[i].stack;
647 ids[i] = g_thread_registry[i].thread_id;
648 snapshots[i].depth = 0;
649 if (!src)
650 continue;
651 expected = 0;
652 if (!atomic_compare_exchange_strong(&src->guard.impl, &expected, 1)) {
653 complete = false;
654 break;
655 }
656 snapshots[i].depth = src->depth;
657 memcpy(snapshots[i].stack, src->stack, (size_t)src->depth * sizeof(mutex_stack_entry_t));
658 atomic_store(&src->guard.impl, 0);
659 }
660 atomic_store(&g_registry_guard.impl, 0);
661 if (!complete) {
662 atomic_flag_clear(&sampling);
663 return;
664 }
665
666 // Check each thread for deadlock conditions
667 for (int i = 0; i < thread_count; i++) {
668 thread_lock_stack_t *stack_a = &snapshots[i];
669 uintptr_t waiting_for = thread_waiting_for_mutex(stack_a);
670
671 if (waiting_for == 0)
672 continue; // Thread not waiting
673
674 // Same-thread deadlock: thread trying to acquire a mutex it already holds
675 if (thread_holds_mutex(stack_a, waiting_for)) {
676 log_error("%s", colored_string(LOG_COLOR_ERROR, "╔═══════════════════════════════════════════════════════════╗"));
677 log_error("%s", colored_string(LOG_COLOR_ERROR, "║ ⚠️ DEADLOCK DETECTED: Same-thread Recursive Lock ⚠️ ║"));
678 log_error("%s", colored_string(LOG_COLOR_ERROR, "╚═══════════════════════════════════════════════════════════╝"));
679 log_error(" Thread Address: 0x%lx", (unsigned long)ids[i]);
680 log_error(" Mutex: 0x%lx", waiting_for);
681 log_error(" Issue: Thread attempts recursive lock on non-recursive mutex");
682 continue;
683 }
684
685 // Multi-thread circular wait: use DFS to detect cycles
686 int cycle_path[MAX_CYCLE_LEN];
687 int cycle_len = 0;
688 int cycle_start = detect_cycle_dfs(snapshots, thread_count, i, cycle_path, &cycle_len);
689
690 if (cycle_start >= 0 && cycle_len > 1) {
691 // Collect mutexes involved in this deadlock
692 uintptr_t cycle_mutexes[MAX_CYCLE_MUTEXES];
693 int mutex_count = 0;
694 for (int k = 0; k < cycle_len && mutex_count < MAX_CYCLE_MUTEXES; k++) {
695 int thread_idx = cycle_path[k];
696 thread_lock_stack_t *stack = &snapshots[thread_idx];
697 uintptr_t waiting_for = thread_waiting_for_mutex(stack);
698 if (waiting_for != 0) {
699 cycle_mutexes[mutex_count++] = waiting_for;
700 }
701 }
702
703 // Check if mutexes are different from last deadlock
704 bool is_new_deadlock = deadlock_mutexes_changed(cycle_mutexes, mutex_count);
705 if (is_new_deadlock) {
706 update_deadlock_state(cycle_mutexes, mutex_count);
707 }
708
709 // Cycle detected! Build complete message in one string
710 char cycle_msg[DEBUG_MUTEX_CYCLE_MSG_BUFFER_SIZE];
711 int msg_len = 0;
712
713 // Leading newline and header box
714 msg_len += snprintf(cycle_msg + msg_len, sizeof(cycle_msg) - msg_len, "\n%s\n",
715 colored_string(LOG_COLOR_ERROR, "╔═════════════════════════════════╗"));
716 msg_len += snprintf(cycle_msg + msg_len, sizeof(cycle_msg) - msg_len, "%s\n",
717 colored_string(LOG_COLOR_ERROR, "║ DEADLOCK: Circular Wait Cycle ║"));
718 msg_len += snprintf(cycle_msg + msg_len, sizeof(cycle_msg) - msg_len, "%s\n",
719 colored_string(LOG_COLOR_ERROR, "╚═════════════════════════════════╝"));
720
721 // Print each thread in the cycle
722 for (int k = 0; k < cycle_len; k++) {
723 int thread_idx = cycle_path[k];
724 int next_thread_idx = cycle_path[(k + 1) % cycle_len];
725
726 thread_lock_stack_t *current_stack = &snapshots[thread_idx];
727 uintptr_t current_waiting = thread_waiting_for_mutex(current_stack);
728
731 NAMED_GET_BY_PTR((uintptr_t)ids[thread_idx], thread_name, sizeof(thread_name));
732 NAMED_GET_BY_PTR((uintptr_t)current_waiting, mutex_name, sizeof(mutex_name));
733 NAMED_GET_BY_PTR((uintptr_t)ids[next_thread_idx], held_by_name, sizeof(held_by_name));
734
735 msg_len += snprintf(cycle_msg + msg_len, sizeof(cycle_msg) - msg_len, " T%d: %s waits for %s (held by %s)%s",
736 k + 1, thread_name, mutex_name, held_by_name, k < cycle_len - 1 ? "\n" : "");
737 }
738
739 // Log repeated deadlock detections (skip first call, throttle subsequent ones)
740 if (!is_new_deadlock) {
741 log_error_every(1000000, "%s", cycle_msg); // 1000000 µs = 1 second
742 }
743 }
744 }
745
746 atomic_flag_clear(&sampling);
747}
unsigned long long uint64_t
Definition common.h:63
#define NAMED_GET_BY_PTR(key, buffer, size)
Get name of a pointer/key or format as address fallback.
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
@ LOG_COLOR_ERROR
Definition log/log.h:152
const char * colored_string(log_color_t color, const char *text)
Build a colored string for terminal output.
Definition string.c:284
#define log_error_every(interval_us, fmt,...)
Rate-limited ERROR logging.
Definition log/log.h:758
#define MAX_CYCLE_MUTEXES
Definition mutex.c:99
#define DEBUG_MUTEX_THREAD_NAME_BUFFER_SIZE
Definition mutex.c:4
#define DEBUG_MUTEX_MUTEX_NAME_BUFFER_SIZE
Definition mutex.c:5
#define MAX_CYCLE_LEN
DFS-based cycle detection in the waits-for graph Returns cycle start index if found,...
Definition mutex.c:455
#define MAX_THREADS
Definition mutex.c:62
#define DEBUG_MUTEX_HELD_BY_NAME_BUFFER_SIZE
Definition mutex.c:6
#define DEBUG_MUTEX_CYCLE_MSG_BUFFER_SIZE
Definition mutex.c:3
Definition mutex.h:40
mutex_stack_entry_t stack[64]
Definition mutex.c:43
atomic_t guard
Definition mutex.c:45

References colored_string(), DEBUG_MUTEX_CYCLE_MSG_BUFFER_SIZE, DEBUG_MUTEX_HELD_BY_NAME_BUFFER_SIZE, DEBUG_MUTEX_MUTEX_NAME_BUFFER_SIZE, DEBUG_MUTEX_THREAD_NAME_BUFFER_SIZE, thread_lock_stack_t::depth, thread_lock_stack_t::guard, LOG_COLOR_ERROR, log_error, log_error_every, MAX_CYCLE_LEN, MAX_CYCLE_MUTEXES, MAX_THREADS, NAMED_GET_BY_PTR, and thread_lock_stack_t::stack.

◆ mutex_stack_free_all_threads()

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().

Definition at line 389 of file mutex.c.

389 {
390
391 if (!stacks || !stack_counts)
392 return;
393
394 for (int i = 0; i < thread_count; i++) {
395 SAFE_FREE(stacks[i]);
396 }
397
398 SAFE_FREE(stacks);
399 SAFE_FREE(stack_counts);
400}
#define SAFE_FREE(ptr)
Definition common.h:377

References SAFE_FREE.

◆ mutex_stack_get_all_threads()

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.

Allocates memory for all thread stacks. Caller must free with mutex_stack_free_all_threads().

Parameters
out_stacksPointer to array of stacks (one per thread)
out_stack_countsArray of stack sizes corresponding to out_stacks
out_thread_countNumber of threads with lock stacks
Returns
0 on success, -1 on error

Definition at line 339 of file mutex.c.

339 {
340
341 if (!out_stacks || !out_stack_counts || !out_thread_count) {
342 return -1;
343 }
344
345 // Read the thread count with acquire semantics (lock-free, no mutex needed)
346 // Use memory_order_seq_cst to ensure we get a consistent snapshot
347 int thread_count = atomic_load_int(&g_thread_registry_count);
348
349 // Allocate arrays for threads in the registry
350 // Note: SAFE_MALLOC takes bytes as first parameter, not count
351 *out_stacks = SAFE_CALLOC(thread_count, sizeof(mutex_stack_entry_t *), mutex_stack_entry_t **);
352 *out_stack_counts = SAFE_CALLOC(thread_count, sizeof(int), int *);
353
354 if (!*out_stacks || !*out_stack_counts) {
355 SAFE_FREE(*out_stacks);
356 SAFE_FREE(*out_stack_counts);
357 return -1;
358 }
359
360 // Copy each thread's stack from the registry
361 // Note: the thread count can change concurrently, so we re-read it in the loop
362 // to avoid accessing out-of-bounds memory if threads exit during iteration
363 for (int i = 0; i < thread_count; i++) {
364 // Re-check thread count in case registry shrank
365 int current_registry_count = atomic_load_int(&g_thread_registry_count);
366 if (i >= current_registry_count) {
367 break;
368 }
369
370 // Allocate before locking because tracked allocation updates the lock stack.
372 (*out_stack_counts)[i] = 0;
373 registry_lock();
374 thread_lock_stack_t *src = g_thread_registry[i].stack;
375 if (src) {
376 stack_lock(src);
377 int depth = src->depth;
378 (*out_stack_counts)[i] = depth;
379 memcpy((*out_stacks)[i], src->stack, depth * sizeof(mutex_stack_entry_t));
380 stack_unlock(src);
381 }
382 registry_unlock();
383 }
384
385 *out_thread_count = thread_count;
386 return 0;
387}
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
#define MUTEX_STACK_MAX_DEPTH
Definition mutex.c:40

References thread_lock_stack_t::depth, MUTEX_STACK_MAX_DEPTH, SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, and thread_lock_stack_t::stack.

◆ mutex_stack_get_current()

int mutex_stack_get_current ( mutex_stack_entry_t * out_entries,
int max_entries )

Get the current thread's lock stack.

Parameters
out_entriesPointer to array where entries will be stored
max_entriesMaximum number of entries to return
Returns
Number of entries in the stack (may be > max_entries if truncated)

Definition at line 321 of file mutex.c.

321 {
322 thread_lock_stack_t *stack = get_thread_local_stack();
323 if (!stack || !out_entries) {
324 return 0;
325 }
326
327 stack_lock(stack);
328 int depth = stack->depth;
329 int count = (depth < max_entries) ? depth : max_entries;
330 memcpy(out_entries, stack->stack, count * sizeof(mutex_stack_entry_t));
331 stack_unlock(stack);
332 return depth; // Return actual depth even if truncated
333}

References thread_lock_stack_t::depth, and thread_lock_stack_t::stack.

◆ mutex_stack_init()

int mutex_stack_init ( void )

Initialize mutex stack system.

Returns
0 on success, -1 on error

Definition at line 939 of file mutex.c.

939 {
940#ifndef NDEBUG
941 atomic_store(&g_next_cond_check_ns, 0);
942#endif
943 // No initialization needed - registry uses lock-free atomic operations
944 return 0;
945}

◆ mutex_stack_mark_locked()

void mutex_stack_mark_locked ( uintptr_t mutex_key)

Mark the top of the current thread's lock stack as LOCKED (transitions PENDING -> LOCKED).

Parameters
mutex_keyPointer to mutex (must match most recent pending push)

Definition at line 271 of file mutex.c.

271 {
272 thread_lock_stack_t *stack = get_thread_local_stack();
273 if (!stack) {
274 return;
275 }
276
277 // Mark the top of the stack as locked
278 stack_lock(stack);
279 if (stack->depth == 0) {
280 stack_unlock(stack);
281 return;
282 }
283 int top = stack->depth - 1;
284 if (stack->stack[top].mutex_key == mutex_key) {
286 stack->stack[top].timestamp_ns = time_get_ns();
287 }
288 stack_unlock(stack);
289
290 // Thread-local only. Registry is populated on-demand by mutex_stack_get_all_threads()
291}
@ MUTEX_STACK_STATE_LOCKED
Definition mutex.h:34
mutex_stack_state_t state
Definition mutex.h:43
uint64_t timestamp_ns
Definition mutex.h:44
uintptr_t mutex_key
Definition mutex.h:41

References thread_lock_stack_t::depth, mutex_stack_entry_t::mutex_key, MUTEX_STACK_STATE_LOCKED, thread_lock_stack_t::stack, mutex_stack_entry_t::state, and mutex_stack_entry_t::timestamp_ns.

◆ mutex_stack_pop()

void mutex_stack_pop ( uintptr_t mutex_key)

Pop the top mutex from the current thread's lock stack (unlock).

Parameters
mutex_keyPointer to mutex (used for validation)

Definition at line 293 of file mutex.c.

293 {
294 thread_lock_stack_t *stack = get_thread_local_stack();
295 if (!stack) {
296 return;
297 }
298
299 // Validate top matches
300 stack_lock(stack);
301 if (stack->depth == 0) {
302 stack_unlock(stack);
303 return;
304 }
305 // Locks may be released out of acquisition order. Remove the most recent
306 // matching acquisition, preserving any recursive acquisitions below it.
307 for (int i = stack->depth - 1; i >= 0; --i) {
308 if (stack->stack[i].mutex_key == mutex_key) {
309 memmove(&stack->stack[i], &stack->stack[i + 1], (size_t)(stack->depth - i - 1) * sizeof(stack->stack[0]));
310 --stack->depth;
311 break;
312 }
313 }
314 stack_unlock(stack);
315
316 // Thread-local only. Registry is populated on-demand by mutex_stack_get_all_threads()
317}

References thread_lock_stack_t::depth, mutex_stack_entry_t::mutex_key, and thread_lock_stack_t::stack.

◆ mutex_stack_push_locked()

void mutex_stack_push_locked ( uintptr_t mutex_key,
const char * mutex_name )

Record an already acquired lock without publishing a transient pending wait.

Definition at line 267 of file mutex.c.

267 {
268 mutex_stack_push(mutex_key, mutex_name, MUTEX_STACK_STATE_LOCKED);
269}

References MUTEX_STACK_STATE_LOCKED.

◆ mutex_stack_push_pending()

void mutex_stack_push_pending ( uintptr_t mutex_key,
const char * mutex_name )

Push a mutex onto the current thread's lock stack (PENDING state).

Parameters
mutex_keyPointer to mutex (used as unique ID)
mutex_nameHuman-readable name of mutex

Definition at line 263 of file mutex.c.

263 {
264 mutex_stack_push(mutex_key, mutex_name, MUTEX_STACK_STATE_PENDING);
265}
@ MUTEX_STACK_STATE_PENDING
Definition mutex.h:33

References MUTEX_STACK_STATE_PENDING.

◆ mutex_stack_try_snapshot()

bool mutex_stack_try_snapshot ( mutex_wait_snapshot_t * waits,
size_t capacity,
size_t * count,
bool * limited )

Definition at line 507 of file mutex.c.

507 {
508 // A single collector owns this scratch space. Never dereference names from
509 // copied stacks: their registry entries may have been removed meanwhile.
510 static thread_lock_stack_t snapshots[MAX_THREADS];
511 static thread_id_t ids[MAX_THREADS];
512 static atomic_flag sampling = ATOMIC_FLAG_INIT;
513 if (atomic_flag_test_and_set(&sampling))
514 return false;
515 uint64_t expected = 0;
516 if (!atomic_compare_exchange_strong(&g_registry_guard.impl, &expected, 1)) {
517 atomic_flag_clear(&sampling);
518 return false;
519 }
520 int threads = atomic_load_int_impl(&g_thread_registry_count);
521 bool complete = true;
522 for (int i = 0; i < threads; ++i) {
523 thread_lock_stack_t *stack = g_thread_registry[i].stack;
524 ids[i] = g_thread_registry[i].thread_id;
525 snapshots[i].depth = 0;
526 if (!stack)
527 continue;
528 expected = 0;
529 if (!atomic_compare_exchange_strong(&stack->guard.impl, &expected, 1)) {
530 complete = false;
531 break;
532 }
533 snapshots[i].depth = stack->depth;
534 memcpy(snapshots[i].stack, stack->stack, sizeof(stack->stack));
535 snapshots[i].telemetry = stack->telemetry;
536 atomic_store(&stack->guard.impl, 0);
537 }
538 atomic_store(&g_registry_guard.impl, 0);
539 if (complete) {
540 *count = 0;
541 *limited = threads >= MAX_CYCLE_LEN;
542 for (int i = 0; i < threads; ++i) {
543 uintptr_t key = thread_waiting_for_mutex(&snapshots[i]);
544 *limited |= snapshots[i].depth == MUTEX_STACK_MAX_DEPTH;
545 if (!key)
546 continue;
547 if (*count == capacity) {
548 *limited = true;
549 continue;
550 }
551 int path[MAX_CYCLE_LEN], length = 0;
552 bool cycle = thread_holds_mutex(&snapshots[i], key);
553 if (detect_cycle_dfs(snapshots, threads, i, path, &length) >= 0)
554 for (int j = 0; j < length; ++j)
555 cycle |= path[j] == i;
556 mutex_wait_snapshot_t *wait = &waits[(*count)++];
557 *wait = snapshots[i].telemetry;
558 wait->mutex_key = key;
559 wait->thread_key = (uintptr_t)ids[i];
560 wait->since_ns = snapshots[i].stack[snapshots[i].depth - 1].timestamp_ns;
561 wait->cycle = cycle;
562 }
563 }
564 atomic_flag_clear(&sampling);
565 return complete;
566}
For mutex wait snapshot, this is the live record that ties mutex_key, thread_key, since_ns,...
Definition mutex.h:111
uint64_t since_ns
Definition mutex.h:114
uintptr_t thread_key
Definition mutex.h:113
uintptr_t mutex_key
Definition mutex.h:112
mutex_wait_snapshot_t telemetry
Definition mutex.c:46

References mutex_wait_snapshot_t::cycle, thread_lock_stack_t::depth, thread_lock_stack_t::guard, MAX_CYCLE_LEN, MAX_THREADS, mutex_wait_snapshot_t::mutex_key, MUTEX_STACK_MAX_DEPTH, mutex_wait_snapshot_t::since_ns, thread_lock_stack_t::stack, thread_lock_stack_t::telemetry, mutex_wait_snapshot_t::thread_key, and mutex_stack_entry_t::timestamp_ns.