BufferProvider: implement and verify the second case for synchronisation

...which happens when a buffer type is referred for the first time
from within a new worker thread, while it is already stored in the
global registry; the full key-chain is down-synced then.
This commit is contained in:
Fischlurch 2026-04-20 19:48:20 +02:00
parent abecd2cbde
commit 3b7877204d
3 changed files with 97 additions and 33 deletions

View file

@ -62,7 +62,7 @@ namespace mem {
* defined anew in the worker, or when a worker refers for the first
* time to an type predefined from the Builder. In those (rare) cases
* however, a mutex synchronisation is necessary to protect the global
* metadata table against corruption and ensure synchronous response:
* metadata table against corruption and ensure synchronous response.
*/
class LocalBufferStage
: public BufferProviderSetup::Stage
@ -76,10 +76,13 @@ namespace mem {
/* === BufferStage interface === */
ID
lookup (HashVal key) override
{
if (not localReg_->isKnown(key))
globalReg_().propagateDown(key, *localReg_);
// try it again after sync with central registry
return localReg_->isKnown(key)? localReg_->get (key)
: metadata::Key::INVALID;
}
@ -149,6 +152,7 @@ namespace mem {
{ }
HashVal getFamilyID() const override { return familyID_; }
size_t cntEntries() const { return localReg_->cntEntries(); }
};

View file

@ -16,31 +16,15 @@
*/
#include "lib/error.hpp"
#include "test/run.hpp"
//#include "test/test-frame.hpp"
#include "test/test-helper.hpp"
#include "vault/mem/buffhandle.hpp"
#include "vault/mem/engine-buffer-metadata.hpp"
#include "vault/mem/local-buffer-stage.hpp"
//#include "lib/uninitialised-storage.hpp"
#include "lib/depend-inject.hpp"
#include "lib/thread.hpp"
//#include "lib/symbol.hpp"
#include "lib/util.hpp"
#include "test/diagnostic-output.hpp"////////////////TODO
#include <memory>
#include "lib/error.hpp"
//using std::strncpy;
//using std::unique_ptr;
//using test::TestFrame;
//using test::testData;
//using util::isSameObject;
//using util::isnil;
//using lib::randStr;
//using lib::Literal;
using std::this_thread::yield;
using lib::Thread;
@ -49,12 +33,10 @@ namespace mem {
namespace test {
using LERR_(LOGIC);
// using LERR_(FATAL);
// using LERR_(INVALID);
using LERR_(LIFECYCLE);
namespace { // Test fixture
namespace { // Test helper
template<typename X>
Buff*
@ -63,7 +45,7 @@ namespace test {
return reinterpret_cast<Buff*> (std::addressof(something));
}
}//(End) Test fixture and helpers
}
@ -83,6 +65,7 @@ namespace test {
run (Arg)
{
seedRand();
verify_DataTransfer();
verify_publishNewKey();
verify_fetchNewKeyOnUse();
@ -174,7 +157,7 @@ namespace test {
* - notably register a totally new buffer type, from within the worker
* - then also perform the steps necessary to lock and release a buffer
* - check that the new type was migrated into the central metadata hub
* - verify that also the complete paretn-chain was published
* - verify that also the complete parent-chain was published
*/
void
verify_publishNewKey()
@ -196,7 +179,7 @@ namespace test {
const auto HANDLER = TypeHandler::create<double>(RANDD);
const LocalTag TAG{defaultGen.u64()};
HashVal metaID{0};
HashVal typeID{0};
// this is our »allocated buffer«
double testBuffer{0};
@ -205,7 +188,7 @@ namespace test {
Thread testWorker{[&] /* === Use Case : a worker defines a new buffer type === */
{
auto& typeKey = stageAPI.defineBufferType (SIZ_D, HANDLER, TAG); // ◁─────────────────┨ data up-sync happens here
metaID = HashVal(typeKey); // sneak out to verify later
typeID = HashVal(typeKey); // sneak out to verify later
// emulate allocation and usage of a buffer
BuffAlloc allocRecord{alloc, typeKey.storageSize(),typeKey.localTag()};
@ -222,13 +205,13 @@ namespace test {
yield(); // wait for worker to finish
CHECK (0 != metaID);
CHECK (0 != typeID);
CHECK (3 == metaHub->cntEntries());
CHECK (metaHub->isKnown (metaID));
CHECK (metaHub->isKnown (typeID));
// complete registration chain has been published to the central registry
HashVal FAM_ID = localStage.getFamilyID();
HashVal parent1 = metaHub->lookup(metaID).parentKey();
HashVal parent1 = metaHub->lookup(typeID).parentKey();
CHECK (metaHub->isKnown (parent1));
CHECK (parent1 == metaHub->defineBufferType (FAM_ID, SIZ_D, HANDLER));
@ -240,9 +223,70 @@ namespace test {
}
/** @test simulate another common buffer provider usage situation,
* where a local instance starts using a type that was previously
* defined in the central registry.
* - setup an empty new EngineBufferMetadata end inject it as mock
* - this time however a new type is defined into this central registry
* - in a new worker thread, the local registry is initially empty
* - yet when a lookup fails, a down-sync from the global registry happens
* - after that the type is also known locally and can be used for allocations.
* - nothing is published back and so any state records remain conined
* to the current worker thread
*/
void
verify_fetchNewKeyOnUse()
{
lib::DependInject<EngineBufferMetadata>::Local<EngineBufferMetadata> metaHub;
// setup information used for buffer type registration
const size_t SIZ_D = sizeof(double);
const double RANDD = 1.0 + defaultGen.uni();
const auto HANDLER = TypeHandler::create<double>(RANDD);
LocalBufferStage localStage{"verify_fetchNewKeyOnUse"};
BufferProviderSetup::Stage& stageAPI = localStage;
HashVal FAM_ID = localStage.getFamilyID();
CHECK (FAM_ID == hash_value (Literal{"verify_fetchNewKeyOnUse"}));
metaHub.triggerCreate();
HashVal typeID = metaHub->defineBufferType (FAM_ID, SIZ_D, HANDLER);
CHECK (metaHub->isKnown(typeID));
CHECK (metaHub->lookup(typeID).storageSize() == SIZ_D);
CHECK (2 == metaHub->cntEntries());
Thread testWorker{[&] /* === Use Case : worker uses a buffer type defined "elsewhere" type === */
{
// initially the thread-local registry is empty
CHECK (0 == localStage.cntEntries());
auto& typeKey = stageAPI.lookup(typeID); // ◁─────────────────────────────────────────┨ data down-sync happens here
CHECK (typeKey != metadata::Key::INVALID);
CHECK (typeKey.storageSize() == SIZ_D);
CHECK (typeKey.localTag() == LocalTag::UNKNOWN);
CHECK (2 == localStage.cntEntries());
double testBuffer{0};
Buff* alloc = mark_as_Buffer (testBuffer);
// emulate allocation and usage of a buffer
BuffAlloc allocRecord{alloc, typeKey.storageSize(),typeKey.localTag()};
auto& stateKey = stageAPI.mark_locked (typeKey, allocRecord);
CHECK (3 == localStage.cntEntries()); // one additional (state)entry
// verify the buffer constructor was applied
CHECK (testBuffer = RANDD);
// release the buffer...
stageAPI.mark_released (stateKey);
stageAPI.discard (stateKey); // ◁──────────────────────────────────────────────────┨ buffer state entry never leaves the worker thread
CHECK (2 == localStage.cntEntries());
}};
while (testWorker)
yield(); // wait for worker to finish
// no new entries in central registry
CHECK (2 == metaHub->cntEntries());
}
};

View file

@ -144486,9 +144486,9 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<node CREATED="1776566302722" ID="ID_563548473" MODIFIED="1776566323627" TEXT="ein release / dtor-call kann nicht synchronisiert werden"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1776566422332" ID="ID_773286904" MODIFIED="1776604505367" TEXT="Verbindung zur zentral-Registry">
<icon BUILTIN="pencil"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1776566441432" ID="ID_83703051" MODIFIED="1776604516229" TEXT="neue Typ-Registrierung im Thread &#x27f9; zur Zentrale publiziert">
<node COLOR="#338800" CREATED="1776566422332" ID="ID_773286904" MODIFIED="1776707180399" TEXT="Verbindung zur zentral-Registry">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1776566441432" ID="ID_83703051" MODIFIED="1776707176040" TEXT="neue Typ-Registrierung im Thread &#x27f9; zur Zentrale publiziert">
<node CREATED="1776604591195" ID="ID_1133340482" MODIFIED="1776604688880" TEXT="brauche daf&#xfc;r die zentralen EngineMetadata">
<node COLOR="#5b280f" CREATED="1776604776169" ID="ID_1295263836" MODIFIED="1776604807258" TEXT="Aua &#x2014; jetzt gibt&apos;s doch eine statische Initialisierung">
<icon BUILTIN="button_cancel"/>
@ -144565,7 +144565,23 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1776566459396" ID="ID_20651536" MODIFIED="1776604516229" TEXT="Verwendung im Thread &#x27f8; Typ aus Zentrale synchronisiert"/>
<node COLOR="#435e98" CREATED="1776566459396" ID="ID_20651536" MODIFIED="1776707176041" TEXT="Verwendung im Thread &#x27f8; Typ aus Zentrale synchronisiert">
<node COLOR="#435e98" CREATED="1776704448745" ID="ID_1701721015" MODIFIED="1776707039908" TEXT="wo wird der down-sync eingeschleift?">
<icon BUILTIN="help"/>
<node CREATED="1776704463019" ID="ID_1647332020" MODIFIED="1776704470100" TEXT="mu&#xdf; in lookup() sitzen"/>
<node CREATED="1776706983787" ID="ID_1432209099" MODIFIED="1776707010485" TEXT="der wird n&#xe4;mlich auch bereits von BufferProvider::lockBuffer() verwendet"/>
<node CREATED="1776707011321" ID="ID_70753884" MODIFIED="1776707021020" TEXT="auch andere Informations-Funktionen machen einen lookup()"/>
</node>
<node COLOR="#338800" CREATED="1776707043517" ID="ID_1534309023" MODIFIED="1776707166222" TEXT="Test">
<icon BUILTIN="button_ok"/>
<node CREATED="1776707049394" ID="ID_1727759495" MODIFIED="1776707052989" TEXT="Aufbau ganz analog"/>
<node COLOR="#338800" CREATED="1776707072891" ID="ID_1256499400" MODIFIED="1776707169760" TEXT="mir fehlt ein Beleg da&#xdf; tats&#xe4;chlich eine Synchronisation stattfindet">
<icon BUILTIN="button_ok"/>
<node CREATED="1776707101847" ID="ID_1368500895" MODIFIED="1776707138447" TEXT="lege die cntEntries() auf die LocalBufferStage-Schnittstelle"/>
<node CREATED="1776707139396" ID="ID_1964413531" MODIFIED="1776707159816" TEXT="das ist nur f&#xfc;r den Test relevant (aber schadet nicht)"/>
</node>
</node>
</node>
</node>
</node>
<node CREATED="1776204575221" ID="ID_869340284" MODIFIED="1776204579271" TEXT="Store-Slice"/>