Search code examples
c++multithreadingvxworks

How to avoid race conditions in a condition variable in VxWorks


We're programming on a proprietary embedded platform sitting atop of VxWorks 5.5. In our toolbox, we have a condition variable, that is implemented using a VxWorks binary semaphore.

Now, POSIX provides a wait function that also takes a mutex. This will unlock the mutex (so that some other task might write to the data) and waits for the other task to signal (it is done writing the data). I believe this implements what's called a Monitor, ICBWT.

We need such a wait function, but implementing it is tricky. A simple approach would do this:

bool condition::wait_for(mutex& mutex) const {
    unlocker ul(mutex);    // relinquish mutex
    return wait(event);
}                          // ul's dtor grabs mutex again

However, this sports a race condition because it allows another task to preempt this one after the unlocking and before the waiting. The other task can write to the date after it was unlocked and signal the condition before this task starts to wait for the semaphore. (We have tested this and this indeed happens and blocks the waiting task forever.)

Given that VxWorks 5.5 doesn't seem to provide an API to temporarily relinquish a semaphore while waiting for a signal, is there a way to implement this on top of the provided synchronization routines?

Note: This is a very old VxWorks version that has been compiled without POSIX support (by the vendor of the proprietary hardware, from what I understood).


Solution

  • Race conditions can be avoided if each waiting task waits on a separate binary semaphore. These semaphores must be registered in a container which the signaling task uses to unblock all waiting tasks. The container must be protected by a mutex.

    The wait_for() method obtains a binary semaphore, waits on it and finally deletes it.

    void condition::wait_for(mutex& mutex) {
        SEM_ID sem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY);
        {
            lock l(listeners_mutex);    // assure exclusive access to listeners container
            listeners.push_back(sem);       
        }                               // l's dtor unlocks listeners_mutex again
    
        unlocker ul(mutex);             // relinquish mutex
        semTake(sem, WAIT_FOREVER);
    
        {
            lock l(listeners_mutex);
            // remove sem from listeners
            // ...
            semDelete(sem);
        }
    }                                   // ul's dtor grabs mutex again
    

    The signal() method iterates over all registered semaphores and unlocks them.

    void condition::signal() {
        lock l(listeners_mutex);
        for_each (listeners.begin(), listeners.end(), /* call semGive()... */ )
    }
    

    This approach assures that wait_for() will never miss a signal. A disadvantage is the need of additional system resources. To avoid creating and destroying semaphores for every wait_for() call, a pool could be used.