BufferProvider: draft setup for engine-wide coordination hub
What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
This commit is contained in:
parent
1601838037
commit
57cd51e3a2
6 changed files with 367 additions and 15 deletions
|
|
@ -2,7 +2,7 @@
|
|||
BUFFER-METADATA.hpp - internal metadata for data buffer providers
|
||||
|
||||
Copyright (C)
|
||||
2011, Hermann Vosseler <Ichthyostega@web.de>
|
||||
2011,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
|
||||
|
|
@ -41,11 +41,29 @@
|
|||
**
|
||||
** @todo 2/2026 After refactoring the BufferProvider and splitting up the implementation
|
||||
** in two realms (state and storage), the purpose of this code her became much
|
||||
** clearer. The problem remains however, that the BufferMetadata directly links
|
||||
** to the one central metadata hashtable. We'll have to reconsider this aspect
|
||||
** once we have to build a metadata store that works well in a massively
|
||||
** concurrent environment, where requests for state transition emanate
|
||||
** any time form within all worker threads!
|
||||
** clearer. The problem remains however, that the BufferMetadata directly
|
||||
** maintains its metadata in an embedded hashtable. This is problematic
|
||||
** when operating within a massively concurrent environment.
|
||||
** @todo 4/2026 After resolving the problems related to BufferProvidder and OutputSlot,
|
||||
** the next task is to build a first draft implementation for a production-grade
|
||||
** buffer allocator and manager. I decided to employ thread-local »satellite«
|
||||
** tables, and a central EngineBufferMetadata hub. It remains to be seen
|
||||
** if this design is sustainable.
|
||||
**
|
||||
** @warning BufferMetadata entails an intricate setup of chained hash keys, which relies
|
||||
** on several implicit assumptions regarding consistency. These are largely unchecked
|
||||
** (beyond the unit test) and guaranteed only through the call sequence in proper use.
|
||||
** - there are Key entries that represent a _buffer type_ — these must not be mutated.
|
||||
** - the hash value for these typeKeys is totally determined by their properties
|
||||
** - a child entry can only vary _one_ of these properties, and the change should
|
||||
** be applied in the fixed sequence as defined in BufferMetadata::key()
|
||||
** - any violation to this scheme might lead to *duplicate entries* -> buffer corruption
|
||||
** - active state entries should only added as leaves, and should be removed after use.
|
||||
** - the hash of an active entry must reflect its associated memory address.
|
||||
** - Warning: this is only ensured by invoking Key::forEntry, but not persisted in the
|
||||
** Key itself. A directly computed hash of such a Key differs from the explicitly
|
||||
** associated hashID_. If the associated buffer* is changed, the entry can not be
|
||||
** found anymore, which might lead to double allocation and memory corruption.
|
||||
**
|
||||
** @see buffer-provider.hpp
|
||||
** @see BufferMetadata_test
|
||||
|
|
@ -591,11 +609,9 @@ namespace mem {
|
|||
using Entry = metadata::Entry;
|
||||
|
||||
/** establish a metadata registry.
|
||||
* Such will maintain a family of buffer type entries
|
||||
* Such will maintain families of chained buffer type entries
|
||||
* and provide a service for storing and retrieving metadata
|
||||
* for concrete buffer entries associated with these types.
|
||||
* @param implementationID to distinguish families of
|
||||
* type keys belonging to different registries.
|
||||
*/
|
||||
BufferMetadata()
|
||||
: table_{}
|
||||
|
|
|
|||
68
src/vault/mem/engine-buffer-manager.hpp
Normal file
68
src/vault/mem/engine-buffer-manager.hpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
ENGINE-BUFFER-MANAGER.hpp - central buffer allocation coordinator
|
||||
|
||||
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 engine-buffer-manager.hpp
|
||||
** Central hub of buffer management within the Lumiera Render Engine.
|
||||
**
|
||||
** @todo WIP-WIP-WIP 4/2026
|
||||
**
|
||||
** @see buffer-provider.hpp
|
||||
** @see LocalBufferStore_test
|
||||
** @see BufferProviderProtocol_test
|
||||
*/
|
||||
|
||||
#ifndef VAULT_MEM_ENGINE_BUFFR_MANAGA_H
|
||||
#define VAULT_MEM_ENGINE_BUFFR_MANAGA_H
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/hash-value.h"
|
||||
#include "include/logging.h"
|
||||
#include "vault/mem/buffhandle.hpp"
|
||||
#include "vault/mem/type-handler.hpp"
|
||||
#include "vault/mem/buffer-local-tag.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace vault{
|
||||
namespace mem {
|
||||
|
||||
using lib::HashVal;
|
||||
using util::unConst;
|
||||
|
||||
namespace err = lumiera::error;
|
||||
|
||||
|
||||
|
||||
|
||||
/** */
|
||||
|
||||
|
||||
|
||||
|
||||
namespace { // internal blah
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* === deaf === */
|
||||
|
||||
|
||||
|
||||
|
||||
}} // namespace vault::mem
|
||||
#endif /*VAULT_MEM_ENGINE_BUFFR_MANAGA_H*/
|
||||
145
src/vault/mem/engine-buffer-metadata.hpp
Normal file
145
src/vault/mem/engine-buffer-metadata.hpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
ENGINE-BUFFER-METADATA.hpp - central buffer metadata registry
|
||||
|
||||
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 engine-buffer-metadata.hpp
|
||||
** Coordination of buffer type registration at Render Engine level.
|
||||
** Within the Render Node Network, access to buffer management is abstracted in the form
|
||||
** of [Buffer Descriptors](\ref BuffDescr) and [Buffer Handles](\ref BuffHandle). And since
|
||||
** processing of media data is organised into jobs that are performed by worker(threads),
|
||||
** the necessity arises to handle parts of the buffer allocation and coordination locally,
|
||||
** within thread-local »satellite« services — which implies there must be some central hub
|
||||
** to exchange and coordinate metadata globally. Data processing within the Engine requires
|
||||
** all kind of different buffer sizes and memory layout, and so this diversity is abstracted
|
||||
** into the notion of a **Buffer Type**. The corresponding metadata entry defines, first and
|
||||
** foremost, the _size_ of the allocation. And, in addition, it _can_ define a TypeHandler
|
||||
** with a constructor- and destructor functor, to allow automated setup of specialised layout
|
||||
** within the buffer allocation. Furthermore, a LocalTag can be added that provides additional
|
||||
** implementation level instructions to the underlying memory manager.
|
||||
**
|
||||
** # Setup
|
||||
**
|
||||
** The EngineBufferMetadata hub is a central service, to be accessed through dependency-injection.
|
||||
** A single service instance will be created as part of booting up the Render Engine. At this point,
|
||||
** a corresponding setup for the BufferProvider is established, which relies on thread-local parts
|
||||
** within the implementation backend services. Notable the BufferProvider::BufferStage implementation,
|
||||
** which is provided by class LocalBufferStage, establishes a buffer metadata registry table for each
|
||||
** worker thread; under specific and well defined conditions, at certain points in the buffer lifecycle
|
||||
** a synchronisation must be made with the central metadata hub, which also involves a global lock to
|
||||
** prevent data corruption by concurrent access, since at those synchronisation points, a complete
|
||||
** exchange of metadata must be ensured, and completed in a single synchronous call. These data
|
||||
** synchronisation activities are initiated by the thread-local BufferMetadata tables, and the
|
||||
** implementation will rely on lib::Depend<EngineBufferMetadata> for access.
|
||||
**
|
||||
** @todo WIP-WIP 4/2026 prototyping to work towards a buffer management scheme.
|
||||
**
|
||||
** @see LocalBufferStage_test
|
||||
** @see BufferMetadata_test
|
||||
** @see buffer-provider.hpp
|
||||
** @see BufferProviderProtocol_test
|
||||
*/
|
||||
|
||||
#ifndef VAULT_MEM_ENGINE_BUFFR_METADAZ_H
|
||||
#define VAULT_MEM_ENGINE_BUFFR_METADAZ_H
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
//#include "lib/hash-value.h"
|
||||
#include "include/logging.h"
|
||||
#include "vault/mem/buffer-metadata.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace vault{
|
||||
namespace mem {
|
||||
// namespace err = lumiera::error;
|
||||
|
||||
using std::move;
|
||||
using util::unConst;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Exchange hub for buffer metadata in the Render Engine.
|
||||
*/
|
||||
class EngineBufferMetadata
|
||||
: util::NonCopyable
|
||||
{
|
||||
BufferMetadata registry_;
|
||||
///////////////////////////TICKET #854 : ensure proper locking happens "somewhere" when mutating metadata
|
||||
|
||||
public:
|
||||
using Key = metadata::Key;
|
||||
using Entry = metadata::Entry;
|
||||
|
||||
EngineBufferMetadata()
|
||||
: registry_{}
|
||||
{ }
|
||||
|
||||
|
||||
bool
|
||||
isKnown (HashVal metaID) const
|
||||
{
|
||||
return registry_.isKnown (metaID);
|
||||
}
|
||||
|
||||
bool
|
||||
isAllotted (HashVal metaID) const
|
||||
{
|
||||
return registry_.isLocked (metaID);
|
||||
}
|
||||
|
||||
bool
|
||||
isAccessible (HashVal metaID) const
|
||||
{
|
||||
return registry_.isAccessible (metaID);
|
||||
}
|
||||
|
||||
Key const&
|
||||
lookup (HashVal metaID) const
|
||||
{
|
||||
return isKnown (metaID)? registry_.get (metaID)
|
||||
: metadata::Key::INVALID;
|
||||
}
|
||||
|
||||
Key const&
|
||||
defineBufferType (HashVal familyID, size_t buffSiz, TypeHandler handlerFunctions, LocalTag localTag)
|
||||
{
|
||||
return lookup (registry_.key (familyID, buffSiz, move(handlerFunctions), localTag));
|
||||
} // possibly create new entry, and return (stable) reference to it
|
||||
|
||||
|
||||
/** @warning potentially dangerous;
|
||||
* must ensure that the entry is not used and registered in any worker thread!
|
||||
*/
|
||||
bool
|
||||
abandon (HashVal metaID, bool invokeDtor =true)
|
||||
{
|
||||
Key const& key = lookup (metaID);
|
||||
Entry& metaEntry = static_cast<Entry&> (unConst(key));
|
||||
if (metaEntry.isTypeKey())
|
||||
return false;
|
||||
metaEntry.invalidate (invokeDtor);
|
||||
registry_.release (metaEntry);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}} // namespace vault::mem
|
||||
#endif /*VAULT_MEM_ENGINE_BUFFR_METADAZ_H*/
|
||||
|
|
@ -69,7 +69,7 @@ namespace mem {
|
|||
ID
|
||||
defineBufferType (size_t buffSiz, TypeHandler handlerFunctions, LocalTag localTag)
|
||||
{
|
||||
return lookup (metadata_.key (familyID_, buffSiz, move (handlerFunctions), localTag));
|
||||
return lookup (metadata_.key (familyID_, buffSiz, move(handlerFunctions), localTag));
|
||||
} // deliberately: maybe create storage, and return reference to it
|
||||
|
||||
ID
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@
|
|||
#include "test/test-frame.hpp"
|
||||
#include "test/test-helper.hpp"
|
||||
#include "vault/mem/buffhandle.hpp"
|
||||
#include "vault/mem/buffer-metadata.hpp"
|
||||
#include "vault/mem/engine-buffer-metadata.hpp"
|
||||
#include "vault/mem/local-buffer-stage.hpp"
|
||||
#include "lib/depend-inject.hpp"
|
||||
#include "lib/symbol.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
|
|
@ -177,6 +178,7 @@ namespace test {
|
|||
void
|
||||
verify_publishNewKey()
|
||||
{
|
||||
lib::DependInject<EngineBufferMetadata>::Local<EngineBufferMetadata> metaHub;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -144503,9 +144503,51 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1776566302722" ID="ID_563548473" MODIFIED="1776566323627" TEXT="ein release / dtor-call kann nicht synchronisiert werden"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1776566422332" ID="ID_773286904" MODIFIED="1776566436334" TEXT="Verbindung zur zentral-Registry">
|
||||
<node CREATED="1776566441432" ID="ID_83703051" MODIFIED="1776566458537" TEXT="neue Typ-Registrierung im Thread ⟹ zur Zentrale publiziert"/>
|
||||
<node CREATED="1776566459396" ID="ID_20651536" MODIFIED="1776566492396" TEXT="Verwendung im Thread ⟸ Typ aus Zentrale synchronisiert"/>
|
||||
<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 ⟹ zur Zentrale publiziert">
|
||||
<node CREATED="1776604591195" ID="ID_1133340482" MODIFIED="1776604688880" TEXT="brauche dafür die zentralen EngineMetadata">
|
||||
<node COLOR="#5b280f" CREATED="1776604776169" ID="ID_1295263836" MODIFIED="1776604807258" TEXT="Aua — jetzt gibt's doch eine statische Initialisierung">
|
||||
<icon BUILTIN="button_cancel"/>
|
||||
</node>
|
||||
<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>
|
||||
<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>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1776604670575" ID="ID_1448023528" MODIFIED="1776604672346" TEXT="Test">
|
||||
<node COLOR="#338800" CREATED="1776604673415" ID="ID_1473562963" MODIFIED="1776612748341" TEXT="die zentralen EngineMetadata mocken">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
soll auf jeden Fall ausschließen, daß dieser Test die zentral-Instanz verschmutzt
|
||||
</p>
|
||||
</body>
|
||||
</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"/>
|
||||
<node CREATED="1776612790102" ID="ID_798359000" MODIFIED="1776612794696" TEXT="geiler code">
|
||||
<icon BUILTIN="ksmiletris"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1776566459396" ID="ID_20651536" MODIFIED="1776604516229" TEXT="Verwendung im Thread ⟸ Typ aus Zentrale synchronisiert"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1776204575221" ID="ID_869340284" MODIFIED="1776204579271" TEXT="Store-Slice"/>
|
||||
|
|
@ -144569,7 +144611,86 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
<node CREATED="1776204702568" ID="ID_1197574712" MODIFIED="1776204706747" TEXT="WorkerBufferStore"/>
|
||||
<node CREATED="1776204787207" ID="ID_1425789996" MODIFIED="1776204792254" TEXT="vorerst nur anlegen">
|
||||
<node CREATED="1776204765269" ID="ID_1404790840" MODIFIED="1776204785977" TEXT="EngineBufferMetadata"/>
|
||||
<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>
|
||||
<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>
|
||||
</node>
|
||||
<node CREATED="1776606067153" ID="ID_1057221878" MODIFIED="1776611491005" TEXT="bekommt ein reduziertes Interface">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Keine Funktionalität zum Bedienen konkreter Allokationen
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<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>
|
||||
<body>
|
||||
<ul>
|
||||
<li>
|
||||
ist NOP wenn es sich um einen typeKey handelt. Diese <b>löschen wir niemals</b>, weil dadruch die <font color="#bf0707">Hierarchie zerstört</font> werden könnte
|
||||
</li>
|
||||
<li>
|
||||
kann auf einem stateKey möglicherweise auch die Destruktoren aufrufen (default: <font face="Monospaced" color="#644545">true </font>)
|
||||
</li>
|
||||
<li>
|
||||
aber dann auch den Entry aus der Tabelle tilgen. Sonst könnte er wiederverwendet werden ⟹ würde Konsistenz der Keys <b>brechen</b>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1776605772600" ID="ID_1300641128" MODIFIED="1776605856910" TEXT="muß aber auf Vault-Level definiert sein">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
darf also keinerlei Funktionalität aus dem Steam-Layer voraussetzen
|
||||
</p>
|
||||
</body>
|
||||
</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>
|
||||
<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>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1776611511064" ID="ID_419155046" MODIFIED="1776611525083" TEXT="Mutex-Locking einführen">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1776204795573" ID="ID_1001789565" MODIFIED="1776204802934" TEXT="EngineBufferManager"/>
|
||||
</node>
|
||||
<node CREATED="1776442826894" ID="ID_509761355" MODIFIED="1776442834087" TEXT="OutputSlot mit integrieren">
|
||||
|
|
|
|||
Loading…
Reference in a new issue