[K/N] Address AtomicStack tsan reports.
This commit is contained in:
committed by
Space Team
parent
9548486b55
commit
d70f891a82
@@ -35,14 +35,21 @@ public:
|
||||
// freeing pages during STW.
|
||||
T* Pop() noexcept {
|
||||
T* elm = stack_.load(std::memory_order_acquire);
|
||||
while (elm && !stack_.compare_exchange_weak(elm, elm->next_, std::memory_order_acq_rel)) {}
|
||||
return elm;
|
||||
while (true) {
|
||||
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 {
|
||||
T* head = nullptr;
|
||||
do {
|
||||
elm->next_ = head;
|
||||
elm->next_.store(head, std::memory_order_relaxed);
|
||||
} 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.
|
||||
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
|
||||
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
|
||||
// simultaneously make `otherHead` the new current head.
|
||||
do {
|
||||
otherTail->next_ = thisHead;
|
||||
} while (!stack_.compare_exchange_weak(thisHead, otherHead, std::memory_order_acq_rel));
|
||||
otherTail->next_.store(thisHead, std::memory_order_relaxed);
|
||||
} 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; }
|
||||
|
||||
@@ -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 {
|
||||
CustomAllocInfo("ExtraObjectPage(%p)::ExtraObjectPage()", this);
|
||||
nextFree_ = cells_;
|
||||
nextFree_.store(cells_, std::memory_order_relaxed);
|
||||
ExtraObjectCell* end = cells_ + EXTRA_OBJECT_COUNT;
|
||||
for (ExtraObjectCell* cell = cells_; cell < end; cell = cell->next_) {
|
||||
cell->next_ = cell + 1;
|
||||
for (ExtraObjectCell* cell = cells_; cell < end; cell = cell->next_.load(std::memory_order_relaxed)) {
|
||||
cell->next_.store(cell + 1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,11 +37,12 @@ void ExtraObjectPage::Destroy() 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;
|
||||
}
|
||||
ExtraObjectCell* freeBlock = nextFree_;
|
||||
nextFree_ = freeBlock->next_;
|
||||
ExtraObjectCell* freeBlock = next;
|
||||
nextFree_.store(freeBlock->next_.load(std::memory_order_relaxed), std::memory_order_relaxed);
|
||||
CustomAllocDebug("ExtraObjectPage(%p)::TryAllocate() = %p", this, freeBlock->Data());
|
||||
return freeBlock->Data();
|
||||
}
|
||||
@@ -52,10 +53,10 @@ bool ExtraObjectPage::Sweep(GCSweepScope& sweepHandle, FinalizerQueue& finalizer
|
||||
// necessarily match an actual block starting point.
|
||||
ExtraObjectCell* end = cells_ + EXTRA_OBJECT_COUNT;
|
||||
bool alive = false;
|
||||
ExtraObjectCell** nextFree = &nextFree_;
|
||||
std::atomic<ExtraObjectCell*>* nextFree = &nextFree_;
|
||||
for (ExtraObjectCell* cell = cells_; cell < end; ++cell) {
|
||||
// If the current cell is free, move on.
|
||||
if (cell == *nextFree) {
|
||||
if (cell == nextFree->load(std::memory_order_relaxed)) {
|
||||
nextFree = &cell->next_;
|
||||
continue;
|
||||
}
|
||||
@@ -64,8 +65,8 @@ bool ExtraObjectPage::Sweep(GCSweepScope& sweepHandle, FinalizerQueue& finalizer
|
||||
alive = true;
|
||||
} else {
|
||||
// Free the current block and insert it into the free list.
|
||||
cell->next_ = *nextFree;
|
||||
*nextFree = cell;
|
||||
cell->next_.store(nextFree->load(std::memory_order_relaxed), std::memory_order_relaxed);
|
||||
nextFree->store(cell, std::memory_order_relaxed);
|
||||
nextFree = &cell->next_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ struct ExtraObjectCell {
|
||||
|
||||
// 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.
|
||||
ExtraObjectCell* next_;
|
||||
std::atomic<ExtraObjectCell*> next_;
|
||||
struct alignas(mm::ExtraObjectData) {
|
||||
uint8_t data_[sizeof(mm::ExtraObjectData)];
|
||||
};
|
||||
@@ -54,8 +54,8 @@ private:
|
||||
ExtraObjectPage() noexcept;
|
||||
|
||||
// Used for linking pages together in `pages` queue or in `unswept` queue.
|
||||
ExtraObjectPage* next_;
|
||||
ExtraObjectCell* nextFree_;
|
||||
std::atomic<ExtraObjectPage*> next_;
|
||||
std::atomic<ExtraObjectCell*> nextFree_;
|
||||
ExtraObjectCell cells_[];
|
||||
};
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ private:
|
||||
friend class AtomicStack<FixedBlockPage>;
|
||||
|
||||
// Used for linking pages together in `pages` queue or in `unswept` queue.
|
||||
FixedBlockPage* next_;
|
||||
std::atomic<FixedBlockPage*> next_;
|
||||
FixedCellRange nextFree_;
|
||||
uint32_t blockSize_;
|
||||
uint32_t end_;
|
||||
|
||||
@@ -46,7 +46,7 @@ private:
|
||||
void UpdateCurBlock(uint32_t cellsNeeded) noexcept;
|
||||
|
||||
friend class AtomicStack<NextFitPage>;
|
||||
NextFitPage* next_;
|
||||
std::atomic<NextFitPage*> next_;
|
||||
|
||||
Cell* curBlock_;
|
||||
Cell cells_[]; // cells_[0] is reserved for an empty block
|
||||
|
||||
@@ -41,7 +41,7 @@ private:
|
||||
// Testing method
|
||||
std_support::vector<uint8_t*> GetAllocatedBlocks() noexcept;
|
||||
|
||||
SingleObjectPage* next_;
|
||||
std::atomic<SingleObjectPage*> next_;
|
||||
bool isAllocated_ = false;
|
||||
size_t size_;
|
||||
struct alignas(8) {
|
||||
|
||||
Reference in New Issue
Block a user