I was reading through my STL implementation (standard-issue g++ 4.6.2
) and came across this bit of race condition inside of condition_variable
:
template<typename _Rep, typename _Period>
cv_status
wait_for(unique_lock<mutex>& __lock,
const chrono::duration<_Rep, _Period>& __rtime)
{
return wait_until(__lock, __clock_t::now() + __rtime);
}
Because __clock_t
is an std::chrono::system_clock
, we are tied to the whims of things like NTP (if the clock is moved back by a day after __clock_t::now() + __rtime
, then we'll wait for a day).
The C++ standard (30.5.1) appears to get it right:
26
Effects: as if
return wait_until(lock, chrono::steady_clock::now() + rel_time);
Boost's condition_variable
implementation has the same problem:
template<typename duration_type>
bool timed_wait(unique_lock<mutex>& m,duration_type const& wait_duration)
{
return timed_wait(m,get_system_time()+wait_duration);
}
In fact, the underlying pthreads implementation seems to be the problem:
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
because abstime
is specified as "system time," not a monotonic clock.
So my question is: How would one implement something like std::condition_variable::wait_for
correctly? Is there an existing implementation that gets this right? Or am I missing something?
The trick is to use a pthread_condattr_setclock
to tell the pthread_condattr_t
to use CLOCK_MONOTONIC
. The C code for doing this is pretty simple:
#include <time.h>
#include <pthread.h>
#include <errno.h>
#include <stdio.h>
int main()
{
// Set the clock to be CLOCK_MONOTONIC
pthread_condattr_t attr;
pthread_condattr_init(&attr);
if (int err = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC))
{
printf("Error setting clock: %d\n", err);
}
// Now we can initialize the pthreads objects with that condattr
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond;
pthread_cond_init(&cond, &attr);
// when getting the time, we must poll from CLOCK_MONOTONIC
struct timespec timeout;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
timeout.tv_sec = now.tv_sec + 5;
timeout.tv_nsec = now.tv_nsec;
// business as usual...
pthread_mutex_lock(&mutex);
int rc = pthread_cond_timedwait(&cond, &mutex, &timeout);
if (rc == ETIMEDOUT)
printf("Success!\n");
else
printf("Got return that wasn't timeout: %d\n", rc);
pthread_mutex_unlock(&mutex);
return 0;
}
I'm going to leave this open for a while because somebody might have an easier answer. The thing I'm not happy about here is that it means a wait_until
is rather difficult to implement with a real-time clock (my best solution to that is to convert the provided Clock
in the time_point
into the steady_clock
's time and go from there...it is still subject to time-change race conditions, but if you're specifying a timeout in real time, you're already making a terrible mistake).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With