Library: more stringent deleter logic

The setup for `ArrayBucket` is special, insofar it shell de-allocate itself,
which creates the danger of re-entrant calls, or to the contrary, the danger
to invoke this clean-up function without actually invoking the destructor.

These problems become relevant once the destructor function itself is statefull,
as is the case when embedding a non-trivial, instance bound allocator
to be used for the clean-up work. Using the new `lib::TrackingAllocator`
highlighted this potential problem, since the allocator maintains a use-count.

Thus I decided to move the »destruction mechanics« one level down into
a dedicated and well encapsulated base class; invoking ArrayBucket's destructor
thereby becomes the only way to trigger the clean-up, and even ElementFactory::destroy()
can now safely check if the destructor was already invoked, and otherwise
re-invoke itself through this embedded destructor function. Moreover,
as an additional safety measure, the actual destructor function is now
moved into the local stack frame of the object's destructor call, removing
any possibility for the de-allocation to interfere with the destructor
invokation itself
This commit is contained in:
Fischlurch 2024-06-18 18:15:58 +02:00
parent 31c24e0017
commit 50306db164
4 changed files with 95 additions and 20 deletions

View file

@ -244,6 +244,12 @@ namespace lib {
destroy (ArrayBucket<I>* bucket)
{
REQUIRE (bucket);
if (bucket->isArmed())
{ // ensure the bucket's destructor is invoked
// and in turn itself invokes this function
bucket->destroy();
return;
}
if (not is_trivially_destructible_v<E>)
{
size_t cnt = bucket->cnt;
@ -279,7 +285,7 @@ namespace lib {
Bucket* newBucket = Fac::create (cnt, spread, alignof(E));
if (data)
try {
newBucket->deleter = data->deleter;
newBucket->installDestructor (data->getDtor());
size_t elms = min (cnt, data->cnt);
for (size_t idx=0; idx<elms; ++idx)
moveElem(idx, data, newBucket);
@ -482,8 +488,8 @@ namespace lib {
ensureDeleter()
{
Deleter deleterFunctor = selectDestructor<TY>();
if (Coll::data_->deleter) return;
Coll::data_->deleter = deleterFunctor;
if (Coll::data_->isArmed()) return;
Coll::data_->installDestructor (move (deleterFunctor));
}
/** ensure sufficient element capacity or the ability to adapt element spread */

View file

@ -73,6 +73,40 @@ namespace lib {
namespace {// Storage header implementation details
/** @internal mix-in for self-destruction capabilities
* @remark the destructor function is assumed to perform deallocation;
* thus the functor is moved in the local stack frame, where it
* can be invoked safely; this also serves to prevent re-entrance.
*/
template<class TAR>
class SelfDestructor
{
std::function<void(TAR*)> dtor_{};
public:
template<class FUN>
void
installDestructor (FUN&& dtor)
{
dtor_ = std::forward<FUN> (dtor);
}
~SelfDestructor()
{
if (isArmed())
{
auto destructionFun = std::move(dtor_);
ENSURE (not dtor_);
destructionFun (target());
}
}
bool isArmed() const { return bool(dtor_); }
auto getDtor() const { return dtor_; }
void destroy() { target()->~TAR(); }
TAR* target() { return static_cast<TAR*>(this); }
};
/**
* Metadata record placed immediately before the data storage.
* @remark SeveralBuilder uses a custom allocation scheme to acquire
@ -80,13 +114,13 @@ namespace lib {
*/
template<class I>
struct ArrayBucket
: SelfDestructor<ArrayBucket<I>>
{
ArrayBucket (size_t storageSize, size_t buffStart, size_t elmSize = sizeof(I))
: cnt{0}
, spread{elmSize}
, buffSiz{storageSize - buffStart}
, buffOffset{buffStart}
, deleter{nullptr}
{ }
using Deleter = std::function<void(ArrayBucket*)>;
@ -95,7 +129,6 @@ namespace lib {
size_t spread;
size_t buffSiz;
size_t buffOffset;
Deleter deleter;
static constexpr size_t storageOffset = sizeof(ArrayBucket);
@ -114,13 +147,6 @@ namespace lib {
ENSURE (storage() <= elm and elm < storage()+buffSiz);
return * std::launder (reinterpret_cast<I*> (elm));
}
void
destroy()
{
if (deleter)
deleter (this);
}
};
}//(End)implementation details

View file

@ -570,7 +570,7 @@ SHOW_EXPR(TrackingAllocator::use_count());
SHOW_TYPE(decltype(builder))
SHOW_EXPR(builder.size())
SHOW_EXPR(builder.capacity())
builder.fillElm(11);
builder.fillElm(55);
SHOW_EXPR(builder.size())
SHOW_EXPR(builder.capacity())

View file

@ -83973,12 +83973,12 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1718581783068" ID="ID_1789618356" MODIFIED="1718581797769" TEXT="standardkonformen custom-Allocator ankoppeln">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1718674398898" ID="ID_1136800053" MODIFIED="1718674423125" TEXT="Checksumme vom TrackingAllocator &#x27f2; wrapped">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#5b178b" CREATED="1718674398898" ID="ID_1136800053" MODIFIED="1718726484671" TEXT="Checksumme vom TrackingAllocator &#x27f2; wrapped">
<icon BUILTIN="broken-line"/>
<node CREATED="1718674427169" ID="ID_516703978" MODIFIED="1718674444637" TEXT="lt. Log sehen die Alloc/Dealloc-Paare alle sauber aus"/>
<node CREATED="1718674446057" ID="ID_801400606" MODIFIED="1718674470177" TEXT="die gewrappte Summe deutet auf einen &#xfc;bersch&#xfc;ssigen de-Alloc hin"/>
<node CREATED="1718674471214" ID="ID_1992064343" MODIFIED="1718674487640" TEXT="use-Count ist immer noch 4 (im aktiven Zustand war sie 5)">
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1718674570632" ID="ID_825638546" MODIFIED="1718674586911" TEXT="Verdacht: der testfall provoziert 4 re-Allocs">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1718674570632" ID="ID_825638546" MODIFIED="1718726489651" TEXT="Verdacht: der testfall provoziert 4 re-Allocs">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1718718804897" ID="ID_1202983917" MODIFIED="1718718821977" TEXT="das wirft einen klar einen Verdacht auf den Deleter"/>
@ -83994,8 +83994,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1718718906083" ID="ID_1914635563" MODIFIED="1718718912133" TEXT="use-count bleibt 2 am Ende"/>
</node>
</node>
<node CREATED="1718718952386" ID="ID_1833378958" MODIFIED="1718718966574" TEXT="&#x27f9; Schlu&#xdf;folgerungen">
<node CREATED="1718718968137" ID="ID_113992967" MODIFIED="1718718993621" TEXT="der Destruktor des Funktors selber wird nicht aufgerufen">
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1718718952386" ID="ID_1833378958" MODIFIED="1718726465896" TEXT="&#x27f9; Schlu&#xdf;folgerungen">
<node COLOR="#435e98" CREATED="1718718968137" ID="ID_113992967" MODIFIED="1718726544827" TEXT="der Destruktor des Funktors selber wird nicht aufgerufen">
<node CREATED="1718720020327" ID="ID_97951871" MODIFIED="1718720077878" TEXT="im Fall von re-Alloc ist das ein konzeptioneller Fehler">
<richcontent TYPE="NOTE"><html>
<head>
@ -84022,7 +84022,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</html>
</richcontent>
<node CREATED="1718722668290" ID="ID_592162062" MODIFIED="1718722673197" TEXT="das ist das gleiche Problem"/>
<node CREATED="1718722673857" ID="ID_1638861888" MODIFIED="1718722749402" TEXT="tats&#xe4;chlich wollte ich den Destruktor nicht nach der de-Allokation aufrufen">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1718722673857" ID="ID_1638861888" MODIFIED="1718726537767" TEXT="tats&#xe4;chlich wollte ich den Destruktor nicht nach der de-Allokation aufrufen">
<richcontent TYPE="NOTE"><html>
<head>
@ -84034,10 +84034,12 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</body>
</html>
</richcontent>
<arrowlink COLOR="#417cc2" DESTINATION="ID_1445462323" ENDARROW="Default" ENDINCLINATION="32;-92;" ID="Arrow_ID_1786077186" STARTARROW="None" STARTINCLINATION="-279;10;"/>
<icon BUILTIN="forward"/>
</node>
</node>
</node>
<node CREATED="1718718995909" ID="ID_1826487879" MODIFIED="1718719102723" TEXT="beim re-Alloc mu&#xdf; irgendwo eine Data-corruption verborgen sein">
<node COLOR="#435e98" CREATED="1718718995909" ID="ID_1826487879" MODIFIED="1718726502584" TEXT="beim re-Alloc mu&#xdf; irgendwo eine Data-corruption verborgen sein">
<node CREATED="1718719106342" ID="ID_1068879050" MODIFIED="1718719187571" TEXT="warum kein double-free?">
<richcontent TYPE="NOTE"><html>
<head>
@ -84056,7 +84058,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1718720172105" ID="ID_864045898" MODIFIED="1718720180746" TEXT="auf der Aloc-Seite zweimal die gleiche Adr."/>
<node CREATED="1718720181427" ID="ID_1054319337" MODIFIED="1718720194746" TEXT="daf&#xfc;r werden zwei ganz andere Bl&#xf6;cke dealloziert"/>
</node>
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1718720409168" ID="ID_971852511" MODIFIED="1718720423327" TEXT="das sind Coding-Fehler im Allokator">
<node COLOR="#435e98" CREATED="1718720409168" ID="ID_971852511" MODIFIED="1718725524540" TEXT="das sind Coding-Fehler im Allokator">
<icon BUILTIN="broken-line"/>
<node CREATED="1718720426940" ID="ID_956992006" MODIFIED="1718722346350" TEXT="es wird inkonsistent geloggt">
<richcontent TYPE="NOTE"><html>
@ -84091,6 +84093,47 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1718725556007" ID="ID_1445462323" MODIFIED="1718726532350" TEXT="Umbau destructorFun &#x27f6; SelfDestructor&lt;X&gt;">
<linktarget COLOR="#417cc2" DESTINATION="ID_1445462323" ENDARROW="Default" ENDINCLINATION="32;-92;" ID="Arrow_ID_1786077186" SOURCE="ID_1638861888" STARTARROW="None" STARTINCLINATION="-279;10;"/>
<icon BUILTIN="button_ok"/>
<node CREATED="1718725596306" ID="ID_538004116" MODIFIED="1718725604814" TEXT="&#xbb;ein Stockwerk tiefer legen&#xab;">
<node CREATED="1718725606544" ID="ID_1475922545" MODIFIED="1718725622466" TEXT="die Destruktor-Funktion kommt in die Basisklasse"/>
<node CREATED="1718725623189" ID="ID_898775357" MODIFIED="1718725635232" TEXT="damit wird sie garantiert erst nach allen Element-Destruktoren aufgerufen"/>
<node CREATED="1718725639476" ID="ID_1315502839" MODIFIED="1718725660917" TEXT="der Funktor selber wird ein privates Feld &#x27f9; nicht mehr direkt aufrufbar"/>
<node CREATED="1718725661841" ID="ID_1797905690" MODIFIED="1718725693257" TEXT="Methode destroy() : ruft nun den Destruktor auf &#x27f9; dieser triggert die Destruktor-Funktion"/>
</node>
<node CREATED="1718725694729" ID="ID_70049871" MODIFIED="1718725706951" TEXT="bereits im Vorgriff auf eine &#xdc;berarbeitung der Storage">
<node CREATED="1718725708776" ID="ID_1272726043" MODIFIED="1718725721934" TEXT="der Deleter/Destruktor mu&#xdf; eine spezielle Komponente werden"/>
<node CREATED="1718725726496" ID="ID_1844777457" MODIFIED="1718725747089" TEXT="diese wird dann selber ihre Overflow-Storage platzieren und verwalten"/>
<node CREATED="1718725747821" ID="ID_1108981895" MODIFIED="1718725778611" TEXT="all das mu&#xdf; k&#xfc;nftig &#xfc;ber Template-Parameter bereits statsich festgelegt werden"/>
<node CREATED="1718725820365" ID="ID_115242482" MODIFIED="1718725961225" TEXT="ein trivialer Destructor wird sich per EBO reduzieren">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...konkret, ich plane einen Satz an Steuer-Flags, und auf dieser Basis dann die Belegung weiterer Storage; im einfachsten Fall gibt es keinen Spread, keinen Deleter und einen Standard-Offset; es mu&#223; dann nur die Element-Zahl und Kapazit&#228;t gespeichert werden.
</p>
</body>
</html>
</richcontent>
</node>
</node>
<node CREATED="1718725976503" ID="ID_1978252495" MODIFIED="1718725998920" TEXT="zur Sicherheit: Destructor-Funktion in den Stack-Frame verschieben">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1718726000768" ID="ID_1159163784" MODIFIED="1718726014826" TEXT="die Situation ist potentiell gef&#xe4;hrlich">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1718726018597" ID="ID_1554889294" MODIFIED="1718726028436" TEXT="wir de-allozieren die Storage"/>
<node CREATED="1718726028904" ID="ID_1132252877" MODIFIED="1718726038602" TEXT="w&#xe4;hrend noch die Destruktor-Funktion l&#xe4;uft"/>
<node CREATED="1718726039392" ID="ID_1813430677" MODIFIED="1718726065066" TEXT="und noch vor dem Aufruf des Destructors des Destruction-Functors"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#ad0856" CREATED="1718726066339" ID="ID_1616093011" MODIFIED="1718726096122" TEXT="&#x27f9; Speicher k&#xf6;nnte &#xfc;berschrieben werden"/>
</node>
<node COLOR="#338800" CREATED="1718726102544" ID="ID_1228055536" MODIFIED="1718726122732" TEXT="L&#xf6;sung: den Funktor per move in den lokalen Scope des Destruktor-Aufrufs verschieben">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1718726133442" ID="ID_1354683018" MODIFIED="1718726163554" TEXT="Nebeneffekt: auch die ElementFactory kann zur Sicherheit den Destruktor-Aufruf triggern"/>
</node>
</node>
</node>
</node>
</node>