2026-04-21 23:30:35 +02:00
|
|
|
|
/*
|
|
|
|
|
|
LocalBufferStore(Test) - verify thread-local buffer memory allocator proxy
|
|
|
|
|
|
|
|
|
|
|
|
Copyright (C)
|
|
|
|
|
|
2026, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
|
|
|
|
|
|
|
|
**Lumiera** 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; either version 2 of the License, or (at your
|
|
|
|
|
|
option) any later version. See the file COPYING for further details.
|
|
|
|
|
|
|
|
|
|
|
|
* *****************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
/** @file local-buffer-store-test.cpp
|
|
|
|
|
|
** unit test \ref LocalBufferStore_test
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "test/run.hpp"
|
|
|
|
|
|
#include "test/test-helper.hpp"
|
|
|
|
|
|
#include "vault/mem/local-buffer-store.hpp"
|
2026-05-16 23:06:30 +02:00
|
|
|
|
#include "vault/mem/engine-buffer-manager.hpp"
|
|
|
|
|
|
#include "vault/mem/engine-buffer-allocator.hpp"
|
|
|
|
|
|
#include "lib/depend-inject.hpp"
|
|
|
|
|
|
#include "lib/thread.hpp"
|
|
|
|
|
|
#include "lib/util.hpp"
|
2026-05-23 23:22:25 +02:00
|
|
|
|
#include "test/diagnostic-output.hpp"///////////////////////TODO
|
2026-04-21 23:30:35 +02:00
|
|
|
|
|
2026-05-16 23:06:30 +02:00
|
|
|
|
#include <algorithm>
|
2026-05-24 18:44:32 +02:00
|
|
|
|
#include <atomic>
|
2026-04-21 23:30:35 +02:00
|
|
|
|
|
2026-05-16 23:06:30 +02:00
|
|
|
|
using util::isSameAdr;
|
2026-05-23 23:22:25 +02:00
|
|
|
|
|
2026-05-16 23:06:30 +02:00
|
|
|
|
using lib::Thread;
|
2026-05-20 23:17:59 +02:00
|
|
|
|
using std::this_thread::yield;
|
|
|
|
|
|
using std::this_thread::sleep_for;
|
|
|
|
|
|
using std::chrono_literals::operator ""ms;
|
2026-04-21 23:30:35 +02:00
|
|
|
|
|
|
|
|
|
|
namespace vault {
|
|
|
|
|
|
namespace mem {
|
|
|
|
|
|
namespace test {
|
|
|
|
|
|
|
|
|
|
|
|
// using LERR_(LOGIC);
|
|
|
|
|
|
// using LERR_(LIFECYCLE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace { // Test helper
|
|
|
|
|
|
|
|
|
|
|
|
// template<typename X>
|
|
|
|
|
|
// Buff*
|
|
|
|
|
|
// mark_as_Buffer(X& something)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// return reinterpret_cast<Buff*> (std::addressof(something));
|
|
|
|
|
|
// }
|
|
|
|
|
|
//
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************//**
|
|
|
|
|
|
* @test verify worker memory handling that relies on a thread-local allocator.
|
|
|
|
|
|
*/
|
|
|
|
|
|
class LocalBufferStore_test : public Test
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
virtual void
|
|
|
|
|
|
run (Arg)
|
|
|
|
|
|
{
|
2026-05-16 23:06:30 +02:00
|
|
|
|
// seedRand();
|
2026-04-21 23:30:35 +02:00
|
|
|
|
|
2026-05-16 23:06:30 +02:00
|
|
|
|
verify_API();
|
|
|
|
|
|
verify_announce();
|
|
|
|
|
|
verify_allocate();
|
|
|
|
|
|
verify_release();
|
2026-04-21 23:30:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 23:22:25 +02:00
|
|
|
|
/** @test demonstrate a typical usage cycle of the BufferStore in concurrent setup
|
|
|
|
|
|
* - the main thread provides a mock instance of the EngineBufferManager
|
|
|
|
|
|
* - furthermore, the LocalBufferStore instance is created here
|
|
|
|
|
|
* - yet note: LocalBufferStore includes a LocalMemPool per thread
|
|
|
|
|
|
* - a test thread is launched to issue some allocation requests
|
|
|
|
|
|
* - announce some memory allocation demand
|
|
|
|
|
|
* - claim and use one buffer
|
|
|
|
|
|
* - generate further demand to trigger further allocation supply
|
|
|
|
|
|
* - terminate the thread
|
|
|
|
|
|
* - verify the number of allocations seen in the EngineBufferManager
|
|
|
|
|
|
*/
|
2026-04-21 23:30:35 +02:00
|
|
|
|
void
|
2026-05-16 23:06:30 +02:00
|
|
|
|
verify_API()
|
|
|
|
|
|
{
|
|
|
|
|
|
// provide a »central BufferPool« (as a transient instance for this test)
|
|
|
|
|
|
lib::DependInject<EngineBufferManager>::Local<EngineBufferManager> globalPool;
|
|
|
|
|
|
|
|
|
|
|
|
// provide the *test subject*,
|
2026-05-24 18:44:32 +02:00
|
|
|
|
// which implements the BufferStore API
|
|
|
|
|
|
// and uses a thread-local allocation pool internally
|
2026-05-16 23:06:30 +02:00
|
|
|
|
LocalBufferStore localStore;
|
|
|
|
|
|
BufferProviderSetup::Store& storeAPI = localStore;
|
|
|
|
|
|
|
|
|
|
|
|
const size_t BUFFSIZ = 555;
|
|
|
|
|
|
const HashVal TYPEID = 12345;
|
|
|
|
|
|
const size_t CNT = 5;
|
|
|
|
|
|
const int64_t ARG = 7;
|
|
|
|
|
|
const LocalTag MARK{42};
|
|
|
|
|
|
|
|
|
|
|
|
const size_t ALLOC = HeapBufferAllocator::numTiles(BUFFSIZ) * HeapBufferAllocator::TILE_SIZ;
|
|
|
|
|
|
|
2026-05-18 23:12:43 +02:00
|
|
|
|
Thread testWorker{[&] /* === Play through one typical usage cycle === */
|
2026-05-16 23:06:30 +02:00
|
|
|
|
{
|
|
|
|
|
|
uint avail = storeAPI.prepareBuffers(TYPEID, CNT, BUFFSIZ);
|
|
|
|
|
|
CHECK (0 == avail);
|
|
|
|
|
|
|
|
|
|
|
|
BuffAlloc storageSlot = storeAPI.provideBuffer (TYPEID, BUFFSIZ, MARK, ARG);
|
|
|
|
|
|
auto& [storage,buffSiz,specifics] = storageSlot;
|
|
|
|
|
|
CHECK (storage);
|
|
|
|
|
|
CHECK (BUFFSIZ <= buffSiz);
|
|
|
|
|
|
CHECK (ALLOC == buffSiz);
|
|
|
|
|
|
CHECK (MARK == specifics);
|
|
|
|
|
|
|
|
|
|
|
|
// can use that memory
|
|
|
|
|
|
char* buf = reinterpret_cast<char*> (storage);
|
|
|
|
|
|
std::string ranS{lib::randStr(555-1)};
|
|
|
|
|
|
auto* p = std::copy (ranS.begin(), ranS.end(), buf);
|
|
|
|
|
|
CHECK (ranS.length() == size_t(p - buf));
|
|
|
|
|
|
*p = '\0';
|
|
|
|
|
|
CHECK (ranS == buf);
|
|
|
|
|
|
|
2026-05-20 23:17:59 +02:00
|
|
|
|
// reserve four additional buffers;
|
|
|
|
|
|
// these happen to sit already in the local pool anyway
|
|
|
|
|
|
// and will be marked as "reserved" now....
|
|
|
|
|
|
avail = storeAPI.prepareBuffers(TYPEID, 4, BUFFSIZ);
|
|
|
|
|
|
CHECK (4 == avail);
|
|
|
|
|
|
|
|
|
|
|
|
// finish usage and return the single buffer claimed thus far...
|
2026-05-16 23:06:30 +02:00
|
|
|
|
storeAPI.mark_emitted (TYPEID, storageSlot);
|
|
|
|
|
|
storeAPI.detachBuffer (TYPEID, storageSlot);
|
|
|
|
|
|
|
2026-05-20 23:17:59 +02:00
|
|
|
|
// now we have 5 buffers in the pool, but 4 are marked as "reserved"
|
|
|
|
|
|
// thus only one buffer can be confirmed and one further is requested from EngineBufferManager
|
2026-05-16 23:06:30 +02:00
|
|
|
|
avail = storeAPI.prepareBuffers(TYPEID, 2, BUFFSIZ);
|
|
|
|
|
|
CHECK (1 == avail);
|
|
|
|
|
|
|
2026-05-20 23:17:59 +02:00
|
|
|
|
// but irrespective of reservation, we can immediately use the buffers locally available
|
2026-05-16 23:06:30 +02:00
|
|
|
|
BuffAlloc slot1 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, MARK, ARG);
|
|
|
|
|
|
auto& [sto1,siz1,tag1] = slot1;
|
|
|
|
|
|
BuffAlloc slot2 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, MARK, ARG);
|
|
|
|
|
|
auto& [sto2,siz2,tag2] = slot2;
|
|
|
|
|
|
CHECK (sto1);
|
|
|
|
|
|
CHECK (sto2);
|
|
|
|
|
|
CHECK (isSameAdr (storage, sto1));
|
2026-05-18 23:12:43 +02:00
|
|
|
|
CHECK (not isSameAdr (storage, sto2));
|
2026-05-16 23:06:30 +02:00
|
|
|
|
CHECK (BUFFSIZ <= siz1);
|
|
|
|
|
|
CHECK (BUFFSIZ <= siz2);
|
|
|
|
|
|
|
|
|
|
|
|
storeAPI.detachBuffer (TYPEID, slot2);
|
|
|
|
|
|
storeAPI.detachBuffer (TYPEID, slot1);
|
|
|
|
|
|
}}; // LocalMemPool winds down at end of thread
|
|
|
|
|
|
while (testWorker)
|
|
|
|
|
|
yield(); // wait for worker to finish
|
|
|
|
|
|
|
2026-05-20 23:17:59 +02:00
|
|
|
|
// additional delay for the local pool's destructor
|
|
|
|
|
|
// that is invoked after the thread has finished
|
|
|
|
|
|
sleep_for(1ms);
|
|
|
|
|
|
|
2026-05-16 23:06:30 +02:00
|
|
|
|
CHECK (globalPool); // the Mock was accessed (and thus instantiated)
|
|
|
|
|
|
|
|
|
|
|
|
// before shutdown, LocalMemPool has sent back all allocations;
|
2026-05-20 23:17:59 +02:00
|
|
|
|
// these should sit now in the EngineBufferManager's input queue,
|
|
|
|
|
|
// unprocessed yet; same for the additional buffer requested later.
|
2026-05-23 23:22:25 +02:00
|
|
|
|
// EngineBufferManager only sees the first CNT allocations actually dispatched
|
|
|
|
|
|
CHECK (CNT == watch(*globalPool).numAllocs());
|
|
|
|
|
|
CHECK (CNT*ALLOC == watch(*globalPool).bytesLeased());
|
2026-05-16 23:06:30 +02:00
|
|
|
|
|
2026-05-23 23:22:25 +02:00
|
|
|
|
// process the in-queue and retrieve all allocations...
|
2026-05-16 23:06:30 +02:00
|
|
|
|
globalPool->processPendingRequests();
|
2026-05-23 23:22:25 +02:00
|
|
|
|
// everything should be cleaned-up by now...
|
|
|
|
|
|
CHECK (0 == watch(*globalPool).bytesLeased());
|
2026-05-16 23:06:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-24 18:44:32 +02:00
|
|
|
|
|
|
|
|
|
|
/** @test announcement of capacity demand is propagated eventually
|
|
|
|
|
|
* - test thread must proceed in lockstep to make effects observable
|
|
|
|
|
|
* - Step-1: test thread announces demand on the subject (`BufferStore` API)
|
|
|
|
|
|
* - Step-2: „someone“ processes pending requests in the EngineBufferManager
|
|
|
|
|
|
* - Step-3: test thread announces further demand that remains unprocessed
|
|
|
|
|
|
* - test thread then terminates, sends back all allocations
|
|
|
|
|
|
* - the pending request is ignored transparently
|
2026-05-16 23:06:30 +02:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_announce()
|
|
|
|
|
|
{
|
2026-05-24 18:44:32 +02:00
|
|
|
|
// »central BufferPool« (transient instance for this test)
|
|
|
|
|
|
lib::DependInject<EngineBufferManager>::Local<EngineBufferManager> globalPool;
|
|
|
|
|
|
|
|
|
|
|
|
// *test subject*
|
|
|
|
|
|
LocalBufferStore localStore;
|
|
|
|
|
|
BufferProviderSetup::Store& storeAPI = localStore;
|
|
|
|
|
|
|
|
|
|
|
|
const size_t BUFFSIZ = 1024;
|
|
|
|
|
|
const HashVal TYPEID = 12345;
|
|
|
|
|
|
|
|
|
|
|
|
// coordination of test steps
|
|
|
|
|
|
std::atomic_uint step{1};
|
|
|
|
|
|
|
|
|
|
|
|
Thread testWorker{[&] /* === Scenario: test subject announces capacity demand === */
|
|
|
|
|
|
{
|
|
|
|
|
|
// Step-1
|
|
|
|
|
|
uint avail = storeAPI.prepareBuffers(TYPEID, 1, BUFFSIZ);
|
|
|
|
|
|
CHECK (0 == avail);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (watch(localStore).isEmpty());
|
|
|
|
|
|
|
|
|
|
|
|
// wait for being serviced...
|
|
|
|
|
|
++step;
|
|
|
|
|
|
while (step < 3)
|
|
|
|
|
|
sleep_for(1ms);
|
|
|
|
|
|
|
|
|
|
|
|
// Step-3
|
|
|
|
|
|
CHECK (not watch(localStore).isEmpty());
|
|
|
|
|
|
CHECK (watch(localStore).canServe(BUFFSIZ));
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 1);
|
|
|
|
|
|
CHECK (watch(localStore).size() == 1);
|
|
|
|
|
|
|
|
|
|
|
|
// Note: this block was supplied but the local pool does not know why,
|
|
|
|
|
|
// since reservations are only tracked on blocks already available
|
|
|
|
|
|
CHECK (watch(localStore).cntResd() == 0);
|
|
|
|
|
|
|
|
|
|
|
|
// Now, instead of using this buffer block,
|
|
|
|
|
|
// rather generate another capacity announcement...
|
|
|
|
|
|
avail = storeAPI.prepareBuffers(TYPEID, 2, BUFFSIZ);
|
|
|
|
|
|
CHECK (1 == avail);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (watch(localStore).cntResd() == 1);
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 1);
|
|
|
|
|
|
CHECK (watch(localStore).size() == 1);
|
|
|
|
|
|
CHECK (watch(localStore).canServe(BUFFSIZ));
|
|
|
|
|
|
|
|
|
|
|
|
// Note: while a further request is "out there",
|
|
|
|
|
|
// only a single block was received up to now.
|
|
|
|
|
|
// LocalMemPool winds down automatically at end of thread
|
|
|
|
|
|
}};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* === Main thread: observe effects in the global pool === */
|
|
|
|
|
|
|
|
|
|
|
|
// wait until the first announcement was made
|
|
|
|
|
|
while (step < 2)
|
|
|
|
|
|
sleep_for(1ms);
|
|
|
|
|
|
|
|
|
|
|
|
// Step-2
|
|
|
|
|
|
CHECK (watch(*globalPool).numAllocs() == 0);
|
|
|
|
|
|
|
|
|
|
|
|
// process the in-queue and service the allocation request...
|
|
|
|
|
|
globalPool->processPendingRequests();
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (watch(*globalPool).numAllocs() == 1);
|
|
|
|
|
|
CHECK (watch(*globalPool).bytesLeased() >= BUFFSIZ);
|
|
|
|
|
|
|
|
|
|
|
|
// let the test thread proceed...
|
|
|
|
|
|
step = 3;
|
|
|
|
|
|
|
|
|
|
|
|
// wait until test thread has wound down
|
|
|
|
|
|
while (testWorker)
|
|
|
|
|
|
sleep_for(1ms);
|
|
|
|
|
|
|
|
|
|
|
|
// additional delay to be sure the destructor of the local pool has finished
|
|
|
|
|
|
sleep_for(1ms);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (watch(*globalPool).numAllocs() == 1);
|
|
|
|
|
|
globalPool->processPendingRequests();
|
|
|
|
|
|
CHECK (watch(*globalPool).bytesLeased() == 0);
|
2026-05-16 23:06:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-24 20:02:48 +02:00
|
|
|
|
/** @test request an allocation from the local store, without prior announcement
|
|
|
|
|
|
* - again, a coordinated test setup is necessary to watch the effects
|
|
|
|
|
|
* - Step-1: test worker triggers the allocation and retrieves it immediately
|
|
|
|
|
|
* - Step-2: allocation and the leased-out memory is visible in the global pool
|
|
|
|
|
|
* - test worker unwinds, allocations are returned
|
2026-05-16 23:06:30 +02:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_allocate()
|
|
|
|
|
|
{
|
2026-05-24 20:02:48 +02:00
|
|
|
|
// »central BufferPool« (transient instance for this test)
|
|
|
|
|
|
lib::DependInject<EngineBufferManager>::Local<EngineBufferManager> globalPool;
|
|
|
|
|
|
|
|
|
|
|
|
// *test subject*
|
|
|
|
|
|
LocalBufferStore localStore;
|
|
|
|
|
|
BufferProviderSetup::Store& storeAPI = localStore;
|
|
|
|
|
|
|
|
|
|
|
|
const size_t BUFFSIZ = 1024;
|
|
|
|
|
|
const HashVal TYPEID = 12345;
|
|
|
|
|
|
|
|
|
|
|
|
// coordination of test steps
|
|
|
|
|
|
std::atomic_uint step{1};
|
|
|
|
|
|
|
|
|
|
|
|
Thread testWorker{[&] /* === Scenario: test subject requests allocation immediately === */
|
|
|
|
|
|
{
|
|
|
|
|
|
// Step-1
|
|
|
|
|
|
CHECK (watch(localStore).isEmpty());
|
|
|
|
|
|
|
|
|
|
|
|
BuffAlloc slot1 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, 0,0);
|
|
|
|
|
|
auto& [storage,buffSiz,_] = slot1;
|
|
|
|
|
|
CHECK (storage);
|
|
|
|
|
|
CHECK (BUFFSIZ <= buffSiz);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (not watch(localStore).isEmpty());
|
|
|
|
|
|
CHECK (watch(localStore).size() == 1);
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 0);
|
|
|
|
|
|
|
|
|
|
|
|
++step;
|
|
|
|
|
|
// allocation can be re-used locally,
|
|
|
|
|
|
// without any further exchange with the global pool
|
|
|
|
|
|
|
|
|
|
|
|
storeAPI.detachBuffer (TYPEID, slot1);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (watch(localStore).canServe(BUFFSIZ));
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 1);
|
|
|
|
|
|
CHECK (watch(localStore).size() == 1);
|
|
|
|
|
|
|
|
|
|
|
|
BuffAlloc slot2 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, 0,0);
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 0);
|
|
|
|
|
|
|
|
|
|
|
|
// the same memory address was re-used
|
|
|
|
|
|
CHECK (slot1 == slot2);
|
|
|
|
|
|
|
|
|
|
|
|
storeAPI.detachBuffer (TYPEID, slot2);
|
|
|
|
|
|
|
|
|
|
|
|
// can even reserve that memory without any round-trip to the global pool
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 1);
|
|
|
|
|
|
CHECK (watch(localStore).cntResd() == 0);
|
|
|
|
|
|
|
|
|
|
|
|
uint avail = storeAPI.prepareBuffers(TYPEID, 1, BUFFSIZ);
|
|
|
|
|
|
CHECK (1 == avail);
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 1);
|
|
|
|
|
|
CHECK (watch(localStore).cntResd() == 1);
|
|
|
|
|
|
CHECK (watch(localStore).size() == 1);
|
|
|
|
|
|
CHECK (watch(localStore).canServe(BUFFSIZ));
|
|
|
|
|
|
|
|
|
|
|
|
}}; // block automatically sent back after thread end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* === Main thread: observe effects in the global pool === */
|
|
|
|
|
|
|
|
|
|
|
|
// wait until memory was claimed
|
|
|
|
|
|
while (step < 2)
|
|
|
|
|
|
sleep_for(1ms);
|
|
|
|
|
|
|
|
|
|
|
|
// Step-2
|
|
|
|
|
|
CHECK (watch(*globalPool).numAllocs() == 1);
|
|
|
|
|
|
CHECK (watch(*globalPool).bytesLeased() >= BUFFSIZ);
|
|
|
|
|
|
|
|
|
|
|
|
// wait until test thread terminates
|
|
|
|
|
|
while (testWorker)
|
|
|
|
|
|
sleep_for(1ms);
|
|
|
|
|
|
sleep_for(1ms); // additional wait for the dtor
|
|
|
|
|
|
|
|
|
|
|
|
globalPool->processPendingRequests();
|
|
|
|
|
|
CHECK (watch(*globalPool).bytesLeased() == 0);
|
2026-05-16 23:06:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-24 23:38:37 +02:00
|
|
|
|
/** @test heuristic clean-up after releasing several allocations
|
|
|
|
|
|
* - at first, a de-allocated block remains available in the local pool
|
|
|
|
|
|
* - after a number of usages, defined as _turnover_, clean-up is triggered
|
|
|
|
|
|
* - this test scenario requests 3 buffers, but repeatedly uses only one
|
|
|
|
|
|
* - after the third round with this usage pattern, the local pool is reduced
|
2026-05-16 23:06:30 +02:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_release()
|
2026-04-21 23:30:35 +02:00
|
|
|
|
{
|
2026-05-24 23:38:37 +02:00
|
|
|
|
// »central BufferPool« (transient instance for this test)
|
|
|
|
|
|
lib::DependInject<EngineBufferManager>::Local<EngineBufferManager> globalPool;
|
|
|
|
|
|
|
|
|
|
|
|
// *test subject*
|
|
|
|
|
|
LocalBufferStore localStore;
|
|
|
|
|
|
BufferProviderSetup::Store& storeAPI = localStore;
|
|
|
|
|
|
|
|
|
|
|
|
const size_t BUFFSIZ = 1024;
|
|
|
|
|
|
const HashVal TYPEID = 12345;
|
|
|
|
|
|
|
|
|
|
|
|
// coordination of test steps
|
|
|
|
|
|
std::atomic_uint step{1};
|
|
|
|
|
|
|
|
|
|
|
|
Thread testWorker{[&] /* === Scenario: test subject requests allocation immediately === */
|
|
|
|
|
|
{
|
|
|
|
|
|
// Step-1 : first round
|
|
|
|
|
|
uint avail = storeAPI.prepareBuffers(TYPEID, 3, BUFFSIZ);
|
|
|
|
|
|
CHECK (0 == avail);
|
|
|
|
|
|
CHECK (watch(localStore).isEmpty());
|
|
|
|
|
|
|
|
|
|
|
|
BuffAlloc slot1 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, 0,0);
|
|
|
|
|
|
auto& [storage,buffSiz,_] = slot1;
|
|
|
|
|
|
CHECK (storage);
|
|
|
|
|
|
|
|
|
|
|
|
storeAPI.detachBuffer (TYPEID, slot1);
|
|
|
|
|
|
// Result: got three allocations and all three are in the local pool
|
|
|
|
|
|
CHECK (watch(localStore).size() == 3);
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 3);
|
|
|
|
|
|
|
|
|
|
|
|
++step;
|
|
|
|
|
|
// Step-2 : repeat same pattern
|
|
|
|
|
|
BuffAlloc slot2 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, 0,0);
|
|
|
|
|
|
CHECK (slot2 == slot1);
|
|
|
|
|
|
CHECK (watch(localStore).size() == 3);
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 2);
|
|
|
|
|
|
|
|
|
|
|
|
storeAPI.detachBuffer (TYPEID, slot1);
|
|
|
|
|
|
CHECK (watch(localStore).size() == 3);
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 3);
|
|
|
|
|
|
|
|
|
|
|
|
++step;
|
|
|
|
|
|
// Step-3 : repeat for a third time
|
|
|
|
|
|
BuffAlloc slot3 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, 0,0);
|
|
|
|
|
|
CHECK (slot3 == slot1);
|
|
|
|
|
|
CHECK (watch(localStore).size() == 3);
|
|
|
|
|
|
CHECK (watch(localStore).cntFree() == 2);
|
|
|
|
|
|
|
|
|
|
|
|
storeAPI.detachBuffer (TYPEID, slot1);
|
|
|
|
|
|
// this completed a "turnover" of three buffers
|
|
|
|
|
|
SHOW_EXPR(watch(localStore).size());
|
|
|
|
|
|
|
|
|
|
|
|
++step;
|
|
|
|
|
|
// main thread will now see the partial clean-up
|
|
|
|
|
|
|
|
|
|
|
|
}}; // block automatically sent back after thread end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* === Main thread: observe effects in the global pool === */
|
|
|
|
|
|
|
|
|
|
|
|
// wait until Step-2
|
|
|
|
|
|
while (step < 2)
|
|
|
|
|
|
sleep_for(1ms);
|
|
|
|
|
|
CHECK (watch(*globalPool).numAllocs() == 3);
|
|
|
|
|
|
|
|
|
|
|
|
// re-check after Step-2
|
|
|
|
|
|
while (step <= 2)
|
|
|
|
|
|
sleep_for(1ms);
|
|
|
|
|
|
// nothing changed; local pool operates independently
|
|
|
|
|
|
CHECK (watch(*globalPool).numAllocs() == 3);
|
|
|
|
|
|
|
|
|
|
|
|
// re-check after Step-3
|
|
|
|
|
|
while (step <= 3)
|
|
|
|
|
|
sleep_for(1ms);
|
|
|
|
|
|
globalPool->processPendingRequests();
|
|
|
|
|
|
SHOW_EXPR(watch(*globalPool).numAllocs())
|
2026-04-21 23:30:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Register this test class... */
|
|
|
|
|
|
LAUNCHER (LocalBufferStore_test, "unit engine");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}}} // namespace vault::mem::test
|