Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix race in the implementation of __tsan_acquire() #84923

Merged
merged 3 commits into from
Mar 12, 2024

Conversation

daveclausen
Copy link
Contributor

__tsan::Acquire(), which is called by __tsan_acquire(), has a performance optimization which attempts to avoid acquiring the atomic variable's mutex if the variable has no associated memory model state.

However, if the atomic variable was recently written to by a compare_exchange_weak/strong on another thread, the memory model state may be created after the atomic variable is updated. This is a data race, and can cause the thread calling Acquire() to not realize that the atomic variable was previously written to by another thread.

Specifically, if you have code that writes to an atomic variable using compare_exchange_weak/strong, and then in another thread you read the value using a relaxed load, followed by an atomic_thread_fence(memory_order_acquire), followed by a call to __tsan_acquire(), TSAN may not realize that the store happened before the fence, and so it will complain about any other variables you access from both threads if the thread-safety of those accesses depended on the happens-before relationship between the store and the fence.

This change eliminates the unsafe optimization in Acquire(). Now, Acquire() acquires the mutex before checking for the existence of the memory model state.

…lse positive data race warnings when the atomic variable was previously written to using compare_exchange_strong/weak.
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be
notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write
permissions for the repository. In which case you can instead tag reviewers by
name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review
by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate
is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@daveclausen daveclausen marked this pull request as ready for review March 12, 2024 14:41
@llvmbot
Copy link
Collaborator

llvmbot commented Mar 12, 2024

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Dave Clausen (daveclausen)

Changes

__tsan::Acquire(), which is called by __tsan_acquire(), has a performance optimization which attempts to avoid acquiring the atomic variable's mutex if the variable has no associated memory model state.

However, if the atomic variable was recently written to by a compare_exchange_weak/strong on another thread, the memory model state may be created after the atomic variable is updated. This is a data race, and can cause the thread calling Acquire() to not realize that the atomic variable was previously written to by another thread.

Specifically, if you have code that writes to an atomic variable using compare_exchange_weak/strong, and then in another thread you read the value using a relaxed load, followed by an atomic_thread_fence(memory_order_acquire), followed by a call to __tsan_acquire(), TSAN may not realize that the store happened before the fence, and so it will complain about any other variables you access from both threads if the thread-safety of those accesses depended on the happens-before relationship between the store and the fence.

This change eliminates the unsafe optimization in Acquire(). Now, Acquire() acquires the mutex before checking for the existence of the memory model state.


Full diff: https://github.com/llvm/llvm-project/pull/84923.diff

2 Files Affected:

  • (modified) compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp (+1-1)
  • (added) compiler-rt/test/tsan/compare_exchange_acquire_fence.cpp (+46)
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
index 2e978852ea7d37..2a8aa1915c9aeb 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
@@ -446,9 +446,9 @@ void Acquire(ThreadState *thr, uptr pc, uptr addr) {
   if (!s)
     return;
   SlotLocker locker(thr);
+  ReadLock lock(&s->mtx);
   if (!s->clock)
     return;
-  ReadLock lock(&s->mtx);
   thr->clock.Acquire(s->clock);
 }
 
diff --git a/compiler-rt/test/tsan/compare_exchange_acquire_fence.cpp b/compiler-rt/test/tsan/compare_exchange_acquire_fence.cpp
new file mode 100644
index 00000000000000..12bab80f14bfbb
--- /dev/null
+++ b/compiler-rt/test/tsan/compare_exchange_acquire_fence.cpp
@@ -0,0 +1,46 @@
+// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
+// This is a correct program and tsan should not report a race.
+//
+// Verify that there is a happens-before relationship between a
+// memory_order_release store that happens as part of a successful
+// compare_exchange_strong(), and an atomic_thread_fence(memory_order_acquire)
+// that happens after a relaxed load.
+
+#include <atomic>
+#include <sanitizer/tsan_interface.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <thread>
+
+std::atomic<bool> a;
+unsigned int b;
+constexpr int loops = 100000;
+
+void Thread1() {
+  for (int i = 0; i < loops; ++i) {
+    while (a.load(std::memory_order_acquire)) {
+    }
+    b = i;
+    bool expected = false;
+    a.compare_exchange_strong(expected, true, std::memory_order_acq_rel);
+  }
+}
+
+int main() {
+  std::thread t(Thread1);
+  unsigned int sum = 0;
+  for (int i = 0; i < loops; ++i) {
+    while (!a.load(std::memory_order_relaxed)) {
+    }
+    std::atomic_thread_fence(std::memory_order_acquire);
+    __tsan_acquire(&a);
+    sum += b;
+    a.store(false, std::memory_order_release);
+  }
+  t.join();
+  fprintf(stderr, "DONE: %u\n", sum);
+  return 0;
+}
+
+// CHECK: DONE
+// CHECK-NOT: ThreadSanitizer: data race

@vitalybuka vitalybuka requested a review from dvyukov March 12, 2024 17:54
Copy link
Collaborator

@vitalybuka vitalybuka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you run ninjaLIT_OPTS="--time-tests" ninja ...
We may need to tune test if it in the top.

@@ -0,0 +1,46 @@
// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exit code 0 should be enough
we probably do not need | FileCheck here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

I also removed the CHECK and CHECK-NOT comment lines at the end of the file.

Copy link
Contributor Author

@daveclausen daveclausen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't show up in the list of slowest tests:

$ LIT_OPTS="--time-tests" ninja check-tsan

...

Slowest Tests:
--------------------------------------------------------------------------
45.32s: ThreadSanitizer-x86_64 :: restore_stack.cpp
33.56s: ThreadSanitizer-x86_64 :: Linux/check_memcpy.c
21.56s: ThreadSanitizer-x86_64 :: bench_threads.cpp
14.70s: ThreadSanitizer-x86_64 :: deadlock_detector_stress_test.cpp
14.65s: ThreadSanitizer-x86_64 :: signal_thread.cpp
12.60s: ThreadSanitizer-x86_64 :: stress.cpp
10.81s: ThreadSanitizer-x86_64 :: Linux/fork_multithreaded4.cpp
9.79s: ThreadSanitizer-x86_64 :: force_background_thread.cpp
9.53s: ThreadSanitizer-x86_64 :: lots_of_threads.c
7.14s: ThreadSanitizer-x86_64 :: deep_stack1.cpp
5.73s: ThreadSanitizer-x86_64 :: fd_close_norace3.cpp
5.40s: ThreadSanitizer-x86_64 :: signal_reset.cpp
5.19s: ThreadSanitizer-x86_64 :: mmap_stress.cpp
4.84s: ThreadSanitizer-x86_64 :: signal_sync2.cpp
4.48s: ThreadSanitizer-x86_64 :: global_race.cpp
4.41s: ThreadSanitizer-x86_64 :: ignore_lib6.cpp
3.65s: ThreadSanitizer-x86_64 :: mmap_stress2.cpp
3.51s: ThreadSanitizer-x86_64 :: must_deadlock.cpp
3.51s: ThreadSanitizer-x86_64 :: simple_stack.c
3.50s: ThreadSanitizer-x86_64 :: setuid2.c

Tests Times:
--------------------------------------------------------------------------
[    Range    ] :: [               Percentage               ] :: [ Count ]
--------------------------------------------------------------------------
[44.0s,46.0s) :: [                                        ] :: [  1/409]
[42.0s,44.0s) :: [                                        ] :: [  0/409]
[40.0s,42.0s) :: [                                        ] :: [  0/409]
[38.0s,40.0s) :: [                                        ] :: [  0/409]
[36.0s,38.0s) :: [                                        ] :: [  0/409]
[34.0s,36.0s) :: [                                        ] :: [  1/409]
[32.0s,34.0s) :: [                                        ] :: [  0/409]
[30.0s,32.0s) :: [                                        ] :: [  0/409]
[28.0s,30.0s) :: [                                        ] :: [  0/409]
[26.0s,28.0s) :: [                                        ] :: [  0/409]
[24.0s,26.0s) :: [                                        ] :: [  0/409]
[22.0s,24.0s) :: [                                        ] :: [  0/409]
[20.0s,22.0s) :: [                                        ] :: [  1/409]
[18.0s,20.0s) :: [                                        ] :: [  0/409]
[16.0s,18.0s) :: [                                        ] :: [  0/409]
[14.0s,16.0s) :: [                                        ] :: [  2/409]
[12.0s,14.0s) :: [                                        ] :: [  1/409]
[10.0s,12.0s) :: [                                        ] :: [  1/409]
[ 8.0s,10.0s) :: [                                        ] :: [  2/409]
[ 6.0s, 8.0s) :: [                                        ] :: [  1/409]
[ 4.0s, 6.0s) :: [                                        ] :: [  6/409]
[ 2.0s, 4.0s) :: [*                                       ] :: [ 16/409]
[ 0.0s, 2.0s) :: [************************************    ] :: [377/409]
--------------------------------------------------------------------------

Testing Time: 45.37s

Total Discovered Tests: 451
  Unsupported      :  88 (19.51%)
  Passed           : 362 (80.27%)
  Expectedly Failed:   1 (0.22%)

@vitalybuka
Copy link
Collaborator

Merging having that @dvyukov LGTMed code in internal conversation.

@vitalybuka vitalybuka merged commit 47625e4 into llvm:main Mar 12, 2024
4 checks passed
Copy link

@daveclausen Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested
by our build bots. If there is a problem with a build, you may recieve a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as
the builds can include changes from many authors. It is not uncommon for your
change to be included in a build that fails due to someone else's changes, or
infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself.
This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

@vitalybuka
Copy link
Collaborator

It needs %link_libcxx_tsan, like in llvm-project/compiler-rt/test/tsan/compare_exchange.cpp

vitalybuka pushed a commit that referenced this pull request Mar 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants