<condition_variable>: condition_variable_any::wait_for returns cv_status::timeout when the elapsed time is shorter than requested · Issue #4723 · microsoft/STL (original) (raw)

Describe the bug

Revealed by libc++ test test/std/thread/thread.condition/thread.condition.condvarany/wait_for.pass.cpp.

[thread.condvarany.wait]/13:

Returns: cv_status​::​timeout if the relative timeout ([thread.req.timing]) specified by rel_time expired, otherwise cv_status​::​no_timeout.

But MSVC STL's condition_variable_any::wait_for sometimes returns cv_status​::​timeout even though the elapsed time (measured by high_resolution_clock) is shorter than the requested timeout.

Command-line test case

D:\test>type test-condvarany.cpp
#include <condition_variable>
#include <atomic>
#include <cassert>
#include <chrono>
#include <mutex>
#include <thread>
#include <print>

template <class Mutex>
struct MyLock : std::unique_lock<Mutex> {
  using std::unique_lock<Mutex>::unique_lock;
};

template <class Function>
std::chrono::microseconds measure(Function f) {
  std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
  f();
  std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
}

template <class Lock>
void test() {
  using Mutex = typename Lock::mutex_type;

  // Test unblocking via a timeout.
  //
  // To test this, we create a thread that waits on a condition variable
  // with a certain timeout, and we never awaken it. To guard against
  // spurious wakeups, we wait again whenever we are awoken for a reason
  // other than a timeout.
  {
    auto timeout = std::chrono::milliseconds(250);
    std::condition_variable_any cv;
    Mutex mutex;

    std::thread t1([&] {
      Lock lock(mutex);
      std::cv_status result;
      do {
        auto elapsed = measure([&] { result = cv.wait_for(lock, timeout); });
        if (result == std::cv_status::timeout)
          if (elapsed < timeout) {
            std::println("elapsed: {}", elapsed);
            std::println("timeout: {}", timeout);
          }

      } while (result != std::cv_status::timeout);
    });

    t1.join();
  }
}

int main(int, char**) {
  test<std::unique_lock<std::mutex>>();
  test<std::unique_lock<std::timed_mutex>>();
  test<MyLock<std::mutex>>();
  test<MyLock<std::timed_mutex>>();
  return 0;
}

D:\test>cl /EHs /std:c++latest test-condvarany.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.41.33901 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

/std:c++latest is provided as a preview of language features from the latest C++
working draft, and we're eager to hear about bugs and suggestions for improvements.
However, note that these features are provided as-is without support, and subject
to changes or removal as the working draft evolves. See
https://go.microsoft.com/fwlink/?linkid=2045807 for details.

test-condvarany.cpp
Microsoft (R) Incremental Linker Version 14.41.33901.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test-condvarany.exe
test-condvarany.obj

D:\test>.\test-condvarany.exe
elapsed: 249758us
timeout: 250ms

(You might need to execute the program several times to see the output.)

Expected behavior

.\test-condvarany.exe should consistently produce no output

STL version