[K/N] Address AtomicStack tsan reports.

This commit is contained in:
Alexander Shabalin
2023-07-31 14:03:58 +02:00
committed by Space Team
parent 9548486b55
commit d70f891a82
7 changed files with 90 additions and 23 deletions
@@ -35,14 +35,21 @@ public:
// freeing pages during STW. // freeing pages during STW.
T* Pop() noexcept { T* Pop() noexcept {
T* elm = stack_.load(std::memory_order_acquire); T* elm = stack_.load(std::memory_order_acquire);
while (elm && !stack_.compare_exchange_weak(elm, elm->next_, std::memory_order_acq_rel)) {} while (true) {
return elm; if (!elm) {
return nullptr;
}
auto* elmNext = elm->next_.load(std::memory_order_relaxed);
if (stack_.compare_exchange_weak(elm, elmNext, std::memory_order_acq_rel)) {
return elm;
}
}
} }
void Push(T* elm) noexcept { void Push(T* elm) noexcept {
T* head = nullptr; T* head = nullptr;
do { do {
elm->next_ = head; elm->next_.store(head, std::memory_order_relaxed);
} while (!stack_.compare_exchange_weak(head, elm, std::memory_order_acq_rel)); } while (!stack_.compare_exchange_weak(head, elm, std::memory_order_acq_rel));
} }
@@ -58,14 +65,14 @@ public:
} }
// `this` stack is not empty. Find the tail of `other`. If no deletions are performed, this is safe. // `this` stack is not empty. Find the tail of `other`. If no deletions are performed, this is safe.
T* otherTail = otherHead; T* otherTail = otherHead;
while (otherTail->next_) otherTail = otherTail->next_; while (auto* next = otherTail->next_.load(std::memory_order_relaxed)) otherTail = next;
// can't be because of the loop above // can't be because of the loop above
RuntimeAssert(otherTail->next_ == nullptr, "otherTail->next_ must be a tail"); RuntimeAssert(otherTail->next_.load(std::memory_order_relaxed) == nullptr, "otherTail->next_ must be a tail");
// Now make `otherTail->next_` point to the current head of `this` and // Now make `otherTail->next_` point to the current head of `this` and
// simultaneously make `otherHead` the new current head. // simultaneously make `otherHead` the new current head.
do { do {
otherTail->next_ = thisHead; otherTail->next_.store(thisHead, std::memory_order_relaxed);
} while (!stack_.compare_exchange_weak(thisHead, otherHead, std::memory_order_acq_rel)); } while (!stack_.compare_exchange_weak(thisHead, otherHead, std::memory_order_release, std::memory_order_relaxed));
} }
bool isEmpty() const noexcept { return stack_.load(std::memory_order_relaxed) == nullptr; } bool isEmpty() const noexcept { return stack_.load(std::memory_order_relaxed) == nullptr; }
@@ -0,0 +1,59 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "AtomicStack.hpp"
#include <array>
#include "TestSupport.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "ScopedThread.hpp"
using namespace kotlin;
namespace {
struct Element {
std::atomic<Element*> next_ = nullptr;
};
} // namespace
TEST(AtomicStackTest, StressPushPop) {
alloc::AtomicStack<Element> ready;
alloc::AtomicStack<Element> used;
std_support::vector<Element> elements(1000);
std_support::vector<Element*> expected;
for (auto& element : elements) {
ready.Push(&element);
expected.push_back(&element);
}
std::atomic<bool> canStart = false;
std_support::vector<ScopedThread> mutators;
for (int i = 0; i < kDefaultThreadCount; ++i) {
mutators.emplace_back([&]() NO_INLINE {
while (!canStart.load(std::memory_order_relaxed)) {
}
while (auto* element = ready.Pop()) {
used.Push(element);
}
});
}
canStart.store(true, std::memory_order_relaxed);
mutators.clear();
std_support::vector<Element*> actual;
while (auto* element = ready.Pop()) {
actual.push_back(element);
}
while (auto* element = used.Pop()) {
actual.push_back(element);
}
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
}
@@ -25,10 +25,10 @@ ExtraObjectPage* ExtraObjectPage::Create(uint32_t ignored) noexcept {
ExtraObjectPage::ExtraObjectPage() noexcept { ExtraObjectPage::ExtraObjectPage() noexcept {
CustomAllocInfo("ExtraObjectPage(%p)::ExtraObjectPage()", this); CustomAllocInfo("ExtraObjectPage(%p)::ExtraObjectPage()", this);
nextFree_ = cells_; nextFree_.store(cells_, std::memory_order_relaxed);
ExtraObjectCell* end = cells_ + EXTRA_OBJECT_COUNT; ExtraObjectCell* end = cells_ + EXTRA_OBJECT_COUNT;
for (ExtraObjectCell* cell = cells_; cell < end; cell = cell->next_) { for (ExtraObjectCell* cell = cells_; cell < end; cell = cell->next_.load(std::memory_order_relaxed)) {
cell->next_ = cell + 1; cell->next_.store(cell + 1, std::memory_order_relaxed);
} }
} }
@@ -37,11 +37,12 @@ void ExtraObjectPage::Destroy() noexcept {
} }
mm::ExtraObjectData* ExtraObjectPage::TryAllocate() noexcept { mm::ExtraObjectData* ExtraObjectPage::TryAllocate() noexcept {
if (nextFree_ >= cells_ + EXTRA_OBJECT_COUNT) { auto* next = nextFree_.load(std::memory_order_relaxed);
if (next >= cells_ + EXTRA_OBJECT_COUNT) {
return nullptr; return nullptr;
} }
ExtraObjectCell* freeBlock = nextFree_; ExtraObjectCell* freeBlock = next;
nextFree_ = freeBlock->next_; nextFree_.store(freeBlock->next_.load(std::memory_order_relaxed), std::memory_order_relaxed);
CustomAllocDebug("ExtraObjectPage(%p)::TryAllocate() = %p", this, freeBlock->Data()); CustomAllocDebug("ExtraObjectPage(%p)::TryAllocate() = %p", this, freeBlock->Data());
return freeBlock->Data(); return freeBlock->Data();
} }
@@ -52,10 +53,10 @@ bool ExtraObjectPage::Sweep(GCSweepScope& sweepHandle, FinalizerQueue& finalizer
// necessarily match an actual block starting point. // necessarily match an actual block starting point.
ExtraObjectCell* end = cells_ + EXTRA_OBJECT_COUNT; ExtraObjectCell* end = cells_ + EXTRA_OBJECT_COUNT;
bool alive = false; bool alive = false;
ExtraObjectCell** nextFree = &nextFree_; std::atomic<ExtraObjectCell*>* nextFree = &nextFree_;
for (ExtraObjectCell* cell = cells_; cell < end; ++cell) { for (ExtraObjectCell* cell = cells_; cell < end; ++cell) {
// If the current cell is free, move on. // If the current cell is free, move on.
if (cell == *nextFree) { if (cell == nextFree->load(std::memory_order_relaxed)) {
nextFree = &cell->next_; nextFree = &cell->next_;
continue; continue;
} }
@@ -64,8 +65,8 @@ bool ExtraObjectPage::Sweep(GCSweepScope& sweepHandle, FinalizerQueue& finalizer
alive = true; alive = true;
} else { } else {
// Free the current block and insert it into the free list. // Free the current block and insert it into the free list.
cell->next_ = *nextFree; cell->next_.store(nextFree->load(std::memory_order_relaxed), std::memory_order_relaxed);
*nextFree = cell; nextFree->store(cell, std::memory_order_relaxed);
nextFree = &cell->next_; nextFree = &cell->next_;
} }
} }
@@ -21,7 +21,7 @@ struct ExtraObjectCell {
// This is used to simultaneously build two lists: a free list and a finalizers queue. // This is used to simultaneously build two lists: a free list and a finalizers queue.
// A cell cannot exist in both of them, but can be in neither when it's alive. // A cell cannot exist in both of them, but can be in neither when it's alive.
ExtraObjectCell* next_; std::atomic<ExtraObjectCell*> next_;
struct alignas(mm::ExtraObjectData) { struct alignas(mm::ExtraObjectData) {
uint8_t data_[sizeof(mm::ExtraObjectData)]; uint8_t data_[sizeof(mm::ExtraObjectData)];
}; };
@@ -54,8 +54,8 @@ private:
ExtraObjectPage() noexcept; ExtraObjectPage() noexcept;
// Used for linking pages together in `pages` queue or in `unswept` queue. // Used for linking pages together in `pages` queue or in `unswept` queue.
ExtraObjectPage* next_; std::atomic<ExtraObjectPage*> next_;
ExtraObjectCell* nextFree_; std::atomic<ExtraObjectCell*> nextFree_;
ExtraObjectCell cells_[]; ExtraObjectCell cells_[];
}; };
@@ -53,7 +53,7 @@ private:
friend class AtomicStack<FixedBlockPage>; friend class AtomicStack<FixedBlockPage>;
// Used for linking pages together in `pages` queue or in `unswept` queue. // Used for linking pages together in `pages` queue or in `unswept` queue.
FixedBlockPage* next_; std::atomic<FixedBlockPage*> next_;
FixedCellRange nextFree_; FixedCellRange nextFree_;
uint32_t blockSize_; uint32_t blockSize_;
uint32_t end_; uint32_t end_;
@@ -46,7 +46,7 @@ private:
void UpdateCurBlock(uint32_t cellsNeeded) noexcept; void UpdateCurBlock(uint32_t cellsNeeded) noexcept;
friend class AtomicStack<NextFitPage>; friend class AtomicStack<NextFitPage>;
NextFitPage* next_; std::atomic<NextFitPage*> next_;
Cell* curBlock_; Cell* curBlock_;
Cell cells_[]; // cells_[0] is reserved for an empty block Cell cells_[]; // cells_[0] is reserved for an empty block
@@ -41,7 +41,7 @@ private:
// Testing method // Testing method
std_support::vector<uint8_t*> GetAllocatedBlocks() noexcept; std_support::vector<uint8_t*> GetAllocatedBlocks() noexcept;
SingleObjectPage* next_; std::atomic<SingleObjectPage*> next_;
bool isAllocated_ = false; bool isAllocated_ = false;
size_t size_; size_t size_;
struct alignas(8) { struct alignas(8) {