BufferProvider: cover capacity anouncement in unit test
...need an elaborate test setup, with the test worker thread and the main thread proceeding in lockstep, otherwise the effect would not be visible. Furthermore, the diagnostic access for LocalMemPool can be re-used here, thereby mapping requests onto the pool for the current thread.
This commit is contained in:
parent
e32eec38dd
commit
0256ad8528
4 changed files with 188 additions and 19 deletions
|
|
@ -53,6 +53,9 @@ namespace mem {
|
|||
|
||||
using util::_Fmt;
|
||||
|
||||
class LocalStoreDiagnostic;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Production-grade implementation of buffer allocation
|
||||
|
|
@ -98,8 +101,8 @@ namespace mem {
|
|||
}
|
||||
};
|
||||
|
||||
LocalPool localReg_;
|
||||
GlobalPool globalReg_;
|
||||
LocalPool localPool_;
|
||||
GlobalPool globalPool_;
|
||||
|
||||
public:
|
||||
LocalBufferStore()
|
||||
|
|
@ -118,9 +121,9 @@ namespace mem {
|
|||
uint
|
||||
prepareBuffers (HashVal, uint numBuffers, size_t sizRequest) override
|
||||
{
|
||||
uint cnt = localReg_->reserve(numBuffers, sizRequest);
|
||||
uint cnt = localPool_->reserve(numBuffers, sizRequest);
|
||||
for (uint n{cnt}; n < numBuffers; ++n)
|
||||
globalReg_().async_requestAllocation (*localReg_, sizRequest);
|
||||
globalPool_().async_requestAllocation (*localPool_, sizRequest);
|
||||
// signal back the number of buffers reserved and thus immediately available
|
||||
return cnt;
|
||||
}
|
||||
|
|
@ -142,16 +145,16 @@ namespace mem {
|
|||
BuffAlloc
|
||||
provideBuffer (HashVal, size_t sizRequest, LocalTag specifics, int64_t) override
|
||||
{
|
||||
if (not localReg_->canServe(sizRequest))
|
||||
globalReg_().processPendingRequests();
|
||||
if (not localPool_->canServe(sizRequest))
|
||||
globalPool_().processPendingRequests();
|
||||
|
||||
// attempt to satisfy allocation request from the local pool...
|
||||
Alloc alloc = localReg_->retrieve (sizRequest);
|
||||
Alloc alloc = localPool_->retrieve (sizRequest);
|
||||
auto& [storage,buffSiz] = alloc;
|
||||
|
||||
// possibly the request was not announced beforehand...?
|
||||
if (not storage)
|
||||
alloc = globalReg_().requestAllocation (sizRequest);
|
||||
alloc = globalPool_().requestAllocation (sizRequest);
|
||||
if (not storage)
|
||||
throw err::State{_Fmt{"Unable to provide further %d bytes of buffer memory."} % sizRequest
|
||||
,LUMIERA_ERROR_BUFFER_MANAGEMENT};
|
||||
|
|
@ -181,12 +184,23 @@ namespace mem {
|
|||
{
|
||||
auto& [storage,buffSiz,specifics] = storageSlot;
|
||||
REQUIRE (storage);
|
||||
localReg_->reSupply (storage);
|
||||
localPool_->reSupply (storage);
|
||||
/////////////////////////////////////////////////////OOO trigger heuristic clean-up here
|
||||
}
|
||||
|
||||
private:
|
||||
/** diagnostic access for unit testing */
|
||||
friend PoolDiagnostic watch (LocalBufferStore const&);
|
||||
};
|
||||
|
||||
|
||||
/** entrance point to inspection for test */
|
||||
inline PoolDiagnostic
|
||||
watch (LocalBufferStore const& lstor)
|
||||
{
|
||||
return PoolDiagnostic{*lstor.localPool_};
|
||||
}
|
||||
|
||||
|
||||
}} // namespace vault::mem
|
||||
#endif /*VAULT_MEM_LOCAL_BUFFER_STORE_H*/
|
||||
|
|
|
|||
|
|
@ -508,6 +508,19 @@ namespace mem {
|
|||
,[](Block const& b){ return not b.used; });
|
||||
}
|
||||
|
||||
size_t
|
||||
cntResd()
|
||||
{
|
||||
return std::ranges::count_if (memPool_.blocks_
|
||||
,[](Block const& b){ return b.resd; });
|
||||
}
|
||||
|
||||
bool
|
||||
canServe (size_t sizRequest)
|
||||
{
|
||||
return memPool_.canServe (sizRequest);
|
||||
} // map to the pool's diagnostic API for uniform access
|
||||
|
||||
bool
|
||||
isFree (Buff* mem)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include "test/diagnostic-output.hpp"///////////////////////TODO
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
using util::isSameAdr;
|
||||
|
||||
|
|
@ -97,8 +98,8 @@ namespace test {
|
|||
lib::DependInject<EngineBufferManager>::Local<EngineBufferManager> globalPool;
|
||||
|
||||
// provide the *test subject*,
|
||||
// which implements the BufferStage API
|
||||
// and uses a thread-local metadata table internally
|
||||
// which implements the BufferStore API
|
||||
// and uses a thread-local allocation pool internally
|
||||
LocalBufferStore localStore;
|
||||
BufferProviderSetup::Store& storeAPI = localStore;
|
||||
|
||||
|
|
@ -184,11 +185,98 @@ namespace test {
|
|||
|
||||
|
||||
|
||||
/** @test
|
||||
|
||||
/** @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
|
||||
*/
|
||||
void
|
||||
verify_announce()
|
||||
{
|
||||
// »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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -145226,8 +145226,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
Man müßte dazu also den AllocReceiver umformulieren und zudem auch noch die Requests im EngineBufferManager. Vermutlich läuft das auf einen speziellen smart-Handle-Typ hinaus. Dieser würde eine Prüfsumme beinhalten, die dann auf der Empfängerseite genauso im Speicher vorzufinden sein muß. Die Prüfsumme würde auf einer Nonce plus der Ziel-Addresse beruhen, und könnte damit so nur ein einziges Mal pro Programmlauf auftreten (modulo 64-bit Hash-Kollision). Die Sperr-Flag bestünde dann einfach darin, die empfängerseitige Prüfsumme auf Null zu setzen (kein Match, kein Zugriff). Diese empfängerseitige Prüfsumme müßte ein Atomic sein, und beim Zugriff würde eine Lese-Barriere angewendet, beim Ändern eine Schreibbarriere. Das wäre völlig akzeptabel. Darüberhinaus gäbe es keine Contention (außer beim Neuerzeugen eines Objekts, weil dann die Nonce inkrementiert werden muß).
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<arrowlink COLOR="#effec1" DESTINATION="ID_86232371" ENDARROW="Default" ENDINCLINATION="590;70;" ID="Arrow_ID_1616330137" STARTARROW="None" STARTINCLINATION="89;231;"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
|
|
@ -145245,8 +145244,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<linktarget COLOR="#3d7395" DESTINATION="ID_332907520" ENDARROW="Default" ENDINCLINATION="-1840;207;" ID="Arrow_ID_668417083" SOURCE="ID_1820292066" STARTARROW="None" STARTINCLINATION="-634;41;"/>
|
||||
<linktarget COLOR="#3d7395" DESTINATION="ID_332907520" ENDARROW="Default" ENDINCLINATION="-1840;207;" ID="Arrow_ID_1142922633" SOURCE="ID_142165791" STARTARROW="None" STARTINCLINATION="-634;41;"/>
|
||||
<linktarget COLOR="#3d7395" DESTINATION="ID_332907520" ENDARROW="Default" ENDINCLINATION="-1840;207;" ID="Arrow_ID_668417083" SOURCE="ID_1820292066" STARTARROW="None" STARTINCLINATION="-634;41;"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1779302523581" ID="ID_272378166" MODIFIED="1779302536450" TEXT="wird inkorporiert"/>
|
||||
<node CREATED="1779302591078" ID="ID_1400105754" MODIFIED="1779302599488" TEXT="initialisiert sich automatisch mit einer Nonce"/>
|
||||
|
|
@ -145592,12 +145591,67 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1778948944946" ID="ID_399276838" MODIFIED="1778948954589" TEXT="verify_announce">
|
||||
<node CREATED="1778948971327" ID="ID_655345868" MODIFIED="1778948993943" TEXT="muß hier die Prüfung bei laufendem Worker-Thread machen (barrier)"/>
|
||||
<node CREATED="1778948997109" ID="ID_1331981737" MODIFIED="1778949018089" TEXT="Announcment muß im Engine-Pool sichtbar sein"/>
|
||||
<node COLOR="#338800" CREATED="1778948944946" ID="ID_399276838" MODIFIED="1779640869852" TEXT="verify_announce">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1778948997109" ID="ID_1331981737" MODIFIED="1779640861359" TEXT="Announcement muß im Engine-Pool sichtbar sein">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778948971327" ID="ID_655345868" MODIFIED="1779640850047" TEXT="muß hier die Prüfung bei laufendem Worker-Thread machen">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...denn nach dem Thread-Ende wird die Allokation automatisch zurückgeführt, so daß sich der Effekt nicht mehr beobachten läßt
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node CREATED="1779633633221" ID="ID_580266326" MODIFIED="1779633638449" TEXT="schwierig zu testen">
|
||||
<node CREATED="1779633639193" ID="ID_1983957454" MODIFIED="1779633649944" TEXT="da nun ja die Queue erst mal nicht bedient wird"/>
|
||||
<node CREATED="1779633660674" ID="ID_552359761" MODIFIED="1779633677035" TEXT="außerdem: nach dem Thread-Ende soll der Request ignoriert werden"/>
|
||||
</node>
|
||||
<node CREATED="1779633705814" ID="ID_1901638981" MODIFIED="1779640605148" TEXT="brauche koordinierten mehrstufigen Test">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...wenn man das erst mal akzeptiert, ist es auch nicht so schlimm: man inkrementiert einen Atomic
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1779640700775" ID="ID_333012616" MODIFIED="1779640867890" TEXT="nach Zustellung muß die Allokation im lokalen Pool frei verfügbar sein"/>
|
||||
<node COLOR="#435e98" CREATED="1779640715451" ID="ID_1548631619" MODIFIED="1779640867886">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
ein weiters Announcement,
|
||||
</p>
|
||||
<p>
|
||||
das nicht mehr zugestellt wird
|
||||
</p>
|
||||
<p>
|
||||
wird transparent ignoriert
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1778949021417" ID="ID_1256480563" MODIFIED="1778968909103" TEXT="verify_allocate">
|
||||
<node CREATED="1778969027419" ID="ID_1633706484" MODIFIED="1778969039237" TEXT="gemeint ist: eine direkte Allokation, ohne Ankündigung"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1779640817389" ID="ID_223109084" MODIFIED="1779640846459" TEXT="auch hier muß Beobachtung bei noch laufendem Worker erfolgen">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1778968918381" ID="ID_752345967" MODIFIED="1778968922655" TEXT="verify_release">
|
||||
<node CREATED="1778969042432" ID="ID_1262962638" MODIFIED="1778969052166" TEXT="hier sollte auch ein clean-up provoziert werden"/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue