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:
parent
31c24e0017
commit
50306db164
4 changed files with 95 additions and 20 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
||||
|
|
|
|||
|
|
@ -83973,12 +83973,12 @@ Date:   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 ⟲ wrapped">
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#5b178b" CREATED="1718674398898" ID="ID_1136800053" MODIFIED="1718726484671" TEXT="Checksumme vom TrackingAllocator ⟲ 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 überschü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:   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="⟹ Schluß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="⟹ Schluß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:   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ä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ächlich wollte ich den Destruktor nicht nach der de-Allokation aufrufen">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
|
|
@ -84034,10 +84034,12 @@ Date:   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ß irgendwo eine Data-corruption verborgen sein">
|
||||
<node COLOR="#435e98" CREATED="1718718995909" ID="ID_1826487879" MODIFIED="1718726502584" TEXT="beim re-Alloc muß 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:   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ür werden zwei ganz andere Blö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:   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 ⟶ SelfDestructor<X>">
|
||||
<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="»ein Stockwerk tiefer legen«">
|
||||
<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 ⟹ nicht mehr direkt aufrufbar"/>
|
||||
<node CREATED="1718725661841" ID="ID_1797905690" MODIFIED="1718725693257" TEXT="Methode destroy() : ruft nun den Destruktor auf ⟹ dieser triggert die Destruktor-Funktion"/>
|
||||
</node>
|
||||
<node CREATED="1718725694729" ID="ID_70049871" MODIFIED="1718725706951" TEXT="bereits im Vorgriff auf eine Überarbeitung der Storage">
|
||||
<node CREATED="1718725708776" ID="ID_1272726043" MODIFIED="1718725721934" TEXT="der Deleter/Destruktor muß 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ß künftig ü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ß dann nur die Element-Zahl und Kapazitä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ä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ährend noch die Destruktor-Funktion lä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="⟹ Speicher könnte überschrieben werden"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1718726102544" ID="ID_1228055536" MODIFIED="1718726122732" TEXT="Lö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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue