summaryrefslogtreecommitdiffstats
path: root/library/cpp/yt
diff options
context:
space:
mode:
authorsabdenovch <[email protected]>2026-03-13 23:04:24 +0300
committersabdenovch <[email protected]>2026-03-13 23:37:27 +0300
commit323ade82b4304f301ab30560faca08499c3aea6e (patch)
tree3ed49f7790ccfe0e6de6d7638e966ea38da82ffe /library/cpp/yt
parentfbeed14823264a2c90b8dcd8878daa0e6e968727 (diff)
YT-27634: Annotate DCAS with TSAN intrinsics
commit_hash:7b0bb805a82d9829ea93f5b867962e77a2c56244
Diffstat (limited to 'library/cpp/yt')
-rw-r--r--library/cpp/yt/memory/free_list-inl.h15
-rw-r--r--library/cpp/yt/memory/unittests/free_list_ut.cpp85
2 files changed, 97 insertions, 3 deletions
diff --git a/library/cpp/yt/memory/free_list-inl.h b/library/cpp/yt/memory/free_list-inl.h
index 135eb5421b6..a7adbb0e238 100644
--- a/library/cpp/yt/memory/free_list-inl.h
+++ b/library/cpp/yt/memory/free_list-inl.h
@@ -4,6 +4,8 @@
#include "free_list.h"
#endif
+#include <util/system/sanitizers.h>
+
namespace NYT {
////////////////////////////////////////////////////////////////////////////////
@@ -23,6 +25,16 @@ Y_FORCE_INLINE bool CasFreeListPackedPair(
T1 new1,
T2 new2)
{
+#if defined(_tsan_enabled_)
+ __tsan_acquire(const_cast<unsigned __int128*>(atomic));
+ __tsan_release(const_cast<unsigned __int128*>(atomic));
+ // NB(sabdenovch): release must happen before cmpxchg to avoid the situation
+ // where producer writes to non-atomic memory does CAS,
+ // sleeps without letting thread sanitizer know about release,
+ // then consumer does CAS, informs thread sanitizer about acquire and reads non-atomic memory.
+ // No real race exists anyway, this is a workaround for TSAN false positives.
+#endif
+
#if defined(__x86_64__)
bool success;
__asm__ __volatile__
@@ -153,7 +165,6 @@ TFreeList<TItem>::~TFreeList()
template <class TItem>
template <class TPredicate>
-Y_NO_SANITIZE("thread")
bool TFreeList<TItem>::PutIf(TItem* head, TItem* tail, TPredicate predicate)
{
auto* current = Head_.Pointer.load(std::memory_order::relaxed);
@@ -172,7 +183,6 @@ bool TFreeList<TItem>::PutIf(TItem* head, TItem* tail, TPredicate predicate)
}
template <class TItem>
-Y_NO_SANITIZE("thread")
void TFreeList<TItem>::Put(TItem* head, TItem* tail)
{
auto* current = Head_.Pointer.load(std::memory_order::relaxed);
@@ -190,7 +200,6 @@ void TFreeList<TItem>::Put(TItem* item)
}
template <class TItem>
-Y_NO_SANITIZE("thread")
TItem* TFreeList<TItem>::Extract()
{
auto* current = Head_.Pointer.load(std::memory_order::relaxed);
diff --git a/library/cpp/yt/memory/unittests/free_list_ut.cpp b/library/cpp/yt/memory/unittests/free_list_ut.cpp
index 81f60b694fe..371dc6a2da8 100644
--- a/library/cpp/yt/memory/unittests/free_list_ut.cpp
+++ b/library/cpp/yt/memory/unittests/free_list_ut.cpp
@@ -155,5 +155,90 @@ INSTANTIATE_TEST_SUITE_P(
////////////////////////////////////////////////////////////////////////////////
+struct TFreeListItem
+ : public TFreeListItemBase<TFreeListItem>
+{
+ explicit TFreeListItem(i64 value)
+ : Value(value)
+ { }
+ i64 Value;
+};
+
+static std::chrono::steady_clock::time_point Now()
+{
+ return std::chrono::steady_clock::now();
+}
+
+void ProducerThread(TFreeList<TFreeListItem>* freeList, i64 startValue, std::chrono::steady_clock::time_point until)
+{
+ i64 v = startValue;
+ while (Now() < until) {
+ freeList->Put(new TFreeListItem(v));
+ v += 2;
+ }
+}
+
+void ConsumerThread(TFreeList<TFreeListItem>* freeList, std::chrono::steady_clock::time_point until)
+{
+ i64 latestEven = -2;
+ i64 latestOdd = -1;
+ std::vector<i64> buffer;
+ while (Now() < until) {
+ buffer.clear();
+ auto* extractedList = freeList->ExtractAll();
+ while (extractedList) {
+ buffer.push_back(extractedList->Value);
+ auto* next = extractedList->Next.load(std::memory_order::acquire);
+ delete extractedList;
+ extractedList = next;
+ }
+
+ for (auto it = buffer.rbegin(); it != buffer.rend(); ++it) {
+ auto value = *it;
+ if (value % 2 == 0) {
+ ASSERT_GT(value, latestEven);
+ latestEven = value;
+ } else {
+ ASSERT_GT(value, latestOdd);
+ latestOdd = value;
+ }
+ }
+ }
+}
+
+void Cleanup(TFreeList<TFreeListItem>* freeList)
+{
+ auto* node = freeList->ExtractAll();
+ while (node) {
+ auto* next = node->Next.load(std::memory_order::acquire);
+ delete node;
+ node = next;
+ }
+}
+
+TEST(TFreeListTest, ProducerConsumer)
+{
+ auto now = Now();
+ auto until = now + std::chrono::seconds(15);
+
+ auto freeList = TFreeList<TFreeListItem>();
+
+ std::vector<std::thread> threads;
+
+ threads.emplace_back(ProducerThread, &freeList, 0, until);
+ threads.emplace_back(ProducerThread, &freeList, 1, until);
+
+ threads.emplace_back(ConsumerThread, &freeList, until);
+ threads.emplace_back(ConsumerThread, &freeList, until);
+
+ for (auto& thread : threads) {
+ thread.join();
+ }
+
+ Cleanup(&freeList);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
} // namespace
} // namespace NYT