Commit 554c76f7 by bernhard-gatzhammer

Merge remote-tracking branch 'origin/development' into embb453_rwlock

parents fc660db0 cd0ebac0
Embedded Multicore Building Blocks (EMB²)
=========================================
Version 0.3.1
-------------
### Features:
- None
### Changes and improvements:
- Removed one function argument from algorithms::Invoke
- Added "explicit" specifier to base type constructor of Atomic<BaseType*>
- Added "const" qualifier to dereference operator and member access operator of AtomicPointer<>
- Changed AtomicBase<>::CompareAndSwap to atomically return expected value
- Replaced constant in dataflow_cpp_test_simple.cc with corresponding macro
- Added initialization of atomic variable in hazard_pointer_test.cc to avoid warning with GCC 5.1
- Changed initial value of allocated_object_from_different_thread
- Added tests for ID Pool and check for memory leaks
- Updated unit test for the UniqueLock::Swap
### Bug fixes:
- Fixed implementation of ID pool (provided fewer elements than specified by capacity)
- Fixed unsigned overflow bug in timed wait function of condition variables
- Fixed implementation of UniqueLock::Swap
### Build system:
- Improved CMake output for automatic initialization option
- Fixed cpplint and unsigned/signed warnings
### Documentation:
- Fixed documentation of UniqueLock class
- Updated README file
Version 0.3.0
-------------
......
......@@ -28,7 +28,7 @@ cmake_minimum_required (VERSION 2.8.9)
# Version number
set (EMBB_BASE_VERSION_MAJOR 0)
set (EMBB_BASE_VERSION_MINOR 3)
set (EMBB_BASE_VERSION_PATCH 0)
set (EMBB_BASE_VERSION_PATCH 1)
# Fix compilation for CMake versions >= 3.1
#
......@@ -59,7 +59,9 @@ IF(NOT OpenCL_FOUND)
MESSAGE( STATUS "OpenCL is not there, will build without MTAPI OpenCL Plugin." )
ENDIF()
# give the user the possibility, to append compiler flags
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CMAKE_CXX_FLAGS}")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CMAKE_C_FLAGS}")
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING
......@@ -100,6 +102,13 @@ else()
endif()
message(" (set with command line option -DWARNINGS_ARE_ERRORS=ON/OFF)")
if (USE_AUTOMATIC_INITIALIZATION STREQUAL ON)
message("-- MTAPI/Tasks automatic initialization enabled (default)")
else()
message("-- MTAPI/Tasks automatic initialization disabled")
endif()
message(" (set with command line option -DUSE_AUTOMATIC_INITIALIZATION=ON/OFF)")
include(CMakeCommon/SetCompilerFlags.cmake)
SetGNUCompilerFlags(compiler_libs compiler_flags)
SetVisualStudioCompilerFlags(compiler_libs compiler_flags)
......
......@@ -270,8 +270,8 @@ If you want to use the C++ functionalities of EMB², you have to link the
following libraries (names will be different on Windows and on Linux) in the
given order:
embb_base, embb_base_cpp, embb_mtapi_c, embb_mtapi_cpp, embb_containers_cpp,
embb_algorithms_cpp, embb_dataflow_cpp
embb_dataflow_cpp, embb_algorithms_cpp, embb_containers_cpp,
embb_mtapi_cpp, embb_mtapi_c, embb_base_cpp, embb_base_c
The C++ header files can be included as follows:
......@@ -284,7 +284,7 @@ The C++ header files can be included as follows:
The following libraries have to be linked in the given order:
embb_base_c, mtapi_c
embb_mtapi_c, embb_base_c
The C header files can be included as follows:
......@@ -323,6 +323,8 @@ Known Bugs and Limitations
is bounded by a predefined but modifiable constant (see functions
embb_thread_get_max_count() / embb_thread_set_max_count() and class
embb::base::Thread).
- While MTAPI fully supports heterogeneous systems, the algorithms and
dataflow components are currently limited to homogeneous systems.
Development and Contribution
......
......@@ -49,33 +49,37 @@ typedef embb::base::Function<void> InvokeFunctionType;
#ifdef DOXYGEN
/**
* Spawns one to ten function objects at once and runs them in parallel.
* Spawns two to ten function objects at once and runs them in parallel.
*
* Blocks until all of them are done.
*
* \ingroup CPP_ALGORITHMS_INVOKE
*/
template<typename Function1, ...>
template<typename Function1, typename Function2, ...>
void Invoke(
Function1 func1,
/**< [in] First function object to invoke */
Function2 func2,
/**< [in] Second function object to invoke */
...);
/**
* Spawns one to ten function objects at once and runs them in parallel using the
* Spawns two to ten function objects at once and runs them in parallel using the
* given embb::mtapi::ExecutionPolicy.
*
* Blocks until all of them are done.
*
* \ingroup CPP_ALGORITHMS_INVOKE
*/
template<typename Function1, ...>
template<typename Function1, typename Function2, ...>
void Invoke(
Function1 func1,
/**< [in] Function object to invoke */
Function2 func2,
/**< [in] Second function object to invoke */
...,
const embb::mtapi::ExecutionPolicy & policy
/**< [in] embb::mtapi::ExecutionPolicy to use */
const embb::tasks::ExecutionPolicy & policy
/**< [in] embb::tasks::ExecutionPolicy to use */
);
#else // DOXYGEN
......@@ -118,13 +122,6 @@ class TaskWrapper {
};
} // namespace internal
template<typename Function1>
void Invoke(
Function1 func1,
const embb::tasks::ExecutionPolicy& policy) {
internal::TaskWrapper<Function1> wrap1(func1, policy);
}
template<typename Function1, typename Function2>
void Invoke(
Function1 func1,
......@@ -290,12 +287,6 @@ template<typename Function1, typename Function2, typename Function3,
internal::TaskWrapper<Function10> wrap10(func10, policy);
}
template<typename Function1>
void Invoke(
Function1 func1) {
Invoke(func1, embb::tasks::ExecutionPolicy());
}
template<typename Function1, typename Function2>
void Invoke(
Function1 func1,
......
......@@ -44,7 +44,6 @@ static void Invocable10() {}
void InvokeTest::Test() {
using embb::algorithms::Invoke;
Invoke(&Invocable1);
Invoke(&Invocable1, &Invocable2);
Invoke(&Invocable1, &Invocable2, &Invocable3);
Invoke(&Invocable1, &Invocable2, &Invocable3, &Invocable4);
......@@ -61,4 +60,24 @@ void InvokeTest::Test() {
&Invocable6, &Invocable7, &Invocable8, &Invocable9);
Invoke(&Invocable1, &Invocable2, &Invocable3, &Invocable4, &Invocable5,
&Invocable6, &Invocable7, &Invocable8, &Invocable9, &Invocable10);
embb::tasks::ExecutionPolicy policy;
Invoke(&Invocable1, &Invocable2, policy);
Invoke(&Invocable1, &Invocable2, &Invocable3, policy);
Invoke(&Invocable1, &Invocable2, &Invocable3, &Invocable4, policy);
Invoke(&Invocable1, &Invocable2, &Invocable3, &Invocable4, &Invocable5,
policy);
Invoke(&Invocable1, &Invocable2, &Invocable3, &Invocable4, &Invocable5,
&Invocable6, policy);
Invoke(&Invocable1, &Invocable2, &Invocable3, &Invocable4, &Invocable5,
&Invocable6, &Invocable7, policy);
Invoke(&Invocable1, &Invocable2, &Invocable3, &Invocable4, &Invocable5,
&Invocable6, &Invocable7, &Invocable8, policy);
Invoke(&Invocable1, &Invocable2, &Invocable3, &Invocable4, &Invocable5,
&Invocable6, &Invocable7, &Invocable8, &Invocable9, policy);
Invoke(&Invocable1, &Invocable2, &Invocable3, &Invocable4, &Invocable5,
&Invocable6, &Invocable7, &Invocable8, &Invocable9, policy);
Invoke(&Invocable1, &Invocable2, &Invocable3, &Invocable4, &Invocable5,
&Invocable6, &Invocable7, &Invocable8, &Invocable9, &Invocable10,
policy);
}
......@@ -83,8 +83,8 @@ int embb_condition_wait_until(embb_condition_t* condition_var,
embb_time_t now;
embb_time_now(&now);
/* Check if absolute timepoint (in milliseconds) still is in the future */
if (time->seconds * 1000 + time->nanoseconds / 1000000
- now.seconds * 1000 - now.nanoseconds / 1000000 > 0) {
if ((time->seconds * 1000 + time->nanoseconds / 1000000)
> (now.seconds * 1000 + now.nanoseconds / 1000000)) {
/* Convert to (unsigned type) milliseconds and round up */
DWORD time_diff = (DWORD) (
time->seconds * 1000 + time->nanoseconds / 1000000
......
......@@ -128,6 +128,20 @@ void embb_internal_thread_index_set_max(unsigned int max) {
*embb_max_number_thread_indices() = max;
}
/**
* \pre the calling thread is the only active thread
*
* \post the thread indices count and calling thread index is reset
*/
void embb_internal_thread_index_reset() {
/** This function is only called in tests, usually when all other threads
* except the main thread have terminated. However, the main thread still has
* potentially stored its old index value in its thread local storage,
* which might be assigned additionally to another thread (as the counter is
* reset), which may lead to hard to detect bugs. Therefore, reset the thread
* local thread id here.
*/
embb_internal_thread_index_var = UINT_MAX;
embb_counter_init(embb_thread_index_counter());
}
}
\ No newline at end of file
......@@ -38,7 +38,7 @@ ConditionVarTest::ConditionVarTest()
embb_condition_init(&cond_wait_);
embb_mutex_init(&mutex_cond_wait_, EMBB_MUTEX_PLAIN);
CreateUnit("Timed wait timouts")
CreateUnit("Timed wait timeouts")
.Add(&ConditionVarTest::TestTimedWaitTimeouts, this);
if (num_threads_ >= 2) {
CreateUnit("Condition Notify Test")
......@@ -64,10 +64,10 @@ void ConditionVarTest::TestNotify() {
while (embb_counter_get(&counter_)
< static_cast<unsigned int>(num_threads_-1))
{} // all threads entered critical section
{} // All threads entered critical section
embb_mutex_lock(&mutex_cond_notify_);
embb_mutex_unlock(&mutex_cond_notify_);
// All threads called wait on the condition (Even last thread)
// All threads called wait on the condition (even last thread)
embb_counter_init(&counter_);
......@@ -75,7 +75,7 @@ void ConditionVarTest::TestNotify() {
embb_mutex_lock(&mutex_cond_wait_);
embb_condition_wait_for(&cond_wait_, &mutex_cond_wait_, &duration);
while (embb_counter_get(&counter_) == 0)
{} //if hangs here signal has not succeded
{} // If test hangs here, signalling has not succeeded
PT_ASSERT_EQ_MSG(embb_counter_get(&counter_), static_cast<unsigned int>(1),
"Only one thread notified");
......@@ -85,7 +85,7 @@ void ConditionVarTest::TestNotify() {
while (embb_counter_get(&counter_) !=
static_cast<unsigned int>(num_threads_-1))
{} // If this hangs then not all threads were notified.
{} // If test hangs here, not all threads were notified
embb_mutex_unlock(&mutex_cond_wait_);
embb_mutex_destroy(&mutex_cond_wait_);
......@@ -105,13 +105,13 @@ void ConditionVarTest::TestTimedWaitTimeouts() {
embb_time_t time;
embb_duration_t duration = EMBB_DURATION_INIT;
// Wait for now tests already passed time point
// Wait for "now" tests already passed time point
embb_time_now(&time);
embb_mutex_lock(&mutex);
int status = embb_condition_wait_until(&cond, &mutex, &time);
PT_EXPECT_EQ(status, EMBB_TIMEDOUT);
// Wait for a future timepoint
// Wait for a future time point
status = embb_duration_set_milliseconds(&duration, 1);
PT_EXPECT_EQ(status, EMBB_SUCCESS);
status = embb_time_in(&time, &duration); // Time now
......
......@@ -36,6 +36,9 @@ namespace test {
TimeTest::TimeTest() {
CreateUnit("Time in duration").Add(&TimeTest::TestTimeInDuration, this);
CreateUnit("Monotonicity").Add(
&TimeTest::TestMonotonicity, this,
1, partest::TestSuite::GetDefaultNumIterations() * 10);
}
void TimeTest::TestTimeInDuration() {
......@@ -48,6 +51,20 @@ void TimeTest::TestTimeInDuration() {
PT_EXPECT_EQ(status, EMBB_SUCCESS);
}
void TimeTest::TestMonotonicity() {
embb_time_t first;
embb_time_t second;
int status1 = embb_time_in(&first, embb_duration_zero());
int status2 = embb_time_in(&second, embb_duration_zero());
PT_EXPECT_EQ(status1, EMBB_SUCCESS);
PT_EXPECT_EQ(status2, EMBB_SUCCESS);
unsigned long long first_abs = first.seconds * 1000 +
first.nanoseconds / 1000000;
unsigned long long second_abs = second.seconds * 1000 +
second.nanoseconds / 1000000;
PT_EXPECT_GE(second_abs, first_abs);
}
} // namespace test
} // namespace base
} // namespace embb
......@@ -42,9 +42,14 @@ class TimeTest : public partest::TestCase {
private:
/**
* Tests time in duration method.
* Tests time-in-duration method.
*/
void TestTimeInDuration();
/**
* Tests that succeedingly taken times are monotonously increasing.
*/
void TestMonotonicity();
};
} // namespace test
......
......@@ -478,7 +478,7 @@ class Atomic<BaseType*> : public embb::base::internal::atomic::
public:
Atomic() : embb::base::internal::atomic::
AtomicPointer<BaseType, ptrdiff_t, sizeof(BaseType*)>() {}
Atomic(BaseType* p) : embb::base::internal::atomic::
explicit Atomic(BaseType* p) : embb::base::internal::atomic::
AtomicPointer<BaseType, ptrdiff_t, sizeof(BaseType*)>(p) {}
BaseType* operator=(BaseType* p) {
......
......@@ -177,8 +177,7 @@ CompareAndSwap(BaseType& expected, BaseType desired) {
compare_and_swap(&AtomicValue, &native_expected, native_desired)) !=0
? true : false;
if (!return_val)
expected = Load();
memcpy(&expected, &native_expected, sizeof(expected));
return return_val;
}
......
......@@ -65,8 +65,8 @@ class AtomicPointer : public AtomicArithmetic<BaseType*, DifferenceType, S> {
bool IsPointer() const;
// The methods below are documented in atomic.h
BaseType* operator->();
BaseType& operator*();
BaseType* operator->() const;
BaseType& operator*() const;
};
template<typename BaseType, typename DifferenceType, size_t S>
......@@ -93,13 +93,13 @@ inline bool AtomicPointer<BaseType, DifferenceType, S>::
template<typename BaseType, typename DifferenceType, size_t S>
inline BaseType* AtomicPointer<BaseType, DifferenceType, S>::
operator->() {
operator->() const {
return this->Load();
}
template<typename BaseType, typename DifferenceType, size_t S>
inline BaseType& AtomicPointer<BaseType, DifferenceType, S>::
operator*() {
operator*() const {
return *(this->Load());
}
......
......@@ -28,6 +28,7 @@
#define EMBB_BASE_INTERNAL_MUTEX_INL_H_
#include <cassert>
#include <algorithm>
namespace embb {
namespace base {
......@@ -95,8 +96,8 @@ void UniqueLock<Mutex>::Unlock() {
template<typename Mutex>
void UniqueLock<Mutex>::Swap(UniqueLock<Mutex>& other) {
locked_ = other.locked_;
mutex_ = other.Release();
std::swap(mutex_, other.mutex_);
std::swap(locked_, other.locked_);
}
template<typename Mutex>
......
......@@ -439,11 +439,11 @@ class UniqueLock {
void Unlock();
/**
* Transfers ownership of a mutex to this lock.
* Exchanges ownership of the wrapped mutex with another lock.
*/
void Swap(
UniqueLock<Mutex>& other
/**< [IN/OUT] Lock from which ownership shall be transferred */
/**< [IN/OUT] The lock to exchange ownership with */
);
/**
......
......@@ -191,13 +191,21 @@ void MutexTest::TestUniqueLock() {
}
{ // Test lock swapping
UniqueLock<> lock1;
UniqueLock<> lock2(mutex_);
PT_EXPECT_EQ(lock1.OwnsLock(), false);
PT_EXPECT_EQ(lock2.OwnsLock(), true);
lock1.Swap(lock2);
UniqueLock<> lock1(mutex_);
PT_EXPECT_EQ(lock1.OwnsLock(), true);
PT_EXPECT_EQ(lock2.OwnsLock(), false);
{
UniqueLock<> lock2;
PT_EXPECT_EQ(lock2.OwnsLock(), false);
lock1.Swap(lock2);
PT_EXPECT_EQ(lock1.OwnsLock(), false);
PT_EXPECT_EQ(lock2.OwnsLock(), true);
}
// At this point, "lock2" was destroyed and "mutex_" must be unlocked.
UniqueLock<> lock3(mutex_, embb::base::try_lock);
PT_EXPECT_EQ(lock3.OwnsLock(), true);
}
}
......
......@@ -77,7 +77,12 @@ LockFreeMPMCQueue<Type, ValuePool>::~LockFreeMPMCQueue() {
template< typename Type, typename ValuePool >
LockFreeMPMCQueue<Type, ValuePool>::LockFreeMPMCQueue(size_t capacity) :
capacity(capacity),
capacity(capacity),
// Object pool, size with respect to the maximum number of retired nodes not
// eligible for reuse. +1 for dummy node.
objectPool(
MPMCQueueNodeHazardPointer_t::ComputeMaximumRetiredObjectCount(2) +
capacity + 1),
// Disable "this is used in base member initializer" warning.
// We explicitly want this.
#ifdef EMBB_PLATFORM_COMPILER_MSVC
......@@ -89,13 +94,7 @@ delete_pointer_callback(*this,
#ifdef EMBB_PLATFORM_COMPILER_MSVC
#pragma warning(pop)
#endif
hazardPointer(delete_pointer_callback, NULL, 2),
// Object pool, size with respect to the maximum number of retired nodes not
// eligible for reuse. +1 for dummy node.
objectPool(
hazardPointer.GetRetiredListMaxSize()*
embb::base::Thread::GetThreadsMaxCount() +
capacity + 1) {
hazardPointer(delete_pointer_callback, NULL, 2) {
// Allocate dummy node to reduce the number of special cases to consider.
internal::LockFreeMPMCQueueNode<Type>* dummyNode = objectPool.Allocate();
// Initially, head and tail point to the dummy node.
......@@ -120,7 +119,7 @@ bool LockFreeMPMCQueue<Type, ValuePool>::TryEnqueue(Type const& element) {
for (;;) {
my_tail = tail;
hazardPointer.GuardPointer(0, my_tail);
hazardPointer.Guard(0, my_tail);
// Check if pointer is still valid after guarding.
if (my_tail != tail) {
......@@ -163,12 +162,12 @@ bool LockFreeMPMCQueue<Type, ValuePool>::TryDequeue(Type & element) {
Type data;
for (;;) {
my_head = head;
hazardPointer.GuardPointer(0, my_head);
hazardPointer.Guard(0, my_head);
if (my_head != head) continue;
my_tail = tail;
my_next = my_head->GetNext();
hazardPointer.GuardPointer(1, my_next);
hazardPointer.Guard(1, my_next);
if (head != my_head) continue;
if (my_next == NULL)
......@@ -187,7 +186,7 @@ bool LockFreeMPMCQueue<Type, ValuePool>::TryDequeue(Type & element) {
break;
}
hazardPointer.EnqueuePointerForDeletion(my_head);
hazardPointer.EnqueueForDeletion(my_head);
element = data;
return true;
}
......
......@@ -81,13 +81,12 @@ capacity(capacity),
#ifdef EMBB_PLATFORM_COMPILER_MSVC
#pragma warning(pop)
#endif
hazardPointer(delete_pointer_callback, NULL, 1),
// Object pool, size with respect to the maximum number of retired nodes not
// eligible for reuse:
objectPool(
hazardPointer.GetRetiredListMaxSize()*
embb::base::Thread::GetThreadsMaxCount() +
capacity) {
StackNodeHazardPointer_t::ComputeMaximumRetiredObjectCount(1) +
capacity),
hazardPointer(delete_pointer_callback, NULL, 1) {
}
template< typename Type, typename ValuePool >
......@@ -128,7 +127,7 @@ bool LockFreeStack< Type, ValuePool >::TryPop(Type & element) {
return false;
// Guard top_cached
hazardPointer.GuardPointer(0, top_cached);
hazardPointer.Guard(0, top_cached);
// Check if top is still top. If this is the case, it has not been
// retired yet (because before retiring that thing, the retiring thread
......@@ -144,16 +143,16 @@ bool LockFreeStack< Type, ValuePool >::TryPop(Type & element) {
break;
} else {
// We continue with the next and can unguard top_cached
hazardPointer.GuardPointer(0, NULL);
hazardPointer.Guard(0, NULL);
}
}
Type data = top_cached->GetElement();
// We don't need to read from this reference anymore, unguard it
hazardPointer.GuardPointer(0, NULL);
hazardPointer.Guard(0, NULL);
hazardPointer.EnqueuePointerForDeletion(top_cached);
hazardPointer.EnqueueForDeletion(top_cached);
element = data;
return true;
......
......@@ -42,7 +42,7 @@ template<typename Type, Type Undefined, class PoolAllocator,
class TreeAllocator >
bool LockFreeTreeValuePool<Type, Undefined, PoolAllocator, TreeAllocator>::
IsLeaf(int node) {
if (node >= size - 1 && node <= 2 * size - 1) {
if (node >= size_ - 1 && node <= 2 * size_ - 1) {
return true;
}
return false;
......@@ -52,7 +52,7 @@ template<typename Type, Type Undefined, class PoolAllocator,
class TreeAllocator >
bool LockFreeTreeValuePool<Type, Undefined, PoolAllocator, TreeAllocator>::
IsValid(int node) {
return (node >= 0 && node <= 2 * size - 1);
return (node >= 0 && node <= 2 * size_ - 1);
}
template<typename Type, Type Undefined, class PoolAllocator,
......@@ -77,14 +77,14 @@ template<typename T, T Undefined, class PoolAllocator, class TreeAllocator >
int LockFreeTreeValuePool<T, Undefined, PoolAllocator, TreeAllocator>::
NodeIndexToPoolIndex(int node) {
assert(IsLeaf(node));
return(node - (size - 1));
return(node - (size_ - 1));
}
template<typename Type, Type Undefined, class PoolAllocator,
class TreeAllocator >
int LockFreeTreeValuePool<Type, Undefined, PoolAllocator, TreeAllocator>::
PoolIndexToNodeIndex(int index) {
int node = index + (size - 1);
int node = index + (size_ - 1);
assert(IsLeaf(node));
return node;
}
......@@ -100,7 +100,7 @@ template<typename T, T Undefined, class PoolAllocator, class TreeAllocator >
int LockFreeTreeValuePool<T, Undefined, PoolAllocator, TreeAllocator>::
GetParentNode(int node) {
int parent = (node - 1) / 2;
assert(parent >= 0 && parent < size - 1);
assert(parent >= 0 && parent < size_ - 1);
return parent;
}
......@@ -112,11 +112,11 @@ allocate_rec(int node, Type& element) {
if (IsLeaf(node)) {
int pool_index = NodeIndexToPoolIndex(node);
Type expected = pool[pool_index];
Type expected = pool_[pool_index];
if (expected == Undefined)
return -1;
if (pool[pool_index].CompareAndSwap(expected, Undefined)) {
if (pool_[pool_index].CompareAndSwap(expected, Undefined)) {
element = expected;
return pool_index;
}
......@@ -131,11 +131,11 @@ allocate_rec(int node, Type& element) {
// atomically decrement the value in the node if the result is greater than
// or equal to zero. This cannot be done atomically.
do {
current = tree[node];
current = tree_[node];
desired = current - 1;
if (desired < 0)
return -1;
} while (!tree[node].CompareAndSwap(current, desired));
} while (!tree_[node].CompareAndSwap(current, desired));
int leftResult = allocate_rec(GetLeftChildIndex(node), element);
if (leftResult != -1) {
......@@ -156,7 +156,7 @@ Fill(int node, int elementsToStore, int power2Value) {
if (IsLeaf(node))
return;
tree[node] = elementsToStore;
tree_[node] = elementsToStore;
int postPower2Value = power2Value >> 1;
......@@ -188,14 +188,14 @@ Free(Type element, int index) {
assert(element != Undefined);
// Put the element back
pool[index].Store(element);
pool_[index].Store(element);
assert(index >= 0 && index < size);
assert(index >= 0 && index < size_);
int node = PoolIndexToNodeIndex(index);
while (!IsRoot(node)) {
node = GetParentNode(node);
tree[node].FetchAndAdd(1);
tree_[node].FetchAndAdd(1);
}
}
......@@ -205,37 +205,76 @@ template< typename ForwardIterator >
LockFreeTreeValuePool<Type, Undefined, PoolAllocator, TreeAllocator>::
LockFreeTreeValuePool(ForwardIterator first, ForwardIterator last) {
// Number of elements to store
real_size = static_cast<int>(::std::distance(first, last));
real_size_ = static_cast<int>(::std::distance(first, last));
// Let k be smallest number so that real_size <= 2^k, size = 2^k
size = GetSmallestPowerByTwoValue(real_size);
size_ = GetSmallestPowerByTwoValue(real_size_);
// Size of binary tree without the leaves
tree_size = size - 1;
tree_size_ = size_ - 1;
// make sure, signed values are not negative
assert(tree_size_ >= 0);
assert(real_size_ >= 0);
size_t tree_size_unsigned = static_cast<size_t>(tree_size_);
size_t real_size_unsigned = static_cast<size_t>(real_size_);
// Pool stores elements of type T
pool = poolAllocator.allocate(static_cast<size_t>(real_size));
pool_ = pool_allocator_.allocate(real_size_unsigned);
// invoke inplace new for each pool element
for (size_t i = 0; i != real_size_unsigned; ++i) {
new (&pool_[i]) embb::base::Atomic<Type>();
}
// Tree holds the counter of not allocated elements
tree = treeAllocator.allocate(static_cast<size_t>(tree_size));
tree_ = tree_allocator_.allocate(tree_size_unsigned);
// invoke inplace new for each tree element
for (size_t i = 0; i != tree_size_unsigned; ++i) {
new (&tree_[i]) embb::base::Atomic<int>();
}
int i = 0;
// Store the elements from the range
for (ForwardIterator curIter(first); curIter != last; ++curIter) {
pool[i++] = *curIter;
pool_[i++] = *curIter;
}
// Initialize the binary tree without leaves (counters)
Fill(0, static_cast<int>(::std::distance(first, last)), size);
Fill(0, static_cast<int>(::std::distance(first, last)), size_);
}
template<typename Type, Type Undefined, class PoolAllocator,
class TreeAllocator >
LockFreeTreeValuePool<Type, Undefined, PoolAllocator, TreeAllocator>::
~LockFreeTreeValuePool() {
poolAllocator.deallocate(pool, static_cast<size_t>(real_size));
treeAllocator.deallocate(tree, static_cast<size_t>(tree_size));
size_t tree_size_unsigned = static_cast<size_t>(tree_size_);
size_t real_size_unsigned = static_cast<size_t>(real_size_);
// invoke destructor for each pool element
for (size_t i = 0; i != real_size_unsigned; ++i) {
pool_[i].~Atomic();
}
pool_allocator_.deallocate(pool_, real_size_unsigned);
// invoke destructor for each tree element
for (size_t i = 0; i != tree_size_unsigned; ++i) {
tree_[i].~Atomic();
}
tree_allocator_.deallocate(tree_, tree_size_unsigned);
}
template<typename Type, Type Undefined, class PoolAllocator,
class TreeAllocator >
size_t LockFreeTreeValuePool<Type, Undefined, PoolAllocator, TreeAllocator>::
GetMinimumElementCountForGuaranteedCapacity(size_t capacity) {
// for this value pool, this is just capacity...
return capacity;
}
} // namespace containers
......
......@@ -83,7 +83,8 @@ ReturningTrueIterator::operator!=(const self_type& rhs) {
template<class Type, typename ValuePool, class ObjectAllocator>
bool ObjectPool<Type, ValuePool, ObjectAllocator>::
IsContained(const Type &obj) const {
if ((&obj < &objects[0]) || (&obj > &objects[capacity - 1])) {
if ((&obj < &objects_array_[0]) ||
(&obj > &objects_array_[value_pool_size_ - 1])) {
return false;
} else {
return true;
......@@ -94,17 +95,17 @@ template<class Type, typename ValuePool, class ObjectAllocator>
int ObjectPool<Type, ValuePool, ObjectAllocator>::
GetIndexOfObject(const Type &obj) const {
assert(IsContained(obj));
return(static_cast<int>(&obj - &objects[0]));
return(static_cast<int>(&obj - &objects_array_[0]));
}
template<class Type, typename ValuePool, class ObjectAllocator>
Type* ObjectPool<Type, ValuePool, ObjectAllocator>::AllocateRaw() {
bool val;
int allocated_index = p.Allocate(val);
int allocated_index = value_pool_.Allocate(val);
if (allocated_index == -1) {
return NULL;
} else {
Type* ret_pointer = &(objects[allocated_index]);
Type* ret_pointer = &(objects_array_[allocated_index]);
return ret_pointer;
}
......@@ -112,15 +113,17 @@ Type* ObjectPool<Type, ValuePool, ObjectAllocator>::AllocateRaw() {
template<class Type, typename ValuePool, class ObjectAllocator>
size_t ObjectPool<Type, ValuePool, ObjectAllocator>::GetCapacity() {
return capacity;
return capacity_;
}
template<class Type, typename ValuePool, class ObjectAllocator>
ObjectPool<Type, ValuePool, ObjectAllocator>::ObjectPool(size_t capacity) :
capacity(capacity),
p(ReturningTrueIterator(0), ReturningTrueIterator(capacity)) {
// Allocate the objects (without construction, just get the memory)
objects = objectAllocator.allocate(capacity);
capacity_(capacity),
value_pool_size_(
ValuePool::GetMinimumElementCountForGuaranteedCapacity(capacity)),
value_pool_(ReturningTrueIterator(0), ReturningTrueIterator(
value_pool_size_)),
objects_array_(object_allocator_.allocate(value_pool_size_)) {
}
template<class Type, typename ValuePool, class ObjectAllocator>
......@@ -128,7 +131,7 @@ void ObjectPool<Type, ValuePool, ObjectAllocator>::Free(Type* obj) {
int index = GetIndexOfObject(*obj);
obj->~Type();
p.Free(true, index);
value_pool_.Free(true, index);
}
template<class Type, typename ValuePool, class ObjectAllocator>
......@@ -189,7 +192,7 @@ Type* ObjectPool<Type, ValuePool, ObjectAllocator>::Allocate(
template<class Type, typename ValuePool, class ObjectAllocator>
ObjectPool<Type, ValuePool, ObjectAllocator>::~ObjectPool() {
// Deallocate the objects
objectAllocator.deallocate(objects, capacity);
object_allocator_.deallocate(objects_array_, value_pool_size_);
}
} // namespace containers
} // namespace embb
......
......@@ -35,21 +35,21 @@ Free(Type element, int index) {
assert(element != Undefined);
// Just put back the element
pool[index].Store(element);
pool_array_[index].Store(element);
}
template<typename Type, Type Undefined, class Allocator >
int WaitFreeArrayValuePool<Type, Undefined, Allocator>::
Allocate(Type & element) {
for (int i = 0; i != size; ++i) {
for (int i = 0; i != size_; ++i) {
Type expected;
// If the memory cell is not available, go ahead
if (Undefined == (expected = pool[i].Load()))
if (Undefined == (expected = pool_array_[i].Load()))
continue;
// Try to get the memory cell
if (pool[i].CompareAndSwap(expected, Undefined)) {
if (pool_array_[i].CompareAndSwap(expected, Undefined)) {
// When the CAS was successful, this element is ours
element = expected;
return i;
......@@ -64,23 +64,45 @@ WaitFreeArrayValuePool<Type, Undefined, Allocator>::
WaitFreeArrayValuePool(ForwardIterator first, ForwardIterator last) {
size_t dist = static_cast<size_t>(std::distance(first, last));
size = static_cast<int>(dist);
size_ = static_cast<int>(dist);
// conversion may result in negative number. check!
assert(size_ >= 0);
// Use the allocator to allocate an array of size dist
pool = allocator.allocate(dist);
pool_array_ = allocator_.allocate(dist);
// invoke inplace new for each pool element
for ( size_t i = 0; i != dist; ++i ) {
new (&pool_array_[i]) embb::base::Atomic<Type>();
}
int i = 0;
// Store the elements of the range
for (ForwardIterator curIter(first); curIter != last; ++curIter) {
pool[i++] = *curIter;
pool_array_[i++] = *curIter;
}
}
template<typename Type, Type Undefined, class Allocator >
WaitFreeArrayValuePool<Type, Undefined, Allocator>::~WaitFreeArrayValuePool() {
allocator.deallocate(pool, (size_t)size);
// invoke destructor for each pool element
for (int i = 0; i != size_; ++i) {
pool_array_[i].~Atomic();
}
// free memory
allocator_.deallocate(pool_array_, static_cast<size_t>(size_));
}
template<typename Type, Type Undefined, class Allocator >
size_t WaitFreeArrayValuePool<Type, Undefined, Allocator>::
GetMinimumElementCountForGuaranteedCapacity(size_t capacity) {
// for this value pool, this is just capacity...
return capacity;
}
} // namespace containers
} // namespace embb
......
......@@ -113,8 +113,17 @@ class LockFreeMPMCQueue {
* least as many elements, maybe more.
*/
size_t capacity;
// Do not change the ordering of class local variables.
// Important for initialization.
/**
* The object pool, used for lock-free memory allocation.
*
* Warning: the objectPool has to be initialized before the hazardPointer
* object, to be sure that the hazardPointer object is destructed before the
* Pool as the hazardPointer object might return elements to the pool in its
* destructor. So the ordering of the members objectPool and hazardPointer is
* important here!
*/
ObjectPool< internal::LockFreeMPMCQueueNode<Type>, ValuePool > objectPool;
/**
* Callback to the method that is called by hazard pointers if a pointer is
......@@ -124,15 +133,17 @@ class LockFreeMPMCQueue {
delete_pointer_callback;
/**
* The hazard pointer object, used for memory management.
* Definition of the used hazard pointer type
*/
embb::containers::internal::HazardPointer
< internal::LockFreeMPMCQueueNode<Type>* > hazardPointer;
typedef embb::containers::internal::HazardPointer
< internal::LockFreeMPMCQueueNode<Type>* >
MPMCQueueNodeHazardPointer_t;
/**
* The object pool, used for lock-free memory allocation.
* The hazard pointer object, used for memory management.
*/
ObjectPool< internal::LockFreeMPMCQueueNode<Type>, ValuePool > objectPool;
MPMCQueueNodeHazardPointer_t hazardPointer;
/**
* Atomic pointer to the head node of the queue
......
......@@ -187,11 +187,6 @@ class LockFreeStack {
delete_pointer_callback;
/**
* The hazard pointer object, used for memory management.
*/
internal::HazardPointer<internal::LockFreeStackNode<Type>*> hazardPointer;
/**
* The callback function, used to cleanup non-hazardous pointers.
* \see delete_pointer_callback
*/
......@@ -199,10 +194,27 @@ class LockFreeStack {
/**
* The object pool, used for lock-free memory allocation.
*
* Warning: the objectPool has to be initialized before the hazardPointer
* object, to be sure that the hazardPointer object is destructed before the
* Pool as the hazardPointer object might return elements to the pool in its
* destructor. So the ordering of the members objectPool and hazardPointer is
* important here!
*/
ObjectPool< internal::LockFreeStackNode<Type>, ValuePool > objectPool;
/**
* Definition of the used hazard pointer type
*/
typedef internal::HazardPointer < internal::LockFreeStackNode<Type>* >
StackNodeHazardPointer_t;
/**
* The hazard pointer object, used for memory management.
*/
StackNodeHazardPointer_t hazardPointer;
/**
* Atomic pointer to the top node of the stack (element that is popped next)
*/
embb::base::Atomic<internal::LockFreeStackNode<Type>*> top;
......
......@@ -123,22 +123,25 @@ class LockFreeTreeValuePool {
LockFreeTreeValuePool& operator=(const LockFreeTreeValuePool&);
// See algorithm description above
int size;
int size_;
// See algorithm description above
int tree_size;
int tree_size_;
// See algorithm description above
int real_size;
int real_size_;
// The tree above the pool
embb::base::Atomic<int>* tree;
embb::base::Atomic<int>* tree_;
// The actual pool
embb::base::Atomic<Type>* pool;
embb::base::Atomic<Type>* pool_;
PoolAllocator poolAllocator;
TreeAllocator treeAllocator;
// respective allocator
PoolAllocator pool_allocator_;
// respective allocator
TreeAllocator tree_allocator_;
/**
* Computes smallest power of two fitting the specified value
......@@ -278,6 +281,18 @@ class LockFreeTreeValuePool {
);
/**
* Due to concurrency effects, a pool might provide less elements than managed
* by it. However, usually one wants to guarantee a minimal capacity. The
* count of elements, that must be given to the pool when to guarantee \c
* capacity elements is computed using this function.
*
* \return count of indices the pool has to be initialized with
*/
static size_t GetMinimumElementCountForGuaranteedCapacity(
size_t capacity
/**< [IN] count of indices that shall be guaranteed */);
/**
* Destructs the pool.
*
* \notthreadsafe
......
......@@ -35,7 +35,6 @@
namespace embb {
namespace containers {
/**
* \defgroup CPP_CONTAINERS_POOLS Pools
* Concurrent pools
......@@ -62,22 +61,29 @@ class ObjectPool {
/**
* Allocator used to allocate elements of the object pool
*/
ObjectAllocator objectAllocator;
ObjectAllocator object_allocator_;
/**
* Array holding the allocated object
* Capacity of the object pool
*/
Type* objects;
size_t capacity_;
/**
* Capacity of the object pool
* The size of the underlying value pool. This is also the size of the object
* array in this class. It is assumed, that the valuepool manages indices in
* range [0;value_pool_size_-1].
*/
size_t capacity;
size_t value_pool_size_;
/**
* Underlying value pool
*/
ValuePool p;
ValuePool value_pool_;
/**
* Array holding the allocated object
*/
Type* objects_array_;
/**
* Helper providing a virtual iterator that just returns true in each
......
......@@ -39,12 +39,30 @@ namespace containers {
* \ingroup CPP_CONCEPT
* \{
* \par Description
* A value pool is a fixed-size multiset of elements, where each element has a
* unique index. The elements cannot be modified and are given at construction
* time (by providing first/last iterators). A value pool provides two
* operations: \c Allocate and \c Free. \c Allocate removes an element from the
* pool, and \c Free returns an element to the pool. It is only allowed to
* free elements that have previously been allocated.
* A value pool is a multi-set of elements, where each element has a unique,
* continuous (starting with 0) index. The elements cannot be modified and are
* given at construction time by providing first/last iterators.
*
* \par
* A value pool provides two primary operations: \c Allocate and \c Free. \c
* Allocate allocates an element/index "pair" (index via return, element via
* reference parameter) from the pool, and \c Free returns an element/index pair
* to the pool. To guarantee linearizability, \c element is not allowed to be
* modified between \c Allocate and \c Free. It is only allowed to free elements
* that have previously been allocated. The \c Allocate function does not
* guarantee an order on which indices are allocated. The count of elements that
* can be allocated with \c Allocate might be smaller than the count of
* elements, the pool is initialized with. This might be because of
* implementation details and respective concurrency effects: for example, if
* indices are managed within a queue, one has to protect queue elements from
* concurrency effects (reuse and access). As long as a thread potentially
* accesses a node (and with that an index), the respective index cannot not be
* given out to the user, even if being logically not part of the pool anymore.
* However, the user might want to guarantee a certain amount of indices to the
* user. Therefore, the static \c GetMinimumElementCountForGuaranteedCapacity
* method is used. The user passes the count of indices to this method, that
* shall be guaranteed by the pool. The method returns the count on indices, the
* pool has to be initialized with in order to guarantee this count on indices.
*
* \par Requirements
* - Let \c Pool be the pool class
......@@ -54,6 +72,7 @@ namespace containers {
* - Let \c i, j be forward iterators supporting \c std::distance.
* - Let \c c be an object of type \c Type&
* - Let \c e be a value of type \c int
* - Let \c f be a value of type \c int
*
* \par Valid Expressions
*
......@@ -72,7 +91,7 @@ namespace containers {
* the bottom element. The bottom element cannot be stored in the pool, it
* is exclusively used to mark empty cells. The pool initially contains
* \c std::distance(i, j) elements which are copied during construction from
* the range \c [i, j). A concrete class satisfying the value pool concept
* the range \c [i, j]. A concrete class satisfying the value pool concept
* might provide additional template parameters for specifying allocators.
* </td>
* </tr>
......@@ -80,9 +99,10 @@ namespace containers {
* <td>\code{.cpp} Allocate(c) \endcode</td>
* <td>\c int</td>
* <td>
* Gets an element from the pool. Returns -1, if no element is available,
* i.e., the pool is empty. Otherwise, returns the index of the element in
* the pool. The value of the pool element is written into reference \c c.
* Allocates an element/index "pair" from the pool. Returns -1, if no
* element is available, i.e., the pool is empty. Otherwise, returns the
* index of the element in the pool. The value of the pool element is
* written into parameter reference \c c.
* </td>
* </tr>
* <tr>
......@@ -93,6 +113,15 @@ namespace containers {
* \c Allocate. For each allocated element, \c Free must be called exactly
* once.</td>
* </tr>
* <tr>
* <td>\code{.cpp} GetMinimumElementCountForGuaranteedCapacity(f)
* \endcode</td>
* <td>\c void</td>
* <td>Static method, returns the count of indices, the user has to
* initialize the pool with in order to guarantee a count of \c f elements
* (irrespective of concurrency effects).
* </td>
* </tr>
* </table>
*
* \}
......@@ -116,10 +145,10 @@ template<typename Type,
class Allocator = embb::base::Allocator< embb::base::Atomic<Type> > >
class WaitFreeArrayValuePool {
private:
int size;
embb::base::Atomic<Type>* pool;
int size_;
embb::base::Atomic<Type>* pool_array_;
WaitFreeArrayValuePool();
Allocator allocator;
Allocator allocator_;
// Prevent copy-construction
WaitFreeArrayValuePool(const WaitFreeArrayValuePool&);
......@@ -150,6 +179,18 @@ class WaitFreeArrayValuePool {
);
/**
* Due to concurrency effects, a pool might provide less elements than managed
* by it. However, usually one wants to guarantee a minimal capacity. The
* count of elements, that must be given to the pool when to guarantee \c
* capacity elements is computed using this function.
*
* \return count of indices the pool has to be initialized with
*/
static size_t GetMinimumElementCountForGuaranteedCapacity(
size_t capacity
/**< [IN] count of indices that shall be guaranteed */);
/**
* Destructs the pool.
*
* \notthreadsafe
......@@ -175,7 +216,7 @@ class WaitFreeArrayValuePool {
* Returns an element to the pool.
*
* \note The element must have been allocated with Allocate().
*
*
* \waitfree
*
* \see CPP_CONCEPTS_VALUE_POOL
......
......@@ -36,32 +36,112 @@
namespace embb {
namespace containers {
namespace test {
class HazardPointerTest : public partest::TestCase {
/**
* @brief a very simple wait-free object pool implementation to have tests
* being independent of the EMBB object pool implementation.
*/
class IntObjectTestPool {
private:
embb::base::Function<void, embb::base::Atomic<int>*> delete_pointer_callback;
int* simplePoolObjects;
embb::base::Atomic<int>* simplePool;
//used to allocate random stuff, we will just use the pointers, not the
//contents
embb::containers::ObjectPool< embb::base::Atomic<int> >* object_pool;
public:
static const int ALLOCATED_MARKER = 1;
static const int FREE_MARKER = 0;
unsigned int poolSize;
//used to move pointer between threads
embb::containers::LockFreeStack< embb::base::Atomic<int>* >* stack;
embb::base::Mutex vector_mutex;
embb::containers::internal::HazardPointer<embb::base::Atomic<int>*>* hp;
std::vector< embb::base::Atomic<int>* > deleted_vector;
int n_threads;
int n_elements_per_thread;
int n_elements;
explicit IntObjectTestPool(unsigned int pool_size);
~IntObjectTestPool();
/**
* Allocate object from the pool
*
* @return the allocated object
*/
int* Allocate();
/**
* Return an element to the pool
*
* @param objectPointer the object to be freed
*/
void Release(int* object_pointer);
};
class HazardPointerTest : public partest::TestCase {
public:
/**
* Adds test methods.
*/
HazardPointerTest();
void HazardPointerTest1_Pre();
void HazardPointerTest1_Post();
void HazardPointerTest1_ThreadMethod();
void HazardPointerTest1Pre();
void HazardPointerTest1Post();
void HazardPointerTest1ThreadMethod();
void DeletePointerCallback(embb::base::Atomic<int>* to_delete);
private:
embb::base::Function<void, embb::base::Atomic<int>*> delete_pointer_callback_;
//used to allocate random stuff, we will just use the pointers, not the
//contents
embb::containers::ObjectPool< embb::base::Atomic<int> >* object_pool_;
//used to move pointer between threads
embb::containers::LockFreeStack< embb::base::Atomic<int>* >* stack_;
embb::base::Mutex vector_mutex_;
embb::containers::internal::HazardPointer<embb::base::Atomic<int>*>*
hazard_pointer_;
std::vector< embb::base::Atomic<int>* > deleted_vector_;
int n_threads_;
int n_elements_per_thread_;
int n_elements_;
};
class HazardPointerTest2 : public partest::TestCase {
public:
void DeletePointerCallback(int* to_delete);
bool SetRelativeGuards();
void HazardPointerTest2Master();
void HazardPointerTest2Slave();
void HazardPointerTest2Pre();
void HazardPointerTest2Post();
void HazardPointerTest2ThreadMethod();
HazardPointerTest2();
private:
// number of threads, participating in that test
int n_threads;
embb::base::Function<void, int*> delete_pointer_callback_;
// the thread id of the master
embb::base::Atomic<unsigned int> current_master_;
// variables, to synchronize threads. At each point in time, one master,
// the master changes each round until each thread was assigned master once.
embb::base::Atomic<int> sync1_;
embb::base::Atomic<unsigned int> sync2_;
unsigned int guards_per_phread_count_;
unsigned int guaranteed_capacity_pool_;
unsigned int pool_size_using_hazard_pointer_;
// The threads write here, if they guarded an object successfully. Used to
// determine when all allocated objects were guarded successfully.
embb::base::Atomic<int*>* shared_guarded_;
// This array is used by the master, to communicate and share what he has
// allocated with the slaves.
embb::base::Atomic<int*>* shared_allocated_;
// Reference to the object pool
IntObjectTestPool* test_pool_;
embb::containers::internal::HazardPointer<int*>* hazard_pointer_;
static const int FINISH_MARKER = -1;
};
} // namespace test
} // namespace containers
......
......@@ -55,6 +55,7 @@ using embb::containers::test::HazardPointerTest;
using embb::containers::test::QueueTest;
using embb::containers::test::StackTest;
using embb::containers::test::ObjectPoolTest;
using embb::containers::test::HazardPointerTest2;
PT_MAIN("Data Structures C++") {
unsigned int max_threads = static_cast<unsigned int>(
......@@ -64,6 +65,7 @@ PT_MAIN("Data Structures C++") {
PT_RUN(PoolTest< WaitFreeArrayValuePool<int COMMA -1> >);
PT_RUN(PoolTest< LockFreeTreeValuePool<int COMMA -1> >);
PT_RUN(HazardPointerTest);
PT_RUN(HazardPointerTest2);
PT_RUN(QueueTest< WaitFreeSPSCQueue< ::std::pair<size_t COMMA int> > >);
PT_RUN(QueueTest< LockFreeMPMCQueue< ::std::pair<size_t COMMA int> >
COMMA true COMMA true >);
......
......@@ -39,7 +39,7 @@
#define NUM_SLICES 8
#define TEST_COUNT 12
typedef embb::dataflow::Network<8> MyNetwork;
typedef embb::dataflow::Network<NUM_SLICES> MyNetwork;
typedef MyNetwork::ConstantSource< int > MyConstantSource;
typedef MyNetwork::Source< int > MySource;
typedef MyNetwork::SerialProcess< MyNetwork::Inputs<int>::Type,
......@@ -156,9 +156,7 @@ void SimpleTest::TestBasic() {
core_set,
1024, // max tasks (default: 1024)
128, // max groups (default: 128)
// Currently needs to be initialized
// with (max_queues + 1), see defect embb449
num_cores + 1, // max queues (default: 16)
num_cores, // max queues (default: 16)
1024, // queue capacity (default: 1024)
4); // num priorities (default: 4)
......
......@@ -71,7 +71,7 @@ mtapi_uint_t embb_mtapi_id_pool_allocate(embb_mtapi_id_pool_t * that) {
/* acquire position to fetch id from */
mtapi_uint_t id_position = that->get_id_position;
that->get_id_position++;
if (that->capacity <= that->get_id_position) {
if (that->capacity < that->get_id_position) {
that->get_id_position = 0;
}
......@@ -97,7 +97,7 @@ void embb_mtapi_id_pool_deallocate(
/* acquire position to put id to */
mtapi_uint_t id_position = that->put_id_position;
that->put_id_position++;
if (that->capacity <= that->put_id_position) {
if (that->capacity < that->put_id_position) {
that->put_id_position = 0;
}
......
/*
* Copyright (c) 2014-2015, Siemens AG. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <embb_mtapi_test_id_pool.h>
#include <vector>
IdPoolTest::IdPoolTest() {
CreateUnit("mtapi id pool test single threaded").
Add(&IdPoolTest::TestBasic, this, 1, 1000).
Pre(&IdPoolTest::TestBasicPre, this).
Post(&IdPoolTest::TestBasicPost, this);
CreateUnit("mtapi id pool test concurrent").
Add(&IdPoolTest::TestParallel, this, concurrent_accessors_id_pool_2
, 20).
Post(&IdPoolTest::TestParallelPost, this).
Pre(&IdPoolTest::TestParallelPre, this);
}
void IdPoolTest::TestParallel() {
// allocate ID_ELEMENTS_PER_ACCESSOR elements. Each test thread is
// guaranteed to be able to allocate this amount of elements.
TestAllocateDeallocateNElementsFromPool(id_pool_parallel,
id_elements_per_accessor);
}
void IdPoolTest::TestParallelPre() {
// create second id pool with CONCURRENT_ACCESSORS_ID_POOL_2*
// ID_ELEMENTS_PER_ACCESSOR elements
embb_mtapi_id_pool_initialize(&id_pool_parallel,
concurrent_accessors_id_pool_2*id_elements_per_accessor);
}
void IdPoolTest::TestParallelPost() {
// after the parallel tests, try to again allocate and deallocate all
// elements sequentially.
TestAllocateDeallocateNElementsFromPool(id_pool_parallel,
concurrent_accessors_id_pool_2*id_elements_per_accessor, true);
// finalize pool
embb_mtapi_id_pool_finalize(&id_pool_parallel);
}
void IdPoolTest::TestBasic() {
TestAllocateDeallocateNElementsFromPool(id_pool, id_pool_size_1, true);
}
void IdPoolTest::TestBasicPre() {
// create id pool with ID_POOL_SIZE_1 elements
embb_mtapi_id_pool_initialize(&id_pool, id_pool_size_1);
}
void IdPoolTest::TestBasicPost() {
// finalize pool
embb_mtapi_id_pool_finalize(&id_pool);
}
void IdPoolTest::TestAllocateDeallocateNElementsFromPool(
embb_mtapi_id_pool_t &pool,
int count_elements,
bool empty_check) {
std::vector<unsigned int> allocated;
for (int i = 0; i != count_elements; ++i) {
allocated.push_back(embb_mtapi_id_pool_allocate(&pool));
}
// the allocated elements should be disjunctive, and never invalid element
for (unsigned int x = 0; x != allocated.size(); ++x) {
PT_ASSERT(allocated[x] != EMBB_MTAPI_IDPOOL_INVALID_ID);
for (unsigned int y = 0; y != allocated.size(); ++y) {
if (x == y) {
continue;
}
PT_ASSERT(allocated[x] != allocated[y]);
}
}
// now the id pool should be empty... try ten times to get an id,
// we should always get the invalid element
if (empty_check) {
for (int i = 0; i != 10; ++i) {
PT_ASSERT_EQ(embb_mtapi_id_pool_allocate(&pool),
static_cast<unsigned int>(EMBB_MTAPI_IDPOOL_INVALID_ID)
)
}
}
// now return allocated elements in a shuffled manner.
::std::random_shuffle(allocated.begin(), allocated.end());
for (int i = 0; i != count_elements; ++i) {
embb_mtapi_id_pool_deallocate(&pool,
allocated[static_cast<unsigned int>(i)]);
}
}
/*
* Copyright (c) 2014-2015, Siemens AG. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef MTAPI_C_TEST_EMBB_MTAPI_TEST_ID_POOL_H_
#define MTAPI_C_TEST_EMBB_MTAPI_TEST_ID_POOL_H_
#include <partest/partest.h>
#include <embb_mtapi_id_pool_t.h>
// for shuffling a vector
#include <algorithm>
class IdPoolTest : public partest::TestCase {
public:
embb_mtapi_id_pool_t id_pool;
embb_mtapi_id_pool_t id_pool_parallel;
IdPoolTest();
private:
static const unsigned int id_pool_size_1 = 100;
static const unsigned int concurrent_accessors_id_pool_2 = 10;
static const unsigned int id_elements_per_accessor = 10;
/**
* We create a pool of size number_accessors*elements_per_accessor, so
* at each time we can guarantee each thread to be able to allocate
* elements_per_accessor elements.
* We create number_accessor threads, where each thread iteratively
* allocates and frees elements_per_accessor elements, which in each case
* has to be successful. Additionally, the sanity checks from the basic tests
* are repeated. The TestParallelPost function also repeats all
* sequential tests.
*/
void TestParallel();
void TestParallelPre();
void TestParallelPost();
/**
* Create a pool of size N. We repeatedly allocate and free N elements, check
* if the pool always returns disjunctive ids and check that the pool never
* returns the invalid element, if the pool is not empty. Check that the
* invalid element is returned if the pool is empty.
*/
void TestBasic();
void TestBasicPre();
void TestBasicPost();
static void TestAllocateDeallocateNElementsFromPool(
embb_mtapi_id_pool_t &pool,
int count_elements,
bool empty_check = false);
};
#endif // MTAPI_C_TEST_EMBB_MTAPI_TEST_ID_POOL_H_
......@@ -37,6 +37,9 @@
#include <embb_mtapi_test_group.h>
#include <embb_mtapi_test_queue.h>
#include <embb_mtapi_test_error.h>
#include <embb_mtapi_test_id_pool.h>
#include <embb/base/c/memory_allocation.h>
PT_MAIN("MTAPI C") {
embb_log_set_log_level(EMBB_LOG_LEVEL_NONE);
......@@ -48,4 +51,7 @@ PT_MAIN("MTAPI C") {
PT_RUN(InitFinalizeTest);
PT_RUN(GroupTest);
PT_RUN(QueueTest);
PT_RUN(IdPoolTest);
PT_EXPECT(embb_get_bytes_allocated() == 0);
}
......@@ -5,14 +5,10 @@ file(GLOB_RECURSE EMBB_MTAPI_CPP_HEADERS "include/*.h")
file(GLOB_RECURSE EMBB_MTAPI_CPP_TEST_SOURCES "test/*.cc" "test/*.h")
if (USE_AUTOMATIC_INITIALIZATION STREQUAL ON)
message("-- Automatic initialization enabled (default)")
set(MTAPI_CPP_AUTOMATIC_INITIALIZE 1)
else()
set(MTAPI_CPP_AUTOMATIC_INITIALIZE 0)
message("-- Automatic initialization disabled")
endif()
message(" (set with command line option -DUSE_AUTOMATIC_INITIALIZATION=ON/OFF)")
# Execute the GroupSources macro
include(${CMAKE_SOURCE_DIR}/CMakeCommon/GroupSourcesMSVC.cmake)
......
......@@ -5,13 +5,10 @@ file(GLOB_RECURSE EMBB_TASKS_CPP_HEADERS "include/*.h")
file(GLOB_RECURSE EMBB_TASKS_CPP_TEST_SOURCES "test/*.cc" "test/*.h")
if (USE_AUTOMATIC_INITIALIZATION STREQUAL ON)
message("-- Automatic initialization enabled (default)")
set(TASKS_CPP_AUTOMATIC_INITIALIZE 1)
else()
set(TASKS_CPP_AUTOMATIC_INITIALIZE 0)
message("-- Automatic initialization disabled")
endif()
message(" (set with command line option -DUSE_AUTOMATIC_INITIALIZATION=ON/OFF)")
configure_file("include/embb/tasks/internal/cmake_config.h.in"
"include/embb/tasks/internal/cmake_config.h")
......
......@@ -78,13 +78,19 @@ void TaskTest::TestBasic() {
PT_EXPECT_EQ(policy.GetPriority(), 0u);
policy.AddWorker(0u);
PT_EXPECT_EQ(policy.GetAffinity(), 1u);
policy.AddWorker(1u);
PT_EXPECT_EQ(policy.GetAffinity(), 3u);
if (policy.GetCoreCount() > 1) {
policy.AddWorker(1u);
PT_EXPECT_EQ(policy.GetAffinity(), 3u);
}
policy.RemoveWorker(0u);
PT_EXPECT_EQ(policy.GetAffinity(), 2u);
PT_EXPECT_EQ(policy.IsSetWorker(0), false);
PT_EXPECT_EQ(policy.IsSetWorker(1), true);
if (policy.GetCoreCount() > 1) {
PT_EXPECT_EQ(policy.GetAffinity(), 2u);
PT_EXPECT_EQ(policy.IsSetWorker(1), true);
}
std::string test;
embb::tasks::Task task = node.Spawn(
embb::base::Bind(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment