You've already forked mariadb-columnstore-engine
							
							
				mirror of
				https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
				synced 2025-10-31 18:30:33 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			222 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (C) 2024 MariaDB Corporation
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or
 | |
|    modify it under the terms of the GNU General Public License
 | |
|    as published by the Free Software Foundation; version 2 of
 | |
|    the License.
 | |
| 
 | |
|    This program 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 for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 | |
|    MA 02110-1301, USA. */
 | |
| #include <gtest/gtest.h>
 | |
| #include <atomic>
 | |
| #include <cstddef>
 | |
| #include <cstdint>
 | |
| #include <memory>
 | |
| #include <thread>
 | |
| 
 | |
| #include <boost/smart_ptr/allocate_shared_array.hpp>
 | |
| 
 | |
| #include "countingallocator.h"
 | |
| 
 | |
| using namespace allocators;
 | |
| 
 | |
| // Example class to be managed by the allocator
 | |
| struct TestClass
 | |
| {
 | |
|   int value[1024];
 | |
| 
 | |
|   TestClass(int val) : value{val}
 | |
|   {
 | |
|   }
 | |
| };
 | |
| 
 | |
| static const constexpr int64_t MemoryAllowance = 1 * 1024 * 1024;
 | |
| static const constexpr int64_t MemoryLimitStep = MemoryAllowance / 100;
 | |
| 
 | |
| // Test Fixture for AtomicCounterAllocator
 | |
| class CountingAllocatorTest : public ::testing::Test
 | |
| {
 | |
|  protected:
 | |
|   // Atomic counter to track allocated memory
 | |
|   std::atomic<int64_t> allocatedMemory{MemoryAllowance};
 | |
| 
 | |
|   // Custom allocator instance
 | |
|   CountingAllocator<TestClass> allocator;
 | |
| 
 | |
|   // Constructor
 | |
|   CountingAllocatorTest()
 | |
|    : allocatedMemory(MemoryAllowance)
 | |
|    , allocator(&allocatedMemory, MemoryAllowance / 100, MemoryAllowance / 100)
 | |
|   {
 | |
|   }
 | |
| 
 | |
|   // Destructor
 | |
|   ~CountingAllocatorTest() override = default;
 | |
| };
 | |
| 
 | |
| // Test 1: Allocation increases the counter correctly
 | |
| TEST_F(CountingAllocatorTest, Allocation)
 | |
| {
 | |
|   const std::size_t numObjects = 5;
 | |
|   TestClass* ptr = allocator.allocate(numObjects);
 | |
|   EXPECT_NE(ptr, nullptr);
 | |
|   if (MemoryLimitStep > numObjects * static_cast<int64_t>(sizeof(TestClass)))
 | |
|   {
 | |
|     EXPECT_EQ(allocatedMemory.load() - allocator.getCurrentLocalMemoryUsage(),
 | |
|               MemoryAllowance - numObjects * static_cast<int64_t>(sizeof(TestClass)));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     EXPECT_EQ(allocatedMemory.load(), MemoryAllowance - numObjects * static_cast<int64_t>(sizeof(TestClass)));
 | |
|   }
 | |
|   allocator.deallocate(ptr, numObjects);
 | |
| }
 | |
| 
 | |
| // Test 2: Deallocation decreases the counter correctly
 | |
| TEST_F(CountingAllocatorTest, Deallocation)
 | |
| {
 | |
|   const std::size_t numObjects = 3;
 | |
|   TestClass* ptr = allocator.allocate(numObjects);
 | |
|   if (MemoryLimitStep > numObjects * static_cast<int64_t>(sizeof(TestClass)))
 | |
|   {
 | |
|     EXPECT_EQ(allocatedMemory.load() - allocator.getCurrentLocalMemoryUsage(),
 | |
|               MemoryAllowance - numObjects * static_cast<int64_t>(sizeof(TestClass)));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     EXPECT_EQ(allocatedMemory.load(), MemoryAllowance - numObjects * static_cast<int64_t>(sizeof(TestClass)));
 | |
|   }
 | |
|   allocator.deallocate(ptr, numObjects);
 | |
|   EXPECT_EQ(allocatedMemory.load(), MemoryAllowance);
 | |
| }
 | |
| 
 | |
| // Test 3: Allocator equality based on shared counter
 | |
| TEST_F(CountingAllocatorTest, AllocatorEquality)
 | |
| {
 | |
|   CountingAllocator<TestClass> allocator1(&allocatedMemory);
 | |
|   CountingAllocator<TestClass> allocator2(&allocatedMemory);
 | |
|   EXPECT_TRUE(allocator1 == allocator2);
 | |
| 
 | |
|   std::atomic<int64_t> anotherCounter(0);
 | |
|   CountingAllocator<TestClass> allocator3(&anotherCounter);
 | |
|   EXPECT_FALSE(allocator1 == allocator3);
 | |
| }
 | |
| 
 | |
| // Test 4: Using allocator with std::allocate_shared
 | |
| TEST_F(CountingAllocatorTest, AllocateSharedUsesAllocator)
 | |
| {
 | |
|   // Create a shared_ptr using allocate_shared with the custom allocator
 | |
|   CountingAllocator<TestClass> allocatorSmallerStep(&allocatedMemory,
 | |
|                                                     MemoryAllowance / 1000, MemoryAllowance / 100);
 | |
|   std::shared_ptr<TestClass> ptr1 = std::allocate_shared<TestClass>(allocatorSmallerStep, 100);
 | |
|   std::shared_ptr<TestClass> ptr2 = std::allocate_shared<TestClass>(allocatorSmallerStep, 100);
 | |
|   std::shared_ptr<TestClass> ptr3 = std::allocate_shared<TestClass>(allocatorSmallerStep, 100);
 | |
| 
 | |
|   // Check that the counter has increased by the size of TestClass plus control block
 | |
|   // Exact size depends on the implementation, so we verify it's at least sizeof(TestClass)
 | |
|   EXPECT_LE(allocatedMemory.load(), MemoryAllowance - 3 * static_cast<int64_t>(sizeof(TestClass)));
 | |
| 
 | |
|   // Reset the shared_ptr and check that the counter decreases appropriately
 | |
|   ptr1.reset();
 | |
|   ptr2.reset();
 | |
|   ptr3.reset();
 | |
|   // After deallocation, the counter should return to zero
 | |
|   EXPECT_EQ(allocatedMemory.load(), MemoryAllowance);
 | |
| 
 | |
|   // auto deleter = [&allocatorSmallerStep](TestClass* ptr) { allocatorSmallerStep.deallocate(ptr, 1); };
 | |
|   // ptr1.reset(allocatorSmallerStep.allocate(3), deleter);
 | |
|   // EXPECT_LE(allocatedMemory.load(), MemoryAllowance - static_cast<int64_t>(sizeof(TestClass)));
 | |
| 
 | |
|   ptr1.reset();
 | |
|   EXPECT_EQ(allocatedMemory.load(), MemoryAllowance);
 | |
| 
 | |
|   using RGDataBufType = uint8_t[];
 | |
|   size_t allocSize = 16ULL * 8192;
 | |
|   auto buf = boost::allocate_shared<RGDataBufType>(allocator, allocSize);
 | |
| 
 | |
|   EXPECT_LE(allocatedMemory.load(), MemoryAllowance - allocSize);
 | |
| 
 | |
|   buf.reset();
 | |
|   EXPECT_EQ(allocatedMemory.load(), MemoryAllowance);
 | |
| 
 | |
|   CountingAllocator<RGDataBufType> allocator1(&allocatedMemory, MemoryAllowance / 100, MemoryAllowance / 100);
 | |
|   std::optional<CountingAllocator<RGDataBufType>> allocator2(allocator1);
 | |
|   auto buf1 = boost::allocate_shared<RGDataBufType>(*allocator2, allocSize);
 | |
|   EXPECT_LE(allocatedMemory.load(), MemoryAllowance - allocSize);
 | |
| 
 | |
|   buf1.reset();
 | |
|   EXPECT_EQ(allocatedMemory.load(), MemoryAllowance);
 | |
| }
 | |
| 
 | |
| // Test 5: Thread Safety - Concurrent Allocations and Deallocations
 | |
| TEST_F(CountingAllocatorTest, ThreadSafety)
 | |
| {
 | |
|   const std::size_t numThreads = 100;
 | |
|   const std::size_t allocationsPerThread = 3;
 | |
| 
 | |
|   auto worker = [this]()
 | |
|   {
 | |
|     std::vector<TestClass*> ptrs;
 | |
|     CountingAllocator<TestClass> allocatorLocal(&allocatedMemory, MemoryAllowance / 1000,
 | |
|                                                 MemoryAllowance / 100);
 | |
|     for (std::size_t i = 0; i < allocationsPerThread; ++i)
 | |
|     {
 | |
|       ptrs.push_back(allocatorLocal.allocate(1));
 | |
|     }
 | |
| 
 | |
|     int64_t usedMemory = allocationsPerThread * sizeof(TestClass);
 | |
|     EXPECT_EQ(allocatorLocal.getCurrentLocalMemoryUsage(), allocationsPerThread * sizeof(TestClass));
 | |
|     EXPECT_GE(usedMemory - allocatorLocal.getlastMemoryLimitCheckpoint(), 0LL);
 | |
|     EXPECT_LE(allocatedMemory.load(), MemoryAllowance - allocatorLocal.getlastMemoryLimitCheckpoint());
 | |
| 
 | |
|     for (auto* ptr : ptrs)
 | |
|     {
 | |
|       allocatorLocal.deallocate(ptr, 1);
 | |
|     }
 | |
| 
 | |
|     EXPECT_EQ(allocatorLocal.getCurrentLocalMemoryUsage(), 0);
 | |
|     EXPECT_EQ(allocatorLocal.getlastMemoryLimitCheckpoint(), 0);
 | |
|     EXPECT_GE(allocatedMemory.load(), allocationsPerThread * sizeof(TestClass));
 | |
|   };
 | |
| 
 | |
|   std::vector<std::thread> threads;
 | |
|   // Launch multiple threads performing allocations and deallocations
 | |
|   for (std::size_t i = 0; i < numThreads; ++i)
 | |
|   {
 | |
|     threads.emplace_back(worker);
 | |
|   }
 | |
| 
 | |
|   // Wait for all threads to finish
 | |
|   for (auto& th : threads)
 | |
|   {
 | |
|     th.join();
 | |
|   }
 | |
| 
 | |
|   // After all allocations and deallocations, the counter should be zero minus the remainder
 | |
|   EXPECT_EQ(allocatedMemory.load(), MemoryAllowance);
 | |
| }
 | |
| 
 | |
| // Test 6: Allocating zero objects should not change the counter
 | |
| TEST_F(CountingAllocatorTest, AllocateZeroObjects)
 | |
| {
 | |
|   TestClass* ptr = allocator.allocate(0);
 | |
|   EXPECT_NE(ptr, nullptr);
 | |
|   EXPECT_EQ(allocatedMemory.load(), MemoryAllowance);
 | |
|   allocator.deallocate(ptr, 0);
 | |
|   EXPECT_EQ(allocatedMemory.load(), MemoryAllowance);
 | |
| }
 | |
| 
 | |
| TEST_F(CountingAllocatorTest, CopyAssignable)
 | |
| {
 | |
|   CountingAllocator<TestClass> allocator1(&allocatedMemory);
 | |
|   CountingAllocator<TestClass> allocator2(&allocatedMemory);
 | |
|   allocator1 = allocator2;
 | |
|   EXPECT_EQ(allocator1, allocator2);
 | |
| } |