Search code examples
c++c++11c++-chronocondition-variable

Unexpected behavior with `std::chrono::system_clock` and `std::chrono::steady_clock` in `std::condition_variable::wait_until`


I'm working with std::condition_variable::wait_until in C++ and I'm using std::chrono::system_clock and std::chrono::steady_clock to manage the waiting time. After reading the documentation, I understand that the sleeping time should not be affected by changes in the system time when using std::chrono::steady_clock, while it may change when using std::chrono::system_clock.

However, my code is behaving differently: for both clock types, the waiting time is affected by the system time. Here's a minimal reproducible example:

#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <ctime>
#include <sys/time.h>
#include <condition_variable>
#include <atomic>

std::atomic<bool> flag = false;
std::condition_variable cv;


void wait_using_system_clock()
{
    std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
    std::mutex mtx;
    std::unique_lock lock(mtx);
    cv.wait_until(lock, std::chrono::system_clock::now() + std::chrono::seconds(20), [](){return flag.load();});

    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    std::cout << "Time difference [using system clock] = " << std::chrono::duration_cast<std::chrono::seconds>(end - begin).count() << "s" << std::endl;
}

void wait_using_steady_clock()
{
    std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
    std::mutex mtx;
    std::unique_lock lock(mtx);
    cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(20), [](){return flag.load();});

    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    std::cout << "Time difference [using steady clock] = " << std::chrono::duration_cast<std::chrono::seconds>(end - begin).count() << "s" << std::endl;
}

void set_system_time_forward()
{
    std::this_thread::sleep_for(std::chrono::seconds(2)); // Ensure other threads has executed cv.wait_until
    struct timeval tv;
    gettimeofday(&tv, NULL);
    tv.tv_sec += 15;
    settimeofday(&tv, NULL);
}

int main()
{
    std::thread t1(wait_using_system_clock);
    std::thread t2(wait_using_steady_clock);

    std::thread t3(set_system_time_forward);
    t1.join();
    t2.join();
    t3.join();

    return 0;
}

I execute the wait_using_system_clock and wait_using_steady_clock functions on different threads. Another thread is used to change the system time after 2 seconds. I expect that the function wait_using_steady_clock should not be affected by this change and should wait for 20 seconds, but instead, both functions finish their execution in approximately 5 seconds, printing:

Time difference [using system clock] = 5s
Time difference [using steady clock] = 5s

Can anyone help me understand why this discrepancy exists between the expected and actual behavior? Is there something I'm missing in the documentation or misunderstanding about these classes?


Solution

  • First things first, the std::mutex's in your functions wait_using_system_clock and wait_using_steady_clock will not do anything, because you have to acquire the SAME mutex when you want to lock some data. This can easily be detected by the thread sanitizer. You can turn it on with the option -fsanitize=thread on clang and gcc.

    Now, for the part that you are missing about std::condition_varaible::wait_until. We know from the documentation that:

    wait_until causes the current thread to block until the condition variable is notified, a specific time is reached, or a spurious wakeup occurs, optionally looping until some predicate is satisfied (bool(stop_waiting()) == true).

    But the interesting part is in the 2nd paragraph of the Notes section:

    Even if the clock in use is std::chrono::steady_clock or another monotonic clock, a system clock adjustment may induce a spurious wakeup.

    And that explains why using std::chrono::steady_clock in your case made no difference.