🔒 Lock debugging infrastructure with backtrace capture and interactive monitoring
More...
🔒 Lock debugging infrastructure with backtrace capture and interactive monitoring
This module provides comprehensive lock tracking to help identify deadlocks and lock contention issues. It tracks all mutex and rwlock acquisitions with call stack backtraces and provides a debug thread to print held locks.
NOTE: Lock debugging is ONLY enabled when DEBUG_LOCKS is defined. Without DEBUG_LOCKS, all lock_debug functions become no-ops with zero overhead.
Comprehensive lock debugging system for detecting deadlocks, lock contention, and lock ordering violations.
Overview
ascii-chat provides a complete lock debugging infrastructure that tracks all mutex and rwlock acquisitions with full backtrace capture. The system is only enabled in debug builds (when NDEBUG is not defined) and has zero overhead in release builds.
Implementation: lib/debug/lock.c, lib/debug/lock.h
Key Features:
- ✅ Lock acquisition tracking with backtraces (IMPLEMENTED)
- ✅ Lock hold time measurement and warnings (IMPLEMENTED)
- ✅ Interactive debug thread with keyboard monitoring (IMPLEMENTED)
- ✅ Lock usage statistics by code location (IMPLEMENTED)
- ✅ Orphaned release detection (double-unlock detection) (IMPLEMENTED)
- ✅ Thread-safe using uthash with rwlock protection (IMPLEMENTED)
- ❌ Automatic deadlock cycle detection (NOT YET IMPLEMENTED)
- ❌ Lock ordering validation (NOT YET IMPLEMENTED)
Architecture
Threading Model:
ascii-chat uses a per-client threading model where each client gets dedicated threads:
- 2 threads per client: 1 video render (60 FPS) + 1 audio render (172 FPS)
- Linear scaling: No shared bottlenecks, scales to 9+ clients
- Lock debugging thread: Additional monitoring thread when debugging enabled
Critical Lock Ordering Rules:
Always acquire locks in this exact order to prevent deadlocks:
- Global RWLock (
g_client_manager_rwlock in src/server/client_manager.c)
- Per-Client Mutex (
client->client_state_mutex in src/server/client.h)
- Specialized Mutexes (
g_stats_mutex in src/server/stats.c, etc.)
Usage
Enable Lock Debugging
Build with Debug Mode (lock debugging is automatic):
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
Initialize in Code:
lock_debug_init();
lock_debug_start_thread();
lock_debug_cleanup();
lock_debug_cleanup_thread();
return 0;
}
int main(int argc, char **argv)
🔒 Lock debugging and deadlock detection system for ascii-chat
Instrument Your Locks
Replace lock calls with debug versions:
#ifndef NDEBUG
#else
#endif
Or use convenience macros:
DEBUG_MUTEX_LOCK(&my_mutex);
DEBUG_MUTEX_UNLOCK(&my_mutex);
DEBUG_RWLOCK_RDLOCK(&my_rwlock);
DEBUG_RWLOCK_WRLOCK(&my_rwlock);
Implemented Features
Lock Acquisition Tracking
Every tracked lock records:
- Lock address and type (mutex, rwlock read/write)
- Thread ID that acquired the lock
- Acquisition timestamp (CLOCK_MONOTONIC)
- Source file, line number, function name
- Full backtrace (up to 32 frames with symbol resolution)
Data Structure (lib/debug/lock.h:95):
struct lock_record {
void *lock_address;
lock_type_t lock_type;
struct timespec acquisition_time;
const char *file_name;
int line_number;
const char *function_name;
void *backtrace_buffer[32];
int backtrace_size;
char **backtrace_symbols;
};
unsigned long long uint64_t
Lock Hold Time Warnings
Automatic warnings for long-held locks:
The debug thread automatically checks all held locks every 100ms and warns if any lock is held longer than 500ms (configurable via LOCK_HOLD_TIME_WARNING_MS).
Example warning (lib/debug/lock.c:404):
[WARN] Lock held for 1.234s (threshold: 500ms) - MUTEX at 0x7f8a4c001000
Acquired: src/server/render.c:123 in render_video_thread()
Thread ID: 140234567890123
Backtrace from lock acquisition:
#1: render_video_thread at src/server/render.c:123
#2: asciichat_thread_wrapper at lib/platform/posix/thread.c:45
Interactive Debug Thread
Press '?' to print current lock state:
The debug thread (lib/debug/lock.c:484) monitors keyboard input and prints comprehensive lock information when you press '?':
=== LOCK DEBUG STATE ===
Historical Statistics:
Total locks acquired: 1234567
Total locks released: 1234560
Currently held: 7
=== Currently Active Locks ===
Lock #1: MUTEX at 0x7f8a4c001000
Thread ID: 140234567890123
Acquired: src/server/client_manager.c:234 in client_add()
Held for: 0.045 seconds
Call stack (10 frames):
#0: debug_mutex_lock at lib/debug/lock.c:1043
#1: client_add at src/server/client_manager.c:234
...
=== Lock Usage Statistics by Code Location ===
Usage #1: RWLOCK_READ at src/server/client_manager.c:456 in client_find()
Total acquisitions: 50000
Total hold time: 5.123 ms
Average hold time: 0.102 ms
Max hold time: 2.345 ms
Min hold time: 0.001 ms
=== Orphaned Releases ===
No orphaned releases found.
=== End Lock Debug State ===
Orphaned Release Detection
Detects double-unlocks and mismatched lock/unlock:
If you call unlock on a lock that was never tracked (either because it wasn't acquired with debug functions, or it was already released), the system records an "orphaned release" with full backtrace.
Example (lib/debug/lock.c:969):
[ERROR] MUTEX UNTRACKED RELEASED: 0x7f8a4c001000 at client.c:456
[ERROR] *** WARNING: MUTEX lock was acquired and tracked but record was lost! ***
Usage Statistics
Per-location statistics (lib/debug/lock.h:125):
Tracks aggregate stats for each unique file:line:function that acquires locks:
- Total acquisitions
- Total hold time (cumulative)
- Average/min/max hold time
- First and last acquisition timestamps
Implementation Details
Data Structures
Hash Tables (using uthash):
g_lock_debug_manager.lock_records - Currently held locks
g_lock_debug_manager.usage_stats - Statistics by code location
g_lock_debug_manager.orphaned_releases - Double-unlock detections
Thread Safety:
- Each hash table protected by its own rwlock
- Atomic counters for statistics (total_locks_acquired, etc.)
- Careful locking order to prevent deadlocks in the debugger itself
Key Generation (lib/debug/lock.h:204):
uint32_t lock_record_key(
void *lock_address, lock_type_t lock_type) {
struct {
uintptr_t addr;
lock_type_t lock_type;
} key_data = {
.addr = (uintptr_t)lock_address,
.lock_type = lock_type,
};
return fnv1a_hash_bytes(&key_data, sizeof(key_data));
}
uint64_t asciichat_thread_current_id(void)
Recursion Prevention
Smart filtering (lib/debug/lock.c:775):
The system filters out its own internal lock operations to prevent infinite recursion:
- All
log_*() functions
- All
platform_*() functions
- All
lock_debug*() functions
- Symbol resolution code
- During system initialization/shutdown
API Reference
Initialization
int lock_debug_init(void);
int lock_debug_start_thread(void);
void lock_debug_cleanup(void);
void lock_debug_cleanup_thread(void);
Lock Tracking
rwlock_t rwlock
Read-write lock for thread-safe access (uthash requires external locking)
Statistics
void lock_debug_get_stats(
uint64_t *total_acquired,
void lock_debug_print_state(void);
void lock_debug_trigger_print(void);
Complete Example
void process_client(void) {
DEBUG_MUTEX_LOCK(&g_client_mutex);
DEBUG_MUTEX_UNLOCK(&g_client_mutex);
}
lock_debug_init();
lock_debug_start_thread();
process_client();
lock_debug_cleanup();
lock_debug_cleanup_thread();
return 0;
}
🔌 Cross-platform abstraction layer umbrella header for ascii-chat
Best Practices
DO:
- Always acquire locks in the documented order (Global → Client → Specialized)
- Use DEBUG_MUTEX_LOCK/DEBUG_RWLOCK_* macros for automatic file/line capture
- Enable lock debugging during development to catch issues early
- Press '?' during execution to see current lock state
- Review lock usage statistics to find contention hotspots
- Keep lock hold times under 100ms (video is 60 FPS = 16.67ms/frame)
DON'T:
- Don't mix debug and non-debug lock functions (creates orphaned releases)
- Don't hold locks during slow operations (I/O, sleep, etc.)
- Don't ignore "lock held too long" warnings
- Don't disable lock debugging when tracking down deadlocks
- Don't forget to call lock_debug_cleanup() before exit
Future Enhancements
Not Yet Implemented:
- Automatic deadlock cycle detection via lock dependency graph
- Lock ordering validation (automatically detect order violations)
- Graphical visualization of lock dependency graphs
- Export lock statistics to JSON/CSV for analysis
- Per-thread lock stack tracking
References
Source Files:
Key Functions:
- See also
- lib/debug/lock.h
-
lib/debug/lock.c
-
platform/abstraction.h
-
platform/posix/mutex.c
-
platform/posix/rwlock.c