#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2023 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library 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 IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_SUPPORT_SOURCE 1 #include #include #include "ConcurrentBufferTest.h" #include namespace { // ------------------------------------------------------------------------------------------- // /// Shared ring buffer that uses a mutex for synchronization /// /// This should be slower than our atomics-using concurrent ring buffer, /// but we want to at least have for performance comparison. /// template class MutexRingBuffer { /// Initializes a new mutex-based shared ring buffer /// Number of items the ring buffer can hold public: MutexRingBuffer(std::size_t capacity) : items(capacity), firstItemIndex(0), itemCount(0), bufferMutex() {} /// Tries to append the specified element to the ring buffer /// Element that will be appended to the ring buffer /// True if the element was appended, false if there was no space left public: bool TryAppend(const TItem &element) { std::scoped_lock lockScope(this->bufferMutex); if(this->itemCount < this->items.size()) { std::size_t index = (this->firstItemIndex + this->itemCount) % this->items.size(); this->items[index] = element; ++this->itemCount; return true; } else { return false; } } /// Tries to remove an element from the queue /// Element into which the queue's element will be placed /// True if an item was available and return, false otherwise public: bool TryTake(TItem &element) { std::scoped_lock lockScope(this->bufferMutex); if(this->itemCount >= 1) { element = this->items[this->firstItemIndex]; this->firstItemIndex = (this->firstItemIndex + 1) % this->items.size(); --this->itemCount; return true; } else { return false; } } /// Returns the number of items currently stored in the buffer /// Number of items in the buffer public: std::size_t Count() const { std::scoped_lock lockScope(this->bufferMutex); return this->itemCount; } /// Returns the total number of items that the buffer can hold /// The capacity of the buffer public: std::size_t GetCapacity() const { return this->items.size(); } /// Vector used to hold the items of the ring buffer private: std::vector items; /// Index of the first item in the ring buffer private: std::size_t firstItemIndex; /// Number of items currently stored in the ring buffer private: std::size_t itemCount; /// Mutex used to synchronize threads accessing the ring buffer private: mutable std::mutex bufferMutex; }; // ------------------------------------------------------------------------------------------- // } // anonymous namespace namespace Nuclex { namespace Support { namespace Collections { // ------------------------------------------------------------------------------------------- // TEST(ConcurrentRingBufferTest_Mutex, InstancesCanBeCreated) { EXPECT_NO_THROW( MutexRingBuffer test(10); ); } // ------------------------------------------------------------------------------------------- // TEST(ConcurrentRingBufferTest_Mutex, CanReportCapacity) { MutexRingBuffer test(124); EXPECT_EQ(test.GetCapacity(), 124U); } // ------------------------------------------------------------------------------------------- // TEST(ConcurrentRingBufferTest_Mutex, SingleItemsCanBeAppended) { MutexRingBuffer test(10); EXPECT_TRUE(test.TryAppend(123)); EXPECT_TRUE(test.TryAppend(456)); EXPECT_TRUE(test.TryAppend(789)); } // ------------------------------------------------------------------------------------------- // TEST(ConcurrentRingBufferTest_Mutex, SingleAppendFailsIfBufferFull) { MutexRingBuffer test(3); EXPECT_TRUE(test.TryAppend(123)); EXPECT_TRUE(test.TryAppend(456)); EXPECT_TRUE(test.TryAppend(789)); EXPECT_FALSE(test.TryAppend(0)); } // ------------------------------------------------------------------------------------------- // TEST(ConcurrentRingBufferTest_Mutex, ItemsCanBeCounted) { MutexRingBuffer test(3); EXPECT_EQ(test.Count(), 0U); EXPECT_TRUE(test.TryAppend(123)); EXPECT_EQ(test.Count(), 1U); EXPECT_TRUE(test.TryAppend(456)); EXPECT_EQ(test.Count(), 2U); } // ------------------------------------------------------------------------------------------- // TEST(ConcurrentRingBufferTest_Mutex, ItemsCanBeCountedWhenFragmented) { MutexRingBuffer test(10); for(std::size_t index = 0; index < 8; ++index) { EXPECT_TRUE(test.TryAppend(12345)); } // Expected buffer state: ########-- EXPECT_EQ(test.Count(), 8U); for(std::size_t index = 0; index < 6; ++index) { int dummy; EXPECT_TRUE(test.TryTake(dummy)); } // Expected buffer state: ------##-- EXPECT_EQ(test.Count(), 2U); for(std::size_t index = 0; index < 4; ++index) { EXPECT_TRUE(test.TryAppend(12345)); } // Expected buffer state: ##----#### EXPECT_EQ(test.Count(), 6U); } // ------------------------------------------------------------------------------------------- // TEST(ConcurrentRingBufferTest_Mutex, ItemsStayOrderedWhenFragmented) { MutexRingBuffer test(10); for(std::size_t index = 0; index < 8; ++index) { EXPECT_TRUE(test.TryAppend(static_cast(index))); } // Expected buffer state: ########-- for(std::size_t index = 0; index < 6; ++index) { int value; EXPECT_TRUE(test.TryTake(value)); EXPECT_EQ(value, static_cast(index)); } // Expected buffer state: ------##-- for(std::size_t index = 0; index < 4; ++index) { EXPECT_TRUE(test.TryAppend(static_cast(index + 10))); } // Expected buffer state: ##----#### { int value; EXPECT_TRUE(test.TryTake(value)); EXPECT_EQ(value, 6); EXPECT_TRUE(test.TryTake(value)); EXPECT_EQ(value, 7); EXPECT_TRUE(test.TryTake(value)); EXPECT_EQ(value, 10); EXPECT_TRUE(test.TryTake(value)); EXPECT_EQ(value, 11); EXPECT_TRUE(test.TryTake(value)); EXPECT_EQ(value, 12); EXPECT_TRUE(test.TryTake(value)); EXPECT_EQ(value, 13); EXPECT_FALSE(test.TryTake(value)); } } // ------------------------------------------------------------------------------------------- // TEST(ConcurrentRingBufferTest_Mutex, BufferCanBeEmpty) { MutexRingBuffer test(5); int value; EXPECT_FALSE(test.TryTake(value)); // Starts out empty EXPECT_TRUE(test.TryAppend(100)); EXPECT_TRUE(test.TryTake(value)); EXPECT_FALSE(test.TryTake(value)); // Was emptied again with call above } // ------------------------------------------------------------------------------------------- // TEST(ConcurrentRingBufferTest_Mutex, SingleItemsCanBeRead) { MutexRingBuffer test(5); EXPECT_TRUE(test.TryAppend(123)); EXPECT_TRUE(test.TryAppend(456)); EXPECT_TRUE(test.TryAppend(789)); int value; EXPECT_TRUE(test.TryTake(value)); EXPECT_EQ(value, 123); EXPECT_TRUE(test.TryTake(value)); EXPECT_EQ(value, 456); EXPECT_TRUE(test.TryTake(value)); EXPECT_EQ(value, 789); EXPECT_FALSE(test.TryTake(value)); } // ------------------------------------------------------------------------------------------- // #if defined(NUCLEX_SUPPORT_ENABLE_BENCHMARKS) TEST(ConcurrentRingBufferTest_Mutex, BenchmarkAddingItems) { //TEST(ConcurrentRingBufferTest_SPSC, DISABLED_Benchmark) { benchmarkSingleItemAppends(); } #endif // defined(NUCLEX_SUPPORT_ENABLE_BENCHMARKS) // ------------------------------------------------------------------------------------------- // #if defined(NUCLEX_SUPPORT_ENABLE_BENCHMARKS) TEST(ConcurrentRingBufferTest_Mutex, BenchmarkTakingItems) { //TEST(ConcurrentRingBufferTest_SPSC, DISABLED_Benchmark) { benchmarkSingleItemTakes(); } #endif // defined(NUCLEX_SUPPORT_ENABLE_BENCHMARKS) // ------------------------------------------------------------------------------------------- // #if defined(NUCLEX_SUPPORT_ENABLE_BENCHMARKS) TEST(ConcurrentRingBufferTest_Mutex, BenchmarkMixedItems) { //TEST(ConcurrentRingBufferTest_SPSC, DISABLED_Benchmark) { benchmarkSingleItemMixed(); } #endif // defined(NUCLEX_SUPPORT_ENABLE_BENCHMARKS) // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Support::Collections