Search code examples
c++11condition-variablemingw-w64c++-chrono

std::condition_variable::wait_for exits immediately when given std::chrono::duration::max


I have a wrapper around std::queue using C++11 semantics to allow concurrent access. The std::queue is protected with a std::mutex. When an item is pushed to the queue, a std::condition_variable is notified with a call to notify_one.

There are two methods for popping an item from the queue. One method will block indefinitely until an item has been pushed on the queue, using std::condition_variable::wait(). The second will block for an amount of time given by a std::chrono::duration unit using std::condition_variable::wait_for():

template <typename T> template <typename Rep, typename Period>
void ConcurrentQueue<T>::Pop(T &item, std::chrono::duration<Rep, Period> waitTime)
{
    std::cv_status cvStatus = std::cv_status::no_timeout;
    std::unique_lock<std::mutex> lock(m_queueMutex);

    while (m_queue.empty() && (cvStatus == std::cv_status::no_timeout))
    {
        cvStatus = m_pushCondition.wait_for(lock, waitTime);
    }

    if (cvStatus == std::cv_status::no_timeout)
    {
        item = std::move(m_queue.front());
        m_queue.pop();
    }
}

When I call this method like this on an empty queue:

ConcurrentQueue<int> intQueue;

int value = 0;
std::chrono::seconds waitTime(12);

intQueue.Pop(value, waitTime);

Then 12 seconds later, the call to Pop() will exit. But if waitTime is instead set to std::chrono::seconds::max(), then the call to Pop() will exit immediately. The same occurs for milliseconds::max() and hours::max(). But, days::max() works as expected (doesn't exit immediately).

What causes seconds::max() to exit right away?

This is compiled with mingw64:

g++ --version

g++ (rev5, Built by MinGW-W64 project) 4.8.1
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Solution

  • To begin with, the timed wait should likely be a wait_until(lock, std::chrono::steady_clock::now() + waitTime);, not wait_for because the loop will now simply repeat the wait multiple times until finally the condition (m_queue.empty()) becomes true. The repeats can also be caused by spurious wake-ups.

    Fix that part of the code by using the predicated wait methods:

    template <typename Rep, typename Period>
    bool pop(std::chrono::duration<Rep, Period> waitTime, int& popped)
    {
        std::unique_lock<std::mutex> lock(m_queueMutex);
    
        if (m_pushCondition.wait_for(lock, waitTime, [] { return !m_queue.empty(); }))
        {
            popped = m_queue.back();
            m_queue.pop_back();
            return true;
        } else
        {
            return false;
        }
    }
    

    On my implementation at least seconds::max() yields 0x7fffffffffffffff

    §30.5.1 ad 26 states:

    Effects: as if

     return wait_until(lock, chrono::steady_clock::now() + rel_time);
    

    Doing

    auto time = steady_clock::now() + seconds::max();
    std::cout << std::dec << duration_cast<seconds>(time.time_since_epoch()).count() << "\n";
    

    On my system, prints

    265521
    

    Using date --date='@265521' --rfc-822 told me that that is Sun, 04 Jan 1970 02:45:21 +0100

    There's a wrap around bug going on for GCC and Clang, see below


    Tester

    Live On Coliru

    #include <thread>
    #include <condition_variable>
    #include <iostream>
    #include <deque>
    #include <chrono>
    #include <iomanip>
    
    std::mutex m_queueMutex;
    std::condition_variable m_pushCondition;
    
    std::deque<int> m_queue;
    
    template <typename Rep, typename Period>
    bool pop(std::chrono::duration<Rep, Period> waitTime, int& popped)
    {
        std::unique_lock<std::mutex> lock(m_queueMutex);
    
        if (m_pushCondition.wait_for(lock, waitTime, [] { return !m_queue.empty(); }))
        {
            popped = m_queue.back();
            m_queue.pop_back();
            return true;
        } else
        {
            return false;
        }
    }
    
    int main()
    {
        int data;
        using namespace std::chrono;
    
        pop(seconds(2)    , data);
    
        std::cout << std::hex << std::showbase << seconds::max().count() << "\n";
        auto time = steady_clock::now() + seconds::max();
        std::cout << std::dec << duration_cast<seconds>(time.time_since_epoch()).count() << "\n";
        pop(seconds::max(), data);
    }