c1281e6b45
Reviewed-by: kbarrett, dholmes, jwaters
307 lines
8.8 KiB
C++
307 lines
8.8 KiB
C++
/*
|
|
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
#include "precompiled.hpp"
|
|
#include "memory/allocation.inline.hpp"
|
|
#include "runtime/atomic.hpp"
|
|
#include "utilities/globalDefinitions.hpp"
|
|
#include "utilities/lockFreeStack.hpp"
|
|
#include "threadHelper.inline.hpp"
|
|
#include "unittest.hpp"
|
|
|
|
#include <new>
|
|
|
|
class LockFreeStackTestElement {
|
|
typedef LockFreeStackTestElement Element;
|
|
|
|
Element* volatile _entry;
|
|
Element* volatile _entry1;
|
|
size_t _id;
|
|
|
|
static Element* volatile* entry_ptr(Element& e) { return &e._entry; }
|
|
static Element* volatile* entry1_ptr(Element& e) { return &e._entry1; }
|
|
|
|
public:
|
|
LockFreeStackTestElement(size_t id = 0) : _entry(), _entry1(), _id(id) {}
|
|
size_t id() const { return _id; }
|
|
void set_id(size_t value) { _id = value; }
|
|
|
|
typedef LockFreeStack<Element, &entry_ptr> TestStack;
|
|
typedef LockFreeStack<Element, &entry1_ptr> TestStack1;
|
|
};
|
|
|
|
typedef LockFreeStackTestElement Element;
|
|
typedef Element::TestStack TestStack;
|
|
typedef Element::TestStack1 TestStack1;
|
|
|
|
static void initialize_ids(Element* elements, size_t size) {
|
|
for (size_t i = 0; i < size; ++i) {
|
|
elements[i].set_id(i);
|
|
}
|
|
}
|
|
|
|
class LockFreeStackTestBasics : public ::testing::Test {
|
|
public:
|
|
LockFreeStackTestBasics();
|
|
|
|
static const size_t nelements = 10;
|
|
Element elements[nelements];
|
|
TestStack stack;
|
|
|
|
private:
|
|
void initialize();
|
|
};
|
|
|
|
const size_t LockFreeStackTestBasics::nelements;
|
|
|
|
LockFreeStackTestBasics::LockFreeStackTestBasics() : stack() {
|
|
initialize_ids(elements, nelements);
|
|
initialize();
|
|
}
|
|
|
|
void LockFreeStackTestBasics::initialize() {
|
|
ASSERT_TRUE(stack.empty());
|
|
ASSERT_EQ(0u, stack.length());
|
|
ASSERT_TRUE(stack.pop() == nullptr);
|
|
ASSERT_TRUE(stack.top() == nullptr);
|
|
|
|
for (size_t id = 0; id < nelements; ++id) {
|
|
ASSERT_EQ(id, stack.length());
|
|
Element* e = &elements[id];
|
|
ASSERT_EQ(id, e->id());
|
|
stack.push(*e);
|
|
ASSERT_FALSE(stack.empty());
|
|
ASSERT_EQ(e, stack.top());
|
|
}
|
|
}
|
|
|
|
TEST_F(LockFreeStackTestBasics, push_pop) {
|
|
for (size_t i = nelements; i > 0; ) {
|
|
ASSERT_FALSE(stack.empty());
|
|
ASSERT_EQ(i, stack.length());
|
|
--i;
|
|
Element* e = stack.pop();
|
|
ASSERT_TRUE(e != nullptr);
|
|
ASSERT_EQ(&elements[i], e);
|
|
ASSERT_EQ(i, e->id());
|
|
}
|
|
ASSERT_TRUE(stack.empty());
|
|
ASSERT_EQ(0u, stack.length());
|
|
ASSERT_TRUE(stack.pop() == nullptr);
|
|
}
|
|
|
|
TEST_F(LockFreeStackTestBasics, prepend_one) {
|
|
TestStack other_stack;
|
|
ASSERT_TRUE(other_stack.empty());
|
|
ASSERT_TRUE(other_stack.pop() == nullptr);
|
|
ASSERT_EQ(0u, other_stack.length());
|
|
ASSERT_TRUE(other_stack.top() == nullptr);
|
|
ASSERT_TRUE(other_stack.pop() == nullptr);
|
|
|
|
other_stack.prepend(*stack.pop_all());
|
|
ASSERT_EQ(nelements, other_stack.length());
|
|
ASSERT_TRUE(stack.empty());
|
|
ASSERT_EQ(0u, stack.length());
|
|
ASSERT_TRUE(stack.pop() == nullptr);
|
|
ASSERT_TRUE(stack.top() == nullptr);
|
|
|
|
for (size_t i = nelements; i > 0; ) {
|
|
ASSERT_EQ(i, other_stack.length());
|
|
--i;
|
|
Element* e = other_stack.pop();
|
|
ASSERT_TRUE(e != nullptr);
|
|
ASSERT_EQ(&elements[i], e);
|
|
ASSERT_EQ(i, e->id());
|
|
}
|
|
ASSERT_EQ(0u, other_stack.length());
|
|
ASSERT_TRUE(other_stack.pop() == nullptr);
|
|
}
|
|
|
|
TEST_F(LockFreeStackTestBasics, prepend_two) {
|
|
TestStack other_stack;
|
|
ASSERT_TRUE(other_stack.empty());
|
|
ASSERT_EQ(0u, other_stack.length());
|
|
ASSERT_TRUE(other_stack.top() == nullptr);
|
|
ASSERT_TRUE(other_stack.pop() == nullptr);
|
|
|
|
Element* top = stack.pop_all();
|
|
ASSERT_EQ(top, &elements[nelements - 1]);
|
|
other_stack.prepend(*top, elements[0]);
|
|
|
|
for (size_t i = nelements; i > 0; ) {
|
|
ASSERT_EQ(i, other_stack.length());
|
|
--i;
|
|
Element* e = other_stack.pop();
|
|
ASSERT_TRUE(e != nullptr);
|
|
ASSERT_EQ(&elements[i], e);
|
|
ASSERT_EQ(i, e->id());
|
|
}
|
|
ASSERT_EQ(0u, other_stack.length());
|
|
ASSERT_TRUE(other_stack.pop() == nullptr);
|
|
}
|
|
|
|
TEST_F(LockFreeStackTestBasics, two_stacks) {
|
|
TestStack1 stack1;
|
|
ASSERT_TRUE(stack1.pop() == nullptr);
|
|
|
|
for (size_t id = 0; id < nelements; ++id) {
|
|
stack1.push(elements[id]);
|
|
}
|
|
ASSERT_EQ(nelements, stack1.length());
|
|
Element* e0 = stack.top();
|
|
Element* e1 = stack1.top();
|
|
while (true) {
|
|
ASSERT_EQ(e0, e1);
|
|
if (e0 == nullptr) break;
|
|
e0 = stack.next(*e0);
|
|
e1 = stack1.next(*e1);
|
|
}
|
|
|
|
for (size_t i = nelements; i > 0; ) {
|
|
ASSERT_EQ(i, stack.length());
|
|
ASSERT_EQ(i, stack1.length());
|
|
--i;
|
|
Element* e = stack.pop();
|
|
ASSERT_TRUE(e != nullptr);
|
|
ASSERT_EQ(&elements[i], e);
|
|
ASSERT_EQ(i, e->id());
|
|
|
|
Element* e1 = stack1.pop();
|
|
ASSERT_TRUE(e1 != nullptr);
|
|
ASSERT_EQ(&elements[i], e1);
|
|
ASSERT_EQ(i, e1->id());
|
|
|
|
ASSERT_EQ(e, e1);
|
|
}
|
|
ASSERT_EQ(0u, stack.length());
|
|
ASSERT_EQ(0u, stack1.length());
|
|
ASSERT_TRUE(stack.pop() == nullptr);
|
|
ASSERT_TRUE(stack1.pop() == nullptr);
|
|
}
|
|
|
|
class LockFreeStackTestThread : public JavaTestThread {
|
|
uint _id;
|
|
TestStack* _from;
|
|
TestStack* _to;
|
|
volatile size_t* _processed;
|
|
size_t _process_limit;
|
|
size_t _local_processed;
|
|
volatile bool _ready;
|
|
|
|
public:
|
|
LockFreeStackTestThread(Semaphore* post,
|
|
uint id,
|
|
TestStack* from,
|
|
TestStack* to,
|
|
volatile size_t* processed,
|
|
size_t process_limit) :
|
|
JavaTestThread(post),
|
|
_id(id),
|
|
_from(from),
|
|
_to(to),
|
|
_processed(processed),
|
|
_process_limit(process_limit),
|
|
_local_processed(0),
|
|
_ready(false)
|
|
{}
|
|
|
|
virtual void main_run() {
|
|
Atomic::release_store_fence(&_ready, true);
|
|
while (true) {
|
|
Element* e = _from->pop();
|
|
if (e != nullptr) {
|
|
_to->push(*e);
|
|
Atomic::inc(_processed);
|
|
++_local_processed;
|
|
} else if (Atomic::load_acquire(_processed) == _process_limit) {
|
|
tty->print_cr("thread %u processed " SIZE_FORMAT, _id, _local_processed);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ready() const { return Atomic::load_acquire(&_ready); }
|
|
};
|
|
|
|
TEST_VM(LockFreeStackTest, stress) {
|
|
Semaphore post;
|
|
TestStack initial_stack;
|
|
TestStack start_stack;
|
|
TestStack middle_stack;
|
|
TestStack final_stack;
|
|
volatile size_t stage1_processed = 0;
|
|
volatile size_t stage2_processed = 0;
|
|
|
|
const size_t nelements = 10000;
|
|
Element* elements = NEW_C_HEAP_ARRAY(Element, nelements, mtOther);
|
|
for (size_t id = 0; id < nelements; ++id) {
|
|
::new (&elements[id]) Element(id);
|
|
initial_stack.push(elements[id]);
|
|
}
|
|
ASSERT_EQ(nelements, initial_stack.length());
|
|
|
|
// - stage1 threads pop from start_stack and push to middle_stack.
|
|
// - stage2 threads pop from middle_stack and push to final_stack.
|
|
// - all threads in a stage count the number of elements processed in
|
|
// their corresponding stageN_processed counter.
|
|
|
|
const uint stage1_threads = 2;
|
|
const uint stage2_threads = 2;
|
|
const uint nthreads = stage1_threads + stage2_threads;
|
|
LockFreeStackTestThread* threads[nthreads] = {};
|
|
|
|
for (uint i = 0; i < ARRAY_SIZE(threads); ++i) {
|
|
TestStack* from = &start_stack;
|
|
TestStack* to = &middle_stack;
|
|
volatile size_t* processed = &stage1_processed;
|
|
if (i >= stage1_threads) {
|
|
from = &middle_stack;
|
|
to = &final_stack;
|
|
processed = &stage2_processed;
|
|
}
|
|
threads[i] =
|
|
new LockFreeStackTestThread(&post, i, from, to, processed, nelements);
|
|
threads[i]->doit();
|
|
while (!threads[i]->ready()) {} // Wait until ready to start test.
|
|
}
|
|
|
|
// Transfer elements to start_stack to start test.
|
|
start_stack.prepend(*initial_stack.pop_all());
|
|
|
|
// Wait for all threads to complete.
|
|
for (uint i = 0; i < nthreads; ++i) {
|
|
post.wait();
|
|
}
|
|
|
|
// Verify expected state.
|
|
ASSERT_EQ(nelements, stage1_processed);
|
|
ASSERT_EQ(nelements, stage2_processed);
|
|
ASSERT_EQ(0u, initial_stack.length());
|
|
ASSERT_EQ(0u, start_stack.length());
|
|
ASSERT_EQ(0u, middle_stack.length());
|
|
ASSERT_EQ(nelements, final_stack.length());
|
|
while (final_stack.pop() != nullptr) {}
|
|
|
|
FREE_C_HEAP_ARRAY(Element, elements);
|
|
}
|