...
Condition variables must be used inside a while
loop. (see See CON36-C. Wrap functions that can spuriously wake up in a loop for more information.) . To guarantee liveness, programs must test the while
loop condition before invoking the cnd_wait()
function. This early test checks whether another thread has already satisfied the condition predicate and has sent a notification. Invoking the cnd_wait()
function after the notification has been sent results in indefinite blocking.
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> #include <threads.h> enum { NTHREADS = 5 }; mtx_t mutex; cnd_t cond; int run_step(void *t) { static intsize_t current_step = 0; size_t my_step = *(size_t *)t; if (thrd_success != mtx_lock(&mutex)) { /* Handle error */ } printf("Thread %d%zu has the lock\n", my_step); while (current_step != my_step) { printf("Thread %d%zu is sleeping...\n", my_step); if (thrd_success != cnd_wait(&cond, &mutex)) { /* Handle error */ } printf("Thread %d%zu woke up\n", my_step); } /* Do processing ... */ printf("Thread %d%zu is processing...\n", my_step); current_step++; /* Signal awaiting task */ if (thrd_success != cnd_signal(&cond)) { /* Handle error */ } printf("Thread %d%zu is exiting...\n", my_step); if (thrd_success != mtx_unlock(&mutex)) { /* Handle error */ } return 0; } int main(void) { thrd_t threads[NTHREADS]; size_t step[NTHREADS]; if (thrd_success != mtx_init(&mutex, mtx_plain)) { /* Handle error */ } if (thrd_success != cnd_init(&cond)) { /* Handle error */ } /* Create threads */ for (size_t i = 0; i < NTHREADS; ++i) { step[i] = i; if (thrd_success != thrd_create(&threads[i], run_step, &step[i])) { /* Handle error */ } } /* Wait for all threads to complete */ for (size_t i = NTHREADS; i != 0; --i) { if (thrd_success != thrd_join(threads[i-1], NULL)) { /* Handle error */ } } mtx_destroy(&mutex); cnd_destroy(&cond); return 0; } |
In this example, all threads share a condition variable. Each thread has its own distinct condition predicate because each thread requires current_step
to have a different value before proceeding. When the condition variable is signaled, any of the waiting threads can wake up.
The following table illustrates a possible scenario in which the liveness property is violated. If, by chance, the notified thread is not the thread with the next step value, that thread will wait again. No additional notifications can occur, and eventually the pool of available threads will be exhausted.
Deadlock: Out-of-Sequence Step Value
Time | Thread # |
| Action |
---|---|---|---|
0 | 3 | 0 | Thread 3 executes first time: predicate is |
1 | 2 | 0 | Thread 2 executes first time: predicate is |
2 | 4 | 0 | Thread 4 executes first time: predicate is |
3 | 0 | 0 | Thread 0 executes first time: predicate is |
4 | 1 | 1 | Thread 1 executes first time: predicate is |
5 | 3 | 2 | Thread 3 wakes up (scheduler choice): predicate is |
6 | — | — | Thread exhaustion! No more threads to run, and a conditional variable signal is needed to wake up the others |
This noncompliant code example violates the liveness property.
Compliant Solution (cnd_broadcast()
)
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> #include <threads.h> mtx_t mutex; cnd_t cond; int run_step(void *t) { static size_t current_step = 0; size_t my_step = *(size_t *)t; if (thrd_success != mtx_lock(&mutex)) { /* Handle error */ } printf("Thread %d%zu has the lock\n", my_step); while (current_step != my_step) { printf("Thread %d%zu is sleeping...\n", my_step); if (thrd_success != cnd_wait(&cond, &mutex)) { /* Handle error */ } printf("Thread %d%zu woke up\n", my_step); } /* Do processing ... */ printf("Thread %d%zu is processing...\n", my_step); current_step++; /* Signal ALL waiting tasks */ if (thrd_success != cnd_broadcast(&cond)) { /* Handle error */ } printf("Thread %d%zu is exiting...\n", my_step); if (thrd_success != mtx_unlock(&mutex)) { /* Handle error */ } return 0; } |
Awakening all threads solves threads guarantees the liveness property because each thread will execute its condition predicate test, and exactly one will succeed and continue execution.
...
Another compliant solution is to use a unique condition variable for each thread (all associated with the same mutex). In this case, cnd_signal()
wakes up only the thread that is waiting on it. This solution is more efficient than using cnd_broadcast()
because only the desired thread is awakened.
Note that the The condition predicate of the signaled thread must be true; otherwise, a deadlock will occur.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> #include <threads.h> enum { NTHREADS = 5 }; mtx_t mutex; cnd_t cond[NTHREADS]; int run_step(void *t) { static size_t current_step = 0; size_t my_step = *(size_t *)t; if (thrd_success != mtx_lock(&mutex)) { /* Handle error */ } printf("Thread %d%zu has the lock\n", my_step); while (current_step != my_step) { printf("Thread %d%zu is sleeping...\n", my_step); if (thrd_success != cnd_wait(&cond[my_step], &mutex)) { /* Handle error */ } printf("Thread %d%zu woke up\n", my_step); } /* Do processing ... */ printf("Thread %d%zu is processing...\n", my_step); current_step++; /* Signal next step thread */ if ((my_step + 1) < NTHREADS) { if (thrd_success != cnd_signal(&cond[my_step + 1])) { /* Handle error */ } } printf("Thread %d%zu is exiting...\n", my_step); if (thrd_success != mtx_unlock(&mutex)) { /* Handle error */ } return 0; } int main(void) { thrd_t threads[NTHREADS]; size_t step[NTHREADS]; if (thrd_success != mtx_init(&mutex, mtx_plain)) { /* Handle error */ } for (size_t i = 0; i< NTHREADS; ++i) { if (thrd_success != cnd_init(&cond[i])) { /* Handle error */ } } /* Create threads */ for (size_t i = 0; i < NTHREADS; ++i) { step[i] = i; if (thrd_success != thrd_create(&threads[i], run_step, &step[i])) { /* Handle error */ } } /* Wait for all threads to complete */ for (size_t i = NTHREADS; i != 0; --i) { if (thrd_success != thrd_join(threads[i-1], NULL)) { /* Handle error */ } } mtx_destroy(&mutex); for (size_t i = 0; i < NTHREADS; ++i) { cnd_destroy(&cond[i]); } return 0; } |
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <Windows.h> #include <stdio.h> CRITICAL_SECTION lock; CONDITION_VARIABLE cond; DWORD WINAPI run_step(LPVOID t) { static size_t current_step = 0; size_t my_step = (size_t)t; EnterCriticalSection(&lock); printf("Thread %d%zu has the lock\n", my_step); while (current_step != my_step) { printf("Thread %d%zu is sleeping...\n", my_step); if (!SleepConditionVariableCS(&cond, &lock, INFINITE)) { /* Handle error */ } printf("Thread %d%zu woke up\n", my_step); } /* Do processing ... */ printf("Thread %d%zu is processing...\n", my_step); current_step++; LeaveCriticalSection(&lock); /* Signal ALL waiting tasks */ WakeAllConditionVariable(&cond); printf("Thread %d%zu is exiting...\n", my_step); return 0; } enum { NTHREADS = 5 }; int main(void) { HANDLE threads[NTHREADS]; InitializeCriticalSection(&lock); InitializeConditionVariable(&cond); /* Create threads */ for (size_t i = 0; i < NTHREADS; ++i) { threads[i] = CreateThread(NULL, 0, run_step, (LPVOID)i, 0, NULL); } /* Wait for all threads to complete */ WaitForMultipleObjects(NTHREADS, threads, TRUE, INFINITE); DeleteCriticalSection(&lock); return 0; } |
...
Failing to preserve the thread safety and liveness of a program when using condition variables can lead to indefinite blocking and denial of service (DoS).
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
CON38-C | Low | Unlikely | Medium | P2 | L3 |
Automated Detection
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
CERT Oracle Secure Coding Standard for Java | THI04-J. Notify all waiting threads rather than a single thread |
Bibliography
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
CodeSonar |
| CONCURRENCY.BADFUNC.CNDSIGNAL | Use of Condition Variable Signal | ||||||
Cppcheck Premium |
| premium-cert-con38-c | Fully implemented | ||||||
Helix QAC |
| C1778, C1779 | |||||||
Klocwork |
| CERT.CONC.UNSAFE_COND_VAR_C | |||||||
Parasoft C/C++test |
| CERT_C-CON38-a | Use the 'cnd_signal()' function with a unique condition variable | ||||||
Polyspace Bug Finder |
| CERT C: Rule CON38-C | Checks for multiple threads waiting on same condition variable (rule fully covered) |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
---|---|---|
CERT Oracle Secure Coding Standard for Java | THI02-J. Notify all waiting threads rather than a single thread | Prior to 2018-01-12: CERT: Unspecified Relationship |
Bibliography
[IEEE |
Std 1003.1:2013] | XSH, System Interfaces, pthread_cond_broadcast XSH, System Interfaces, pthread_cond_signal |
[Lea 2000] |
...
...