BufferProvider: implement locking and data synchronisation
...using the first usage situation as guideline, and verify proper operation through unit-test - the LocalBufferStage gets a set of thread-local BufferMetadata - furthermore, it accesses the central hub via dependency-injection - after each type registration ''from a worker thread'' data is published As an asside: ensure most metadata registration can ''move'' the TypeHandler
This commit is contained in:
parent
606d98bf9c
commit
abecd2cbde
8 changed files with 175 additions and 150 deletions
|
|
@ -84,11 +84,14 @@
|
|||
#include "lib/util.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace vault{
|
||||
namespace mem {
|
||||
|
||||
using std::move;
|
||||
using std::forward;
|
||||
using lib::HashVal;
|
||||
using util::unConst;
|
||||
|
||||
|
|
@ -157,7 +160,7 @@ namespace mem {
|
|||
namespace { // details of hash calculation
|
||||
template<typename VAL>
|
||||
HashVal
|
||||
chainedHash(HashVal accumulatedHash, VAL changedValue)
|
||||
chainedHash(HashVal accumulatedHash, VAL const& changedValue)
|
||||
{
|
||||
boost::hash_combine (accumulatedHash, changedValue);
|
||||
return accumulatedHash;
|
||||
|
|
@ -210,7 +213,7 @@ namespace mem {
|
|||
Key (Key const& parent, size_t differingStorageSize)
|
||||
: parent_(parent.hashID_)
|
||||
, hashID_(chainedHash (parent_, differingStorageSize))
|
||||
, storageSize_(differingStorageSize) // differing from parent
|
||||
, storageSize_(differingStorageSize) // ◁────────────────────────────────┨ differing from the parent
|
||||
, instanceFunc_(parent.instanceFunc_)
|
||||
, specifics_(parent.specifics_)
|
||||
{ }
|
||||
|
|
@ -220,11 +223,11 @@ namespace mem {
|
|||
* Using different ctor and dtor functions,
|
||||
* all else remaining the same as with parent
|
||||
*/
|
||||
Key (Key const& parent, TypeHandler const& differingTypeHandlerFunctions)
|
||||
Key (Key const& parent, TypeHandler differingTypeHandlerFunctions)
|
||||
: parent_(parent.hashID_)
|
||||
, hashID_(chainedHash (parent_, differingTypeHandlerFunctions))
|
||||
, storageSize_(parent.storageSize_)
|
||||
, instanceFunc_(differingTypeHandlerFunctions) // differing from parent
|
||||
, instanceFunc_(move(differingTypeHandlerFunctions)) // ◁─────────────────┨ differing from the parent
|
||||
, specifics_(parent.specifics_)
|
||||
{ }
|
||||
|
||||
|
|
@ -238,7 +241,7 @@ namespace mem {
|
|||
, hashID_(chainedHash (parent_, anotherTypeSpecificInternalTag))
|
||||
, storageSize_(parent.storageSize_)
|
||||
, instanceFunc_(parent.instanceFunc_)
|
||||
, specifics_(anotherTypeSpecificInternalTag) // differing from parent
|
||||
, specifics_(anotherTypeSpecificInternalTag) // ◁───────────────────────┨ differing from the parent
|
||||
{ }
|
||||
|
||||
|
||||
|
|
@ -600,12 +603,14 @@ namespace mem {
|
|||
* @remark in the Render Engine there can be several
|
||||
* BufferProvider services, while the BufferMetadata
|
||||
* uses a common setup with thread-local caches.
|
||||
* @note BufferMetadata is *not threadsafe*, thus the workers
|
||||
* rely on thread-local instances, while the central
|
||||
* EngineBufferMetadata is protected by a monitor.
|
||||
*/
|
||||
class BufferMetadata
|
||||
: util::NonCopyable
|
||||
{
|
||||
metadata::Table table_;
|
||||
///////////////////////////TICKET #854 : ensure proper locking happens "somewhere" when mutating metadata
|
||||
|
||||
public:
|
||||
using Key = metadata::Key;
|
||||
|
|
@ -629,7 +634,7 @@ namespace mem {
|
|||
* the third level. All these levels describe abstract type
|
||||
* keys, not entries for concrete buffers. The latter are
|
||||
* always created as children of a known type key.
|
||||
* @param familyID root anchor to keep mandators apart
|
||||
* @param familyID root anchor to keep the mandators apart
|
||||
*/
|
||||
Key
|
||||
key (HashVal familyID
|
||||
|
|
@ -641,7 +646,7 @@ namespace mem {
|
|||
Key typeKey = trackKey (familyID, storageSize);
|
||||
|
||||
if (nontrivial(instanceFunc))
|
||||
typeKey = trackKey (typeKey, instanceFunc);
|
||||
typeKey = trackKey (typeKey, move(instanceFunc));
|
||||
|
||||
if (nontrivial(specifics))
|
||||
typeKey = trackKey (typeKey, specifics);
|
||||
|
|
@ -651,9 +656,9 @@ namespace mem {
|
|||
|
||||
/** create a sub-type, using a different type/handler functor */
|
||||
Key
|
||||
key (Key const& parentKey, TypeHandler const& instanceFunc)
|
||||
key (Key const& parentKey, TypeHandler instanceFunc)
|
||||
{
|
||||
return trackKey (parentKey, instanceFunc);
|
||||
return trackKey (parentKey, move(instanceFunc));
|
||||
}
|
||||
|
||||
/** create a sub-type,
|
||||
|
|
@ -871,9 +876,9 @@ namespace mem {
|
|||
|
||||
template<typename PAR, typename DEF>
|
||||
Key
|
||||
trackKey (PAR parent, DEF specialisation)
|
||||
trackKey (PAR parent, DEF&& specialisation)
|
||||
{
|
||||
Key newKey{parent, specialisation};
|
||||
Key newKey{parent, forward<DEF>(specialisation)};
|
||||
maybeStore (newKey);
|
||||
return newKey;
|
||||
}
|
||||
|
|
@ -945,9 +950,9 @@ namespace mem {
|
|||
entry->mark(otherEntry.state());
|
||||
}
|
||||
else
|
||||
return true;
|
||||
return false;
|
||||
// something to synchronise => continue up the path
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ namespace mem {
|
|||
virtual void discard (HashVal stateKey) =0;
|
||||
virtual bool isAllotted (HashVal stateKey) const =0;
|
||||
virtual bool isAccessible (HashVal stateKey) const =0;
|
||||
virtual HashVal getFamilyID() const =0;
|
||||
};
|
||||
|
||||
class BufferStore
|
||||
|
|
|
|||
|
|
@ -149,7 +149,6 @@ namespace mem {
|
|||
|
||||
StageImp stage_;
|
||||
Tracker& tracker_;
|
||||
DiagnosticBufferProvider& provider_;
|
||||
|
||||
|
||||
/* === BufferStage proxy implementation === */
|
||||
|
|
@ -217,11 +216,12 @@ namespace mem {
|
|||
}
|
||||
|
||||
public:
|
||||
InstrumentedStageProxy (StageImp rawImpl, Tracker& tracker, DiagnosticBufferProvider& provider)
|
||||
InstrumentedStageProxy (StageImp rawImpl, Tracker& tracker)
|
||||
: stage_{move(rawImpl)}
|
||||
, tracker_{tracker}
|
||||
, provider_{provider}
|
||||
{ }
|
||||
|
||||
HashVal getFamilyID() const override { return stage_->getFamilyID(); }
|
||||
};
|
||||
|
||||
//(End) internal diagnostics helper
|
||||
|
|
@ -234,7 +234,7 @@ namespace mem {
|
|||
: NaiveBufferSetup{}
|
||||
, tracker_{std::make_unique<BlockTracker>()}
|
||||
{
|
||||
decorate<InstrumentedStageProxy> (bufferStage_, *tracker_, *this);
|
||||
decorate<InstrumentedStageProxy> (bufferStage_, *tracker_);
|
||||
}
|
||||
|
||||
DiagnosticBufferProvider::~DiagnosticBufferProvider()
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
#include "include/logging.h"
|
||||
#include "vault/mem/buffer-metadata.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/sync.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
|
@ -68,18 +69,24 @@ namespace mem {
|
|||
using std::move;
|
||||
using util::unConst;
|
||||
|
||||
using lib::Sync;
|
||||
using lib::RecursiveLock_NoWait;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Exchange hub for buffer metadata in the Render Engine.
|
||||
* @note all access is threadsafe (protected by mutex)
|
||||
* @remark in the Render Engine, the bulk of metadata handling
|
||||
* happens in the worker threads, using LocalBufferStage.
|
||||
*/
|
||||
class EngineBufferMetadata
|
||||
: util::NonCopyable
|
||||
, public Sync<RecursiveLock_NoWait>
|
||||
{
|
||||
BufferMetadata registry_;
|
||||
///////////////////////////TICKET #854 : ensure proper locking happens "somewhere" when mutating metadata
|
||||
|
||||
public:
|
||||
using Key = metadata::Key;
|
||||
|
|
@ -99,31 +106,39 @@ namespace mem {
|
|||
bool
|
||||
isKnown (HashVal metaID) const
|
||||
{
|
||||
Lock sync{this};
|
||||
return registry_.isKnown (metaID);
|
||||
}
|
||||
|
||||
bool
|
||||
isAllotted (HashVal metaID) const
|
||||
{
|
||||
Lock sync{this};
|
||||
return registry_.isLocked (metaID);
|
||||
}
|
||||
|
||||
bool
|
||||
isAccessible (HashVal metaID) const
|
||||
{
|
||||
Lock sync{this};
|
||||
return registry_.isAccessible (metaID);
|
||||
}
|
||||
|
||||
Key const&
|
||||
lookup (HashVal metaID) const
|
||||
{
|
||||
Lock sync{this};
|
||||
return isKnown (metaID)? registry_.get (metaID)
|
||||
: metadata::Key::INVALID;
|
||||
}
|
||||
|
||||
Key const&
|
||||
defineBufferType (HashVal familyID, size_t buffSiz, TypeHandler handlerFunctions, LocalTag localTag)
|
||||
defineBufferType (HashVal familyID
|
||||
,size_t buffSiz
|
||||
,TypeHandler handlerFunctions =TypeHandler::RAW
|
||||
,LocalTag localTag =LocalTag::UNKNOWN)
|
||||
{
|
||||
Lock sync{this};
|
||||
return lookup (registry_.key (familyID, buffSiz, move(handlerFunctions), localTag));
|
||||
} // possibly create new entry, and return (stable) reference to it
|
||||
|
||||
|
|
@ -134,6 +149,7 @@ namespace mem {
|
|||
bool
|
||||
abandon (HashVal metaID, bool invokeDtor =true)
|
||||
{
|
||||
Lock sync{this};
|
||||
Key const& key = lookup (metaID);
|
||||
Entry& metaEntry = static_cast<Entry&> (unConst(key));
|
||||
if (metaEntry.isTypeKey())
|
||||
|
|
@ -142,10 +158,26 @@ namespace mem {
|
|||
registry_.release (metaEntry);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** push a new metadata chain from the local registry into the central hub.
|
||||
* @remark typically required after defining a new buffer type locally */
|
||||
void
|
||||
propagateUp (HashVal metaID, BufferMetadata const& localRegistry)
|
||||
{
|
||||
Lock sync{this};
|
||||
registry_.import(metaID, localRegistry);
|
||||
}
|
||||
|
||||
/** retrieve a metadata chain from the central registry */
|
||||
void
|
||||
propagateDown (HashVal metaID, BufferMetadata& localRegistry) const
|
||||
{
|
||||
Lock sync{this};
|
||||
localRegistry.import(metaID, registry_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}} // namespace vault::mem
|
||||
#endif /*VAULT_MEM_ENGINE_BUFFR_METADAZ_H*/
|
||||
|
|
|
|||
|
|
@ -42,10 +42,11 @@
|
|||
#define VAULT_MEM_LOCAL_BUFFER_STAGE_H
|
||||
|
||||
|
||||
#include "vault/mem/buffer-metadata.hpp"
|
||||
#include "vault/mem/buffer-provider-setup.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include "vault/mem/engine-buffer-metadata.hpp"
|
||||
#include "vault/mem/buffer-metadata.hpp"
|
||||
#include "lib/local-slice.hpp"
|
||||
#include "lib/depend.hpp"
|
||||
|
||||
|
||||
namespace vault {
|
||||
|
|
@ -54,53 +55,67 @@ namespace mem {
|
|||
|
||||
|
||||
/**
|
||||
* Simple Buffer type and state tracking registry, for test and demonstration.
|
||||
* Relies on a central hashtable, without considering any concurrency concerns.
|
||||
* Buffer type and state tracking registry, specialised for running
|
||||
* in a worker thread and within a massively concurrent environment.
|
||||
* Requests are first and foremost handled by a thread-local registry.
|
||||
* Metadata is synchronised with a global registry only when types are
|
||||
* 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:
|
||||
*/
|
||||
class LocalBufferStage
|
||||
: public BufferProviderSetup::Stage
|
||||
{
|
||||
using LocalRegistry = lib::LocalSlice<BufferMetadata>;
|
||||
using EngineRegistry = lib::Depend<EngineBufferMetadata>;
|
||||
|
||||
HashVal familyID_;
|
||||
BufferMetadata metadata_;
|
||||
|
||||
LocalRegistry localReg_;
|
||||
EngineRegistry globalReg_;
|
||||
|
||||
|
||||
/* === BufferStage interface === */
|
||||
|
||||
ID
|
||||
lookup (HashVal key) override
|
||||
{
|
||||
return metadata_.isKnown(key)? metadata_.get (key)
|
||||
: metadata::Key::INVALID;
|
||||
return localReg_->isKnown(key)? localReg_->get (key)
|
||||
: metadata::Key::INVALID;
|
||||
}
|
||||
|
||||
bool
|
||||
isAllotted (HashVal stateKey) const override
|
||||
{
|
||||
return metadata_.isLocked (stateKey);
|
||||
return localReg_->isLocked (stateKey);
|
||||
}
|
||||
|
||||
bool
|
||||
isAccessible (HashVal stateKey) const override
|
||||
{
|
||||
return metadata_.isAccessible (stateKey);
|
||||
return localReg_->isAccessible (stateKey);
|
||||
}
|
||||
|
||||
ID
|
||||
defineBufferType (size_t buffSiz, TypeHandler handlerFunctions, LocalTag localTag)
|
||||
{
|
||||
return lookup (metadata_.key (familyID_, buffSiz, move (handlerFunctions), localTag));
|
||||
} // deliberately: maybe create storage, and return reference to it
|
||||
ID typeKey = lookup (localReg_->key (familyID_, buffSiz, move (handlerFunctions), localTag));
|
||||
// possibly create new entry, and retrieve (stable) reference
|
||||
globalReg_().propagateUp (typeKey, *localReg_);
|
||||
return typeKey;
|
||||
}
|
||||
|
||||
ID
|
||||
mark_locked (ID typeKey, BuffAlloc alloc) override
|
||||
{
|
||||
auto& [storage,size,implMark] = alloc;
|
||||
return metadata_.markLocked (typeKey, storage, size, implMark);
|
||||
return localReg_->markLocked (typeKey, storage, size, implMark);
|
||||
}
|
||||
|
||||
ID
|
||||
mark_emitted (HashVal stateKey) override
|
||||
{
|
||||
metadata::Entry& metaEntry = metadata_.get (stateKey);
|
||||
metadata::Entry& metaEntry = localReg_->get (stateKey);
|
||||
metaEntry.mark(EMITTED);
|
||||
return metaEntry; // contains also the key
|
||||
}
|
||||
|
|
@ -108,7 +123,7 @@ namespace mem {
|
|||
ID
|
||||
mark_released (HashVal stateKey) override
|
||||
{
|
||||
metadata::Entry& metaEntry = metadata_.get (stateKey);
|
||||
metadata::Entry& metaEntry = localReg_->get (stateKey);
|
||||
metaEntry.mark(FREE); // might invoke embedded dtor function
|
||||
return metaEntry;
|
||||
}
|
||||
|
|
@ -116,7 +131,7 @@ namespace mem {
|
|||
ID
|
||||
abandon (HashVal stateKey, bool invokeDtor) override
|
||||
{
|
||||
metadata::Entry& metaEntry = metadata_.get (stateKey);
|
||||
metadata::Entry& metaEntry = localReg_->get (stateKey);
|
||||
metaEntry.invalidate (invokeDtor);
|
||||
return metaEntry;
|
||||
}
|
||||
|
|
@ -124,15 +139,16 @@ namespace mem {
|
|||
void
|
||||
discard (HashVal stateKey) override
|
||||
{
|
||||
metadata_.release (stateKey);
|
||||
localReg_->release (stateKey);
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
LocalBufferStage (Literal implementationID)
|
||||
: familyID_{hash_value (implementationID)}
|
||||
, metadata_{}
|
||||
{ }
|
||||
|
||||
HashVal getFamilyID() const override { return familyID_; }
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@
|
|||
#include "vault/mem/buffer-metadata.hpp"
|
||||
#include "vault/mem/buffer-provider-setup.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace vault {
|
||||
namespace mem {
|
||||
|
|
@ -115,6 +113,8 @@ namespace mem {
|
|||
: familyID_{hash_value (implementationID)}
|
||||
, metadata_{}
|
||||
{ }
|
||||
|
||||
HashVal getFamilyID() const override { return familyID_; }
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -56,15 +56,6 @@ namespace test {
|
|||
|
||||
namespace { // Test fixture
|
||||
|
||||
template<typename TY>
|
||||
TY&
|
||||
accessAs (metadata::Entry& entry)
|
||||
{
|
||||
TY* ptr = reinterpret_cast<TY*> (entry.access());
|
||||
ASSERT (ptr);
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
template<typename X>
|
||||
Buff*
|
||||
mark_as_Buffer(X& something)
|
||||
|
|
@ -72,16 +63,6 @@ namespace test {
|
|||
return reinterpret_cast<Buff*> (std::addressof(something));
|
||||
}
|
||||
|
||||
// struct TestBuff
|
||||
// : lib::UninitialisedStorage<TestFrame>
|
||||
// {
|
||||
//
|
||||
// };
|
||||
const size_t TEST_MAX_SIZE = 1024 * 1024;
|
||||
|
||||
HashVal JUST_SOMETHING = 123;
|
||||
auto SOME_POINTER = mark_as_Buffer(JUST_SOMETHING);
|
||||
|
||||
}//(End) Test fixture and helpers
|
||||
|
||||
|
||||
|
|
@ -108,6 +89,7 @@ namespace test {
|
|||
}
|
||||
|
||||
|
||||
|
||||
/** @test key-chains can be exported and imported */
|
||||
void
|
||||
verify_DataTransfer()
|
||||
|
|
@ -182,6 +164,7 @@ namespace test {
|
|||
}
|
||||
|
||||
|
||||
|
||||
/** @test simulate a common buffer provider usage situation,
|
||||
* whereby a local instance in some worker thread provides
|
||||
* a new (or refined) buffer type.
|
||||
|
|
@ -191,16 +174,17 @@ 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
|
||||
*/
|
||||
void
|
||||
verify_publishNewKey()
|
||||
{
|
||||
// provide a »central Metadata hub«
|
||||
// provide a »central Metadata hub« (as a transient instance for this test)
|
||||
lib::DependInject<EngineBufferMetadata>::Local<EngineBufferMetadata> metaHub;
|
||||
metaHub.triggerCreate();
|
||||
CHECK (0 == metaHub->cntEntries());
|
||||
CHECK (0 == metaHub->cntEntries()); // no buffer metadata yet...
|
||||
|
||||
// provide the test subject,
|
||||
// provide the *test subject*,
|
||||
// which implements the BufferStage API
|
||||
// and uses a thread-local metadata table internally
|
||||
LocalBufferStage localStage{"verify_publishNewKey"};
|
||||
|
|
@ -218,9 +202,9 @@ namespace test {
|
|||
double testBuffer{0};
|
||||
Buff* alloc = mark_as_Buffer (testBuffer);
|
||||
|
||||
Thread testWorker{[&]
|
||||
Thread testWorker{[&] /* === Use Case : a worker defines a new buffer type === */
|
||||
{
|
||||
auto& typeKey = stageAPI.defineBufferType (SIZ_D, HANDLER, TAG);
|
||||
auto& typeKey = stageAPI.defineBufferType (SIZ_D, HANDLER, TAG); // ◁─────────────────┨ data up-sync happens here
|
||||
metaID = HashVal(typeKey); // sneak out to verify later
|
||||
|
||||
// emulate allocation and usage of a buffer
|
||||
|
|
@ -232,15 +216,27 @@ namespace test {
|
|||
|
||||
// release the buffer...
|
||||
stageAPI.mark_released (stateKey);
|
||||
stageAPI.discard (stateKey);
|
||||
stageAPI.discard (stateKey); // ◁──────────────────────────────────────────────────┨ buffer state entry never leaves the worker thread
|
||||
}};
|
||||
while (testWorker)
|
||||
yield(); // wait for worker to finish
|
||||
|
||||
|
||||
CHECK (0 != metaID);
|
||||
SHOW_EXPR(metaHub->cntEntries());
|
||||
/////////////////////////////////////////OOO verify the complete chain from the type key to the root
|
||||
CHECK (3 == metaHub->cntEntries());
|
||||
CHECK (metaHub->isKnown (metaID));
|
||||
|
||||
// complete registration chain has been published to the central registry
|
||||
HashVal FAM_ID = localStage.getFamilyID();
|
||||
HashVal parent1 = metaHub->lookup(metaID).parentKey();
|
||||
CHECK (metaHub->isKnown (parent1));
|
||||
CHECK (parent1 == metaHub->defineBufferType (FAM_ID, SIZ_D, HANDLER));
|
||||
|
||||
HashVal parent0 = metaHub->lookup(parent1).parentKey();
|
||||
CHECK (metaHub->isKnown (parent0));
|
||||
CHECK (parent0 == metaHub->defineBufferType (FAM_ID, SIZ_D));
|
||||
|
||||
CHECK (FAM_ID == metaHub->lookup(parent0).parentKey());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -144225,9 +144225,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<icon BUILTIN="pencil"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1776293021744" ID="ID_290625266" MODIFIED="1776522855937" TEXT="Problem: dieses Setup ist effektiv statisch/global">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Das erfordert Vorsicht, weil man dadurch die Eigenschaft einer geschlossenen Service-Instanz verliert; es ist also leider kein drop-in-replacement, mit dem man einfach eine tief eingebettete Koponente thread-local macht; theoretisch würde letzteres zwar gehen, aber dann auf einen zweistufigen Dispatch hinauslaufen, d.h. nach dem thread-local-Zugriff müßte noch ein Dispatch über eine Map per umschließender Service-Instanz erfolgen.
|
||||
|
|
@ -144338,9 +144336,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1776521876873" ID="ID_1636226712" MODIFIED="1776522039333" TEXT="puh ... das wird in jedem Fall hässlich">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
man muß den aktuellen Namespace schließen, in lib gehen, dort die explizite Spezialisierung definieren...
|
||||
|
|
@ -144354,9 +144350,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
<node CREATED="1776521973780" ID="ID_1887344423" MODIFIED="1776522004871" TEXT="default ist dann LocalSlice_DefaultSetup<SRV>">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
direkt bei LocalSlice definiert; erzeugt eine default-konstruierte Instanz vom Service
|
||||
|
|
@ -144425,46 +144419,38 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1776556823233" ID="ID_964120138" MODIFIED="1776556838587" TEXT="State darf nur erlaubten Transitionen folgen"/>
|
||||
<node CREATED="1776556866299" ID="ID_1595480658" MODIFIED="1776557157499" TEXT="setzt implizit einen logisch kohärenten Aufruf der Synchronisation vorraus">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Das bedeutet, die hier aufgestellten Regeln/Verbote sind so konstruiert, daß sie bei einem sinnvollen Abgleich niemals auftreten können.  Der Sinn ergibt sich dabei aus den vorgesehenen Zustandsübergängen; es sollten nur Typ-Informationen angereichert werden, und konkrete Einträge sollten eigentlich gar nicht synchronisiert werden; hilfsweise sehe ich die Möglichkeit vor, daß konkrete Einträge sich gemäß State-Machine vorwärts bewegen (falls wir tatsächlich konkrete Einträge zentral ablegen, wenn sie in den Cache wandern — ich kann diese Möglichkeit derzeit nicht ausschließen)
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1776557583845" ID="ID_1234349568" MODIFIED="1776566359211" TEXT="Schnittstelle / Mechanismus">
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1776557583845" ID="ID_1234349568" MODIFIED="1776639506739" TEXT="Schnittstelle / Mechanismus">
|
||||
<linktarget COLOR="#4898e1" DESTINATION="ID_1234349568" ENDARROW="Default" ENDINCLINATION="-650;566;" ID="Arrow_ID_82908836" SOURCE="ID_1544517395" STARTARROW="None" STARTINCLINATION="-183;12;"/>
|
||||
<icon BUILTIN="list"/>
|
||||
<node CREATED="1776557598572" ID="ID_930030080" MODIFIED="1776557685683" TEXT="hier tritt das Problem der Storage auf">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
es sollte nur alloziert werden, wenn das auch notwendig ist; man könnte entweder einen Buffer vorsehen, oder direkt von Quelle zu Ziel arbeiten.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1776557687442" ID="ID_1438523066" MODIFIED="1776557735700" TEXT="Einsicht: das hier ist Implementierung / es geht nur um Komplexität">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
es müssen keine Komponenten gegeneinander isoliert werden; die Schnittstelle dient nur dazu, den vorgang leicht beherrschbar zu machen
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1776557770647" ID="ID_1041865636" MODIFIED="1776557793535" TEXT="⟹ Operation sitzt auf dem Empfänger und bekommt die Quelle als const"/>
|
||||
<node CREATED="1776557806144" ID="ID_1595165986" MODIFIED="1776557808303" TEXT="Logik">
|
||||
|
|
@ -144477,16 +144463,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<arrowlink DESTINATION="ID_1185060059" ENDARROW="Default" ENDINCLINATION="102;0;" ID="Arrow_ID_680660049" STARTARROW="None" STARTINCLINATION="102;0;"/>
|
||||
<node CREATED="1776604115107" HGAP="30" ID="ID_1186161854" MODIFIED="1776604293378" TEXT="aber nur wenn für den aktuellen Eintrag was zu tun war" VSHIFT="11">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
...damit kann man in der Regel das rekursive Traversieren der Kette vorzeitig abbrechen; das beruht auf einem »Invarianten-Argument«: Daten in der Registry sind stets konsistent, was impliziert, daß wir auch den Parent-Key bereits gespeichert haben, entweder durch reguläres Anlegen, oder durch eine vorausgegangenen Synchronisation
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
|
|
@ -144513,35 +144496,36 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1776604808417" ID="ID_1839658680" MODIFIED="1776604847186" TEXT="besser über lib::Depend lösen ⟹ initialisiert sich lazy"/>
|
||||
<node CREATED="1776604894666" ID="ID_1374623359" MODIFIED="1776605011172" TEXT="wird nur relevant an den zweien hier besprochenen Synchronisations-Szenarien">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
also entweder wenn jeman eine lokale Instanz aufruft, um einen neuen Typ zu registrieren, oder wenn ein lokal nicht bekannter type-Key auftritt
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1776617913235" ID="ID_484033659" MODIFIED="1776617923691" TEXT="muß Synchronisationspunkt einschleifen">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node COLOR="#338800" CREATED="1776617913235" ID="ID_484033659" MODIFIED="1776639405911" TEXT="muß Synchronisationspunkt einschleifen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#435e98" CREATED="1776639369543" ID="ID_757411317" MODIFIED="1776639385010" TEXT="verwende getrennte Funktionen für up-sync und down-sync">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1776604670575" ID="ID_1448023528" MODIFIED="1776619327263" TEXT="Test">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1776639386094" ID="ID_808614387" MODIFIED="1776639429905" TEXT="baue bei der Gelegenheit auch gleich die globale Mutex-Sync ein">
|
||||
<arrowlink COLOR="#4ff278" DESTINATION="ID_419155046" ENDARROW="Default" ENDINCLINATION="1269;0;" ID="Arrow_ID_703288918" STARTARROW="None" STARTINCLINATION="1269;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1776604670575" ID="ID_1448023528" MODIFIED="1776639366613" TEXT="Test">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#338800" CREATED="1776604673415" ID="ID_1473562963" MODIFIED="1776612748341" TEXT="die zentralen EngineMetadata mocken">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
soll auf jeden Fall ausschließen, daß dieser Test die zentral-Instanz verschmutzt
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1776612751815" ID="ID_423973357" MODIFIED="1776612774748" TEXT="verifiziert(Debugger): die globale Instanz ist vorher undefiniert"/>
|
||||
<node CREATED="1776612775704" ID="ID_1966664848" MODIFIED="1776612787131" TEXT="und wird nachher wieder in undefinierten Zustand zurückversetzt"/>
|
||||
|
|
@ -144555,28 +144539,27 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node COLOR="#338800" CREATED="1776617832875" ID="ID_1106246702" MODIFIED="1776617844968" TEXT="Daten für Typ-Registrierung vorbereiten">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1776617847069" ID="ID_1390721757" MODIFIED="1776617855687" TEXT="aus dem Thread einen Typ registrieren">
|
||||
<node COLOR="#435e98" CREATED="1776617847069" ID="ID_1390721757" MODIFIED="1776639361490" TEXT="aus dem Thread einen Typ registrieren">
|
||||
<node COLOR="#338800" CREATED="1776617861154" ID="ID_1231327024" MODIFIED="1776617886524" TEXT="Meta-Entry wurde im Thread angelegt">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1776617887545" ID="ID_469498353" MODIFIED="1776619185986" TEXT="Metadaten wurden synchronisiert in den EngineBufferMetadata-Hub">
|
||||
<icon BUILTIN="flag-pink"/>
|
||||
<node COLOR="#338800" CREATED="1776617887545" ID="ID_469498353" MODIFIED="1776639340169" TEXT="Metadaten wurden synchronisiert in den EngineBufferMetadata-Hub">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1776619194372" ID="ID_1251103269" MODIFIED="1776619205443" TEXT="außerdem die internen Schritte für eine Allokation machen">
|
||||
<node CREATED="1776619206646" ID="ID_419064528" MODIFIED="1776619213721" TEXT="aus BufferProvider kopieren..."/>
|
||||
<node COLOR="#435e98" CREATED="1776619194372" ID="ID_1251103269" MODIFIED="1776639361489" TEXT="außerdem die internen Schritte für eine Allokation machen">
|
||||
<node COLOR="#435e98" CREATED="1776619206646" ID="ID_419064528" MODIFIED="1776639345520" TEXT="aus BufferProvider kopieren...">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1776619214497" ID="ID_931246531" MODIFIED="1776619325326" TEXT="Buffer-Konstruktor wurde tatsächlich aufgerufen">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Ich verwende hier einen TypeHandler, der einen vorher im Test festgelegten random-double-Wert in den buffer pflanzt. Da wir bisher noch den Bug mit dem TypeHandler-Aliasing haben, gehat das »knapp am Abgrund vorbei« (aber wir verwenden ja eine komplett leere BufferMetadata registry....
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
|
|
@ -144649,36 +144632,29 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1776204765269" ID="ID_1404790840" MODIFIED="1776204785977" TEXT="EngineBufferMetadata">
|
||||
<node CREATED="1776605957061" ID="ID_405169256" MODIFIED="1776606058887" TEXT="eigenständiger Service">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
zwar könnte man einfach eine Instanz von BufferMetadata hinstellen — ich halte das aber nicht für klug, denn BufferMetadata ist Implementierungs-Code (und ich bin nicht glücklich damit, wiewohl ich keine Argumente dagegen finde)
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1776606067153" ID="ID_1057221878" MODIFIED="1776611491005" TEXT="bekommt ein reduziertes Interface">
|
||||
<node COLOR="#435e98" CREATED="1776606067153" ID="ID_1057221878" MODIFIED="1776639521908" TEXT="bekommt ein reduziertes Interface">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Keine Funktionalität zum Bedienen konkreter Allokationen
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1776611243004" ID="ID_1857604671" MODIFIED="1776611263510" TEXT="nehme die SimpleBufferStateRegistry als Vorbild"/>
|
||||
<node CREATED="1776611264755" ID="ID_403795481" MODIFIED="1776611278322" TEXT="lasse alle Allokations-bezogenen Aufrufe weg"/>
|
||||
<node CREATED="1776611279319" ID="ID_436854947" MODIFIED="1776611476802" TEXT="biete eine generalisierte discard()-Operation">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<ul>
|
||||
<li>
|
||||
|
|
@ -144695,35 +144671,34 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</html></richcontent>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1776639443069" ID="ID_1544517395" MODIFIED="1776639513459" TEXT="zusätzlich Funktionen für Daten-Abgleich">
|
||||
<arrowlink COLOR="#4898e1" DESTINATION="ID_1234349568" ENDARROW="Default" ENDINCLINATION="-650;566;" ID="Arrow_ID_82908836" STARTARROW="None" STARTINCLINATION="-183;12;"/>
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1776605772600" ID="ID_1300641128" MODIFIED="1776605856910" TEXT="muß aber auf Vault-Level definiert sein">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
darf also keinerlei Funktionalität aus dem Steam-Layer voraussetzen
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1776605858192" ID="ID_563549971" MODIFIED="1776605951752" TEXT="bleibe aber bei dem Namen — weil er klar ist">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
das hier ist keine generische Library, sondern ein Application-Framework. Auch »Vault« ist inhaltlich genau auf Lumiera ausgerichtet, der Unterschied liegt nur im Grad der Integration mit Application-level Services
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1776611511064" ID="ID_419155046" MODIFIED="1776611525083" TEXT="Mutex-Locking einführen">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node COLOR="#338800" CREATED="1776611511064" ID="ID_419155046" MODIFIED="1776639433744" TEXT="Mutex-Locking einführen">
|
||||
<linktarget COLOR="#4ff278" DESTINATION="ID_419155046" ENDARROW="Default" ENDINCLINATION="1269;0;" ID="Arrow_ID_703288918" SOURCE="ID_808614387" STARTARROW="None" STARTINCLINATION="1269;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1776204795573" ID="ID_1001789565" MODIFIED="1776204802934" TEXT="EngineBufferManager"/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue