https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113850
Bug ID: 113850 Summary: condition variables timed wait does a lot of spurious wakeups on Win32 threading implementation Product: gcc Version: 13.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: libgcc Assignee: unassigned at gcc dot gnu.org Reporter: matteo at mitalia dot net Target Milestone: --- Created attachment 57371 --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=57371&action=edit Test program to reproduce the bug Sample program: ``` #include <condition_variable> #include <chrono> #include <thread> #include <stdio.h> int main() { std::condition_variable cv; std::mutex mx; bool pass = false; auto thread_fn = [&](bool timed) { int wakeups = 0; using sc = std::chrono::system_clock; auto before = sc::now(); std::unique_lock<std::mutex> ml(mx); if (timed) { cv.wait_for(ml, std::chrono::seconds(2), [&]{ ++wakeups; return pass; }); } else { cv.wait(ml, [&]{ ++wakeups; return pass; }); } printf("pass: %d; wakeups: %d; elapsed: %d ms\n", pass, wakeups, int((sc::now() - before) / std::chrono::milliseconds(1))); pass = false; }; { // timed wait, let expire std::thread t(thread_fn, true); t.join(); } { // timed wait, wake up explicitly after 1 second std::thread t(thread_fn, true); std::this_thread::sleep_for(std::chrono::seconds(1)); { std::unique_lock<std::mutex> ml(mx); pass = true; } cv.notify_all(); t.join(); } { // non-timed wait, wake up explicitly after 1 second std::thread t(thread_fn, false); std::this_thread::sleep_for(std::chrono::seconds(1)); { std::unique_lock<std::mutex> ml(mx); pass = true; } cv.notify_all(); t.join(); } return 0; } ``` On Linux or on Win32 with the MCF threading model the output looks like ``` pass: 0; wakeups: 2; elapsed: 2000 ms pass: 1; wakeups: 2; elapsed: 991 ms pass: 1; wakeups: 2; elapsed: 996 ms ``` but with the Win32 "regular" threading model ``` pass: 0; wakeups: 1418; elapsed: 2000 ms pass: 1; wakeups: 479; elapsed: 988 ms pass: 1; wakeups: 2; elapsed: 992 ms ``` As can be seen here, there's a huge number of spurious wakeups, but only for the timed waits; ultimately, I traced the problem to `__gthr_win32_abs_to_rel_time`, that is used to convert from the absolute `timespec` value (used internally through the gthread abstraction layer) to the relative milliseconds to be passed to `SleepConditionVariableCS`: ``` /* Convert absolute thread time to relative time in millisecond. */ DWORD __gthr_win32_abs_to_rel_time (const __gthread_time_t *abs_time) { union { ULONGLONG nsec100; FILETIME ft; } now; ULONGLONG abs_time_nsec100; /* The Windows epoch is 1/1/1601 while the Unix epoch is 1/1/1970. */ GetSystemTimeAsFileTime (&now.ft); now.nsec100 -= FILETIME_1970; abs_time_nsec100 = (ULONGLONG) abs_time->tv_sec * NSEC100_PER_SEC + CEIL_DIV (abs_time->tv_nsec, 100); if (abs_time_nsec100 < now.nsec100) return 0; return (DWORD) CEIL_DIV (abs_time_nsec100 - now.nsec100, NSEC100_PER_SEC); } ``` the final line has a typo: as the return value is in milliseconds, it should divide by the constant NSEC100_PER_MSEC; as it is, it returns the value in seconds, so it's going to be 1000 times smaller than it should, which results in ~1000× spurious wakeups. I suppose that was a typo or some rebase/refactoring mishap, as the correct constant was already defined above (and wasn't used for anything else). The patch of course is trivial, and applying it makes the test program return to its "normal" wakeups count.