| name | thread-safety-analyzer |
| description | Analyze C/C++ code for thread safety issues including race conditions, deadlocks, and improper synchronization. Use when reviewing concurrent code or debugging threading issues. |
Thread Safety Analysis for Embedded C
Purpose
Systematically analyze C/C++ code for thread safety issues that can cause race conditions, deadlocks, or performance degradation in embedded systems.
Usage
Invoke this skill when:
- Reviewing multi-threaded code
- Debugging race conditions or deadlocks
- Optimizing synchronization overhead
- Validating thread creation and cleanup
- Investigating lock contention issues
Analysis Process
Step 1: Identify Shared Data
Search for global and static variables:
- Global variables (especially non-const)
- Static variables in functions
- Shared heap allocations
- Reference-counted objects
For each shared variable, verify:
- How is it protected (mutex, atomic, etc.)?
- Is the protection consistent across all accesses?
- Are reads and writes both protected?
- Is initialization thread-safe?
Step 2: Review Thread Creation
Check all pthread_create calls:
- Are thread attributes used?
- Is stack size specified?
- Are threads detached or joinable?
- Is cleanup properly handled?
pthread_t thread;
pthread_create(&thread, NULL, func, arg);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 64 * 1024);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&thread, &attr, func, arg);
pthread_attr_destroy(&attr);
Step 3: Analyze Lock Usage
For each mutex/rwlock:
- Is it initialized before use?
- Is it destroyed when done?
- Are lock/unlock pairs balanced?
- What is the lock ordering?
- Are locks held during expensive operations?
Common patterns to check:
pthread_mutex_lock(&lock);
if (error) return -1;
pthread_mutex_unlock(&lock);
pthread_mutex_lock(&a);
pthread_mutex_lock(&b);
pthread_mutex_lock(&b);
pthread_mutex_lock(&a);
pthread_rwlock_wrlock(&lock);
counter++;
pthread_rwlock_unlock(&lock);
Step 4: Check for Race Conditions
Look for unprotected accesses to shared data:
if (shared_flag == 0) {
shared_flag = 1;
}
pthread_mutex_lock(&lock);
if (shared_flag == 0) {
shared_flag = 1;
}
pthread_mutex_unlock(&lock);
int expected = 0;
atomic_compare_exchange_strong(&shared_flag, &expected, 1);
Step 5: Verify Atomic Usage
For atomic variables:
- Are they declared with proper type (atomic_int, atomic_bool)?
- Is memory ordering appropriate?
- Are non-atomic operations mixed with atomic ones?
atomic_int counter;
atomic_fetch_add(&counter, 1);
int value = atomic_load(&counter);
counter++;
Step 6: Deadlock Detection
Check for common deadlock patterns:
- Circular wait: Lock A → Lock B, Lock B → Lock A
- Lock held while waiting: Mutex held during sleep/wait
- Missing timeout: Indefinite blocking without timeout
- Signal under lock: Condition signal while holding mutex
lock(mutex_a);
lock(mutex_b);
lock(mutex_b);
lock(mutex_a);
lock(mutex);
expensive_network_call();
unlock(mutex);
pthread_mutex_lock(&lock);
Step 7: Check Condition Variables
For condition variables:
- Is wait always in a loop?
- Is predicate checked before and after wait?
- Is signal/broadcast done correctly?
- Is spurious wakeup handled?
pthread_mutex_lock(&mutex);
while (!condition) {
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
condition = true;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
if (!condition) {
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
Common Issues and Fixes
Issue: Default Thread Stack Size
pthread_t thread;
pthread_create(&thread, NULL, worker, arg);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 64 * 1024);
pthread_create(&thread, &attr, worker, arg);
pthread_attr_destroy(&attr);
Issue: Heavy Synchronization
pthread_rwlock_t lock;
int counter;
void increment() {
pthread_rwlock_wrlock(&lock);
counter++;
pthread_rwlock_unlock(&lock);
}
atomic_int counter;
void increment() {
atomic_fetch_add(&counter, 1);
}
Issue: Lock Ordering Violation
void process_a_then_b() {
lock(&resource_a.lock);
lock(&resource_b.lock);
}
void process_b_then_a() {
lock(&resource_b.lock);
lock(&resource_a.lock);
}
void process_a_then_b() {
lock(&resource_a.lock);
lock(&resource_b.lock);
}
void process_b_then_a() {
lock(&resource_a.lock);
lock(&resource_b.lock);
}
Issue: Race in Lazy Initialization
static config_t* config = NULL;
config_t* get_config() {
if (!config) {
config = malloc(sizeof(config_t));
init_config(config);
}
return config;
}
static pthread_once_t init_once = PTHREAD_ONCE_INIT;
static config_t* config = NULL;
static void init_config_once() {
config = malloc(sizeof(config_t));
init_config(config);
}
config_t* get_config() {
pthread_once(&init_once, init_config_once);
return config;
}
Issue: Missing Lock on Error Path
int process_data(data_t* shared) {
pthread_mutex_lock(&shared->lock);
if (validate(shared) != 0) {
return -1;
}
update(shared);
pthread_mutex_unlock(&shared->lock);
return 0;
}
int process_data(data_t* shared) {
int ret = 0;
pthread_mutex_lock(&shared->lock);
if (validate(shared) != 0) {
ret = -1;
goto cleanup;
}
update(shared);
cleanup:
pthread_mutex_unlock(&shared->lock);
return ret;
}
Issue: Long Critical Section
pthread_mutex_lock(&lock);
for (int i = 0; i < 1000000; i++) {
compute();
}
shared_result = final_value;
pthread_mutex_unlock(&lock);
int result = 0;
for (int i = 0; i < 1000000; i++) {
result += compute();
}
pthread_mutex_lock(&lock);
shared_result = result;
pthread_mutex_unlock(&lock);
Testing for Thread Safety
Compile with Thread Sanitizer
gcc -g -fsanitize=thread -O1 source.c -o program -lpthread
./program
Run Helgrind
valgrind --tool=helgrind \
--track-lockorders=yes \
./program
Stress Testing
#define NUM_THREADS 100
#define ITERATIONS 10000
void stress_test() {
pthread_t threads[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, worker, NULL);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
assert(shared_counter == NUM_THREADS * ITERATIONS);
}
Output Format
Provide findings as:
## Thread Safety Analysis
### Critical Issues (must fix)
1. [file.c:123] Race condition - unprotected access to shared_flag
2. [file.c:456] Deadlock potential - lock order violation (A→B vs B→A)
3. [file.c:789] Lock leak - mutex not released on error path
### Warnings (should fix)
1. [file.c:234] Default thread stack - wastes 8MB per thread
2. [file.c:567] Heavy lock - use atomic_int instead of mutex
3. [file.c:890] Long critical section - holds lock during I/O
### Recommendations
1. Establish lock ordering convention (document in header)
2. Use pthread_once for singleton initialization
3. Replace reader-writer locks with atomics for counters
4. Add thread sanitizer to CI pipeline
### Suggested Fixes
[Provide specific code changes for each issue]
Verification
After fixes:
- Thread sanitizer shows no errors
- Helgrind reports clean
- Stress tests pass consistently
- Lock contention metrics acceptable
- No deadlocks under load testing
- Code review confirms thread safety