LLVM Bugzilla is read-only and represents the historical archive of all LLVM issues filled before November 26, 2021. Use github to submit LLVM bugs

Bug 41323 - Race condition in steady_clock::now for _LIBCPP_WIN32API
Summary: Race condition in steady_clock::now for _LIBCPP_WIN32API
Status: RESOLVED FIXED
Alias: None
Product: libc++
Classification: Unclassified
Component: All Bugs (show other bugs)
Version: unspecified
Hardware: PC All
: P normal
Assignee: Unassigned Clang Bugs
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-03-31 06:11 PDT by Ivan Afanasyev
Modified: 2019-04-02 07:00 PDT (History)
4 users (show)

See Also:
Fixed By Commit(s):


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Ivan Afanasyev 2019-03-31 06:11:06 PDT
Method implementation is:

```
steady_clock::time_point
steady_clock::now() _NOEXCEPT
{
  static LARGE_INTEGER freq;
  static BOOL initialized = FALSE;
  if (!initialized)
    initialized = QueryPerformanceFrequency(&freq); // always succceeds

  LARGE_INTEGER counter;
  QueryPerformanceCounter(&counter);
  return time_point(duration(counter.QuadPart * nano::den / freq.QuadPart));
}
```

And seems like there is a race condition on `freq` and `initialized` variables if `steady_clock::now` has never been called before new threads creation.

Possible fixes are:

1. use thread local storage for `freq` and `initialized`, but there might be some problems on Windows XP:
https://reverseengineering.stackexchange.com/questions/14171/thread-local-storage-access-on-windows-xp

2. use `std::call_once` guard, but it is slightly slower than thread local due to atomic access.
Comment 1 Marshall Clow (home) 2019-04-01 06:46:35 PDT
This should fix the problem, but I don't have direct access to a Windows box to check.
Can you let me know?
Also, how did you discover this?

diff --git a/libcxx/src/chrono.cpp b/libcxx/src/chrono.cpp
index ea0a638acc5..d342ee32d6c 100644
--- a/libcxx/src/chrono.cpp
+++ b/libcxx/src/chrono.cpp
@@ -177,16 +177,21 @@ steady_clock::now() _NOEXCEPT
 
 #elif defined(_LIBCPP_WIN32API)
 
+static LARGE_INTEGER
+init_FREQ()
+{
+	LARGE_INTEGER val;
+	(void) QueryPerformanceFrequency(&val);
+	return val;
+}
+
 steady_clock::time_point
 steady_clock::now() _NOEXCEPT
 {
-  static LARGE_INTEGER freq;
-  static BOOL initialized = FALSE;
-  if (!initialized)
-    initialized = QueryPerformanceFrequency(&freq); // always succceeds
+  static const LARGE_INTEGER freq = init_FREQ();
 
   LARGE_INTEGER counter;
-  QueryPerformanceCounter(&counter);
+  (void) QueryPerformanceCounter(&counter);
   return time_point(duration(counter.QuadPart * nano::den / freq.QuadPart));
 }
Comment 2 Ivan Afanasyev 2019-04-01 08:49:05 PDT
Yes, this fix is ok.
And it is cleaner than my proposals.

> Can you let me know? Also, how did you discover this?

Well, I do not have windows box too. I'm just reviewing libcpp code in order to understand how it is organized.
Comment 3 Marshall Clow (home) 2019-04-01 10:23:50 PDT
Fixed in r357413.
Comment 4 ensadc 2019-04-01 20:28:14 PDT
(In reply to Marshall Clow (home) from comment #3)
> Fixed in r357413.

r357413 changes a invocation of "QueryPerformanceCounter" to instead call "QueryPerformanceFrequency" (note Counter vs. Frequency). Seems like a mistake?
Comment 5 Marshall Clow (home) 2019-04-02 07:00:53 PDT
(In reply to ensadc from comment #4)
> (In reply to Marshall Clow (home) from comment #3)
> > Fixed in r357413.
> 
> r357413 changes a invocation of "QueryPerformanceCounter" to instead call
> "QueryPerformanceFrequency" (note Counter vs. Frequency). Seems like a
> mistake?

Oops. r357474