BufferProvider: investigate tricky race at thread shutdown
...even while this situation is unlikely, it needs to be considered: A worker thread might terminate unexpectedly, due to an error; all thread-local objects will be destroyed automatically — while the `EngineBufferManager` might be processing further allocations requested by that very thread, and dispatch them towards an already destroyed `LocalMemPool` This turns out to be a nasty problem, since the whole setup was created to allow the workers to operate independently from each other; any handshake or registration/deregistration mechanism would require a lock of sorts, thereby defeating the whole purpose of the setup. The best solution I could find is to rely on a instance marker hash, that can be checked with an atomic read prior to each dispatch of a memory allocation. The destructor of the local pool would then be able to ''disengage'', by invalidating that hash. To evaluate this solution, I developed this mechanism as a free-standing library component, called `LiveMark`
This commit is contained in:
parent
a4b83135a0
commit
cd7581ba76
7 changed files with 790 additions and 22 deletions
179
src/lib/live-mark.hpp
Normal file
179
src/lib/live-mark.hpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
LIVE-MARK.hpp - safety-tag to prevent invoking a deceased partner
|
||||
|
||||
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 live-mark.hpp
|
||||
** Memory marker tag to prevent segfault in communication with a defunct receiver.
|
||||
** The tag comprises an unique hash value, that can be embedded into another object
|
||||
** and tied to the lifecycle. This setup allows to build a safe-handle, that offers
|
||||
** a liveness check of the communication partner and prevents dereferencing and
|
||||
** invoking into the stale memory of an expired receiver. Notably this allows
|
||||
** to safeguard asynchronous communication between thread-local entities,
|
||||
** even when some of the participating threads terminates unexpectedly.
|
||||
**
|
||||
** @todo WIP 5/2026 developing a thread-local allocation pool
|
||||
**
|
||||
** @see engine-buffer-manager.hpp usage example
|
||||
** @see LiveMark_test
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef LIB_LIVE_MARK_H_
|
||||
#define LIB_LIVE_MARK_H_
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/hash-combine.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
|
||||
namespace lib {
|
||||
namespace err = lumiera::error;
|
||||
|
||||
using lib::HashVal;
|
||||
|
||||
|
||||
class LiveMark;
|
||||
|
||||
template<class SUBJ>
|
||||
concept alive_marked = requires (SUBJ const& subject)
|
||||
{
|
||||
{ subject.getLiveMark() } -> std::convertible_to<LiveMark const&>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Liveness marker tag to be incorporated into another object,
|
||||
* so that a communication link can be severed automatically when
|
||||
* the receiver object goes out of scope.
|
||||
* @see AllocReceiver
|
||||
* @see LiveMark_test
|
||||
*/
|
||||
class LiveMark
|
||||
{
|
||||
std::atomic<HashVal> id_;
|
||||
|
||||
public:
|
||||
LiveMark() = default;
|
||||
~LiveMark() { disengage(); } ///< @note the actual protection happens here
|
||||
|
||||
/**
|
||||
* Create a LiveMark that is connected
|
||||
* to the Subject to be protected.
|
||||
*/
|
||||
LiveMark (alive_marked auto& subject)
|
||||
: id_{generateID (subject)}
|
||||
{ }
|
||||
|
||||
// using default copy/assignment
|
||||
|
||||
/** deactivate and mark as dead (irreversible) */
|
||||
void disengage() { id_.store (0, std::memory_order_relaxed); }
|
||||
|
||||
|
||||
/**
|
||||
* A smart-handle for safe protected access to some object
|
||||
* that might go out of scope eventually. This works under the
|
||||
* assumption that the target object embeds a LiveMark, which
|
||||
* is expressed through the concept alive_marked. The marker
|
||||
* HashVal is unique for each instance and distinct memory
|
||||
* location, and thus even access to a new object instance
|
||||
* with the same type, placed into the same memory location
|
||||
* will be rejected, after the original target was destroyed.
|
||||
*/
|
||||
template<alive_marked SUBJ>
|
||||
class SafeHandle
|
||||
{
|
||||
SUBJ * target_{nullptr};
|
||||
HashVal check_{0};
|
||||
|
||||
public:
|
||||
SafeHandle() = default;
|
||||
// default copy operations
|
||||
|
||||
/** liveness check */
|
||||
operator bool() const
|
||||
{
|
||||
return bool(target_)
|
||||
and check_ == retrieveID (*target_);
|
||||
}
|
||||
|
||||
/* ==== subject access ==== */
|
||||
|
||||
SUBJ&
|
||||
operator*() const
|
||||
{
|
||||
if (not bool(*this))
|
||||
throw err::State{"Accessing defunct target"
|
||||
, LERR_(BOTTOM_VALUE)};
|
||||
return * target_;
|
||||
}
|
||||
|
||||
SUBJ*
|
||||
operator->() const
|
||||
{
|
||||
return & SafeHandle::operator*();
|
||||
}
|
||||
|
||||
private:
|
||||
SafeHandle (SUBJ& target)
|
||||
: target_{& target}
|
||||
, check_{retrieveID(target)}
|
||||
{ }
|
||||
|
||||
friend class LiveMark;
|
||||
|
||||
/** The heart of the implementation: peek into the
|
||||
* target location to see if a valid LiveMark is embedded.
|
||||
*/
|
||||
static HashVal
|
||||
retrieveID (SUBJ& target)
|
||||
{
|
||||
return target.getLiveMark().id_.load (std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a safe smart-handle to refer to the given subject,
|
||||
* similar to a raw pointer. Yet the handle will bool-check
|
||||
* to `false` when the target object was destroyed
|
||||
*/
|
||||
template<alive_marked SUBJ>
|
||||
SafeHandle<SUBJ>
|
||||
makeHandle (SUBJ& subject)
|
||||
{
|
||||
return SafeHandle{subject};
|
||||
}
|
||||
|
||||
private:
|
||||
/** Generate unique HashVal.
|
||||
* @remark seeded by a nonce (counter) and the memory address,
|
||||
* which makes it impossible to get the same hash at the same
|
||||
* target address more than once (unless there is a collision
|
||||
* in the 64bit hash space, which is extremely rare)
|
||||
*/
|
||||
template<alive_marked X>
|
||||
static HashVal
|
||||
generateID (X const& targetAddress)
|
||||
{
|
||||
static std::atomic_size_t nonce;
|
||||
HashVal mark = nonce.fetch_add (1, std::memory_order_relaxed);
|
||||
|
||||
hash::combine (mark, util::addrID (targetAddress));
|
||||
return mark;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace lib
|
||||
#endif /*LIB_LIVE_MARK_H_*/
|
||||
|
|
@ -56,7 +56,7 @@ namespace thread{
|
|||
ThreadWrapper::decorate_with_global_count (string const& rawID)
|
||||
{
|
||||
static atomic_uint globalCnt{1};
|
||||
return _Fmt{"%s.%03i"} % rawID % globalCnt.fetch_add (+1, std::memory_order_acq_rel);
|
||||
return _Fmt{"%s.%03i"} % rawID % globalCnt.fetch_add (+1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -379,8 +379,8 @@ namespace mem {
|
|||
{
|
||||
REQUIRE (not block.used);
|
||||
consumer (block.alloc.mem, block.alloc.siz);
|
||||
blocks_.clear();
|
||||
}
|
||||
blocks_.clear();
|
||||
ENSURE (this->empty());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -478,6 +478,11 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "autodetect deceased partner" LiveMark_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
TEST "Thread-local service instances" LocalSlice_test <<END
|
||||
END
|
||||
|
||||
|
|
|
|||
209
tests/lib/live-mark-test.cpp
Normal file
209
tests/lib/live-mark-test.cpp
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
LiveMark(Test) - verify safeguard against invoking a deceased partner
|
||||
|
||||
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 live-mark-test.cpp
|
||||
** unit test \ref LiveMark_test
|
||||
*/
|
||||
|
||||
#include "test/run.hpp"
|
||||
#include "test/test-helper.hpp"
|
||||
#include "lib/uninitialised-storage.hpp"
|
||||
#include "lib/thread.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include "lib/live-mark.hpp"
|
||||
|
||||
#include <boost/lockfree/queue.hpp>
|
||||
#include <atomic>
|
||||
|
||||
|
||||
using util::isSameAdr;
|
||||
using std::this_thread::yield;
|
||||
using std::this_thread::sleep_for;
|
||||
using std::chrono_literals::operator ""us;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace test {
|
||||
|
||||
using LERR_(BOTTOM_VALUE);
|
||||
|
||||
|
||||
/****************************************************************//**
|
||||
* @test validate a safety tag to prevent segfault when accessing
|
||||
* a deceased object through a previously valid pointer.
|
||||
* - demonstrate how to embody and use the LiveMark tag.
|
||||
* - verify that access is only valid for the unique single
|
||||
* object that generated the access-handle.
|
||||
* - demonstrate that the protection mechanism can be used
|
||||
* to safeguard a multithreaded sender/receiver scenario.
|
||||
* @see live-mark.hpp
|
||||
* @see LocalBufferStore_test
|
||||
*/
|
||||
class LiveMark_test : public Test
|
||||
{
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
simpleUsage();
|
||||
verify_distinctInstances();
|
||||
verify_multithreadAccess();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test target structure
|
||||
* protected with a LiveMark tag
|
||||
*/
|
||||
struct Sidekick
|
||||
{
|
||||
LiveMark mark{*this};
|
||||
uint64_t feat{0};
|
||||
|
||||
LiveMark const& getLiveMark() const { return mark; }
|
||||
};
|
||||
|
||||
using Handle = LiveMark::SafeHandle<Sidekick>;
|
||||
|
||||
|
||||
/** @test attach a LiveMark to a simple object
|
||||
* and create a safe access handle, allowing
|
||||
* to detect when the target went out of scope */
|
||||
void simpleUsage ()
|
||||
{
|
||||
Handle handle;
|
||||
CHECK (not handle);
|
||||
{
|
||||
Sidekick kick;
|
||||
handle = kick.mark.makeHandle (kick);
|
||||
CHECK (handle);
|
||||
handle->feat = 55;
|
||||
CHECK (55 == kick.feat);
|
||||
}// ◁──────────────────┨ Sidekick vanishes
|
||||
|
||||
CHECK (not handle);
|
||||
VERIFY_ERROR (BOTTOM_VALUE, *handle );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @test re-use of the same memory location for another instance
|
||||
* is indeed properly detected, preventing unintended access
|
||||
* through the handle obtained from a preceding instance */
|
||||
void verify_distinctInstances()
|
||||
{
|
||||
UninitialisedStorage<Sidekick, 1> dodgyPlace;
|
||||
|
||||
Sidekick* pos = new(&dodgyPlace) Sidekick;
|
||||
pos->feat = 23;
|
||||
CHECK (23 == dodgyPlace[0].feat);
|
||||
|
||||
Handle handle = pos->mark.makeHandle (*pos);
|
||||
CHECK (handle);
|
||||
|
||||
// destroy the first instance
|
||||
pos->~Sidekick();
|
||||
|
||||
// memory content is still there...
|
||||
CHECK (23 == pos->feat);
|
||||
|
||||
// insidiously plant a new object
|
||||
// into the same memory location on the stack
|
||||
new(&dodgyPlace) Sidekick;
|
||||
|
||||
// the constructor initialised the new object,
|
||||
// but we can manipulate its content through the old pointer;
|
||||
CHECK (isSameAdr (pos, &dodgyPlace));
|
||||
CHECK (0 == pos->feat);
|
||||
|
||||
pos->feat = 55;
|
||||
CHECK (55 == dodgyPlace[0].feat);
|
||||
|
||||
// but the safe handle prevents unintended aliasing
|
||||
CHECK (not handle);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @test assess the liveness state from another thread
|
||||
* - setup a producer ⟶ consumer scenario
|
||||
* - the consumer embodies a LiveMark
|
||||
* - the producer checks the liveness state before sending
|
||||
* - after some delay, the consumer disengages the LiveMark
|
||||
* and then empties the queue...
|
||||
* - the producer should notice that the receiver is no longer
|
||||
* accessible and should stop sending.
|
||||
* - after some further delay, the receiver verifies that
|
||||
* no further messages were received and that the last
|
||||
* number received is the last number produced
|
||||
*/
|
||||
void verify_multithreadAccess()
|
||||
{
|
||||
struct Receiver
|
||||
{
|
||||
using InQueue = boost::lockfree::queue<uint>;
|
||||
|
||||
InQueue queue{1000};
|
||||
LiveMark mark{*this};
|
||||
LiveMark const& getLiveMark() const { return mark; }
|
||||
};
|
||||
using RecHandle = LiveMark::SafeHandle<Receiver>;
|
||||
|
||||
|
||||
Receiver receiver;
|
||||
RecHandle handle = receiver.mark.makeHandle (receiver);
|
||||
CHECK (handle);
|
||||
|
||||
std::atomic_uint source{0};
|
||||
|
||||
Thread testWorker{[&] /* === Receiver of messages === */
|
||||
{
|
||||
sleep_for (100us);
|
||||
|
||||
// signal to accept no further messages
|
||||
receiver.mark.disengage();
|
||||
yield();
|
||||
|
||||
// accept all messages sent thus far
|
||||
uint lastNumber{0};
|
||||
receiver.queue.consume_all ([&](uint val){ lastNumber = val; });
|
||||
|
||||
// verify that the sender indeed stopped...
|
||||
sleep_for (100us);
|
||||
CHECK (receiver.queue.empty());
|
||||
CHECK (lastNumber == source);
|
||||
}};
|
||||
|
||||
/* === Producer of messages === */
|
||||
|
||||
while (handle)
|
||||
{
|
||||
++source;
|
||||
handle->queue.push (source);
|
||||
yield();
|
||||
}
|
||||
|
||||
// receiver thread is still running...
|
||||
CHECK (testWorker);
|
||||
while (testWorker)
|
||||
yield();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER(LiveMark_test, "unit common");
|
||||
|
||||
|
||||
}} // namespace lib::test
|
||||
|
|
@ -31,8 +31,10 @@
|
|||
#include <algorithm>
|
||||
|
||||
using util::isSameAdr;
|
||||
using std::this_thread::yield;
|
||||
using lib::Thread;
|
||||
using std::this_thread::yield;
|
||||
using std::this_thread::sleep_for;
|
||||
using std::chrono_literals::operator ""ms;
|
||||
|
||||
namespace vault {
|
||||
namespace mem {
|
||||
|
|
@ -119,12 +121,22 @@ namespace test {
|
|||
*p = '\0';
|
||||
CHECK (ranS == buf);
|
||||
|
||||
// reserve four additional buffers;
|
||||
// these happen to sit already in the local pool anyway
|
||||
// and will be marked as "reserved" now....
|
||||
avail = storeAPI.prepareBuffers(TYPEID, 4, BUFFSIZ);
|
||||
CHECK (4 == avail);
|
||||
|
||||
// finish usage and return the single buffer claimed thus far...
|
||||
storeAPI.mark_emitted (TYPEID, storageSlot);
|
||||
storeAPI.detachBuffer (TYPEID, storageSlot);
|
||||
|
||||
// now we have 5 buffers in the pool, but 4 are marked as "reserved"
|
||||
// thus only one buffer can be confirmed and one further is requested from EngineBufferManager
|
||||
avail = storeAPI.prepareBuffers(TYPEID, 2, BUFFSIZ);
|
||||
CHECK (1 == avail);
|
||||
|
||||
// but irrespective of reservation, we can immediately use the buffers locally available
|
||||
BuffAlloc slot1 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, MARK, ARG);
|
||||
auto& [sto1,siz1,tag1] = slot1;
|
||||
BuffAlloc slot2 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, MARK, ARG);
|
||||
|
|
@ -142,17 +154,27 @@ namespace test {
|
|||
while (testWorker)
|
||||
yield(); // wait for worker to finish
|
||||
|
||||
// additional delay for the local pool's destructor
|
||||
// that is invoked after the thread has finished
|
||||
sleep_for(1ms);
|
||||
|
||||
CHECK (globalPool); // the Mock was accessed (and thus instantiated)
|
||||
|
||||
// before shutdown, LocalMemPool has sent back all allocations;
|
||||
// these should sit now in the EngineBufferManager's input queue
|
||||
// these should sit now in the EngineBufferManager's input queue,
|
||||
// unprocessed yet; same for the additional buffer requested later.
|
||||
SHOW_EXPR(watch(*globalPool).numAllocs());
|
||||
CHECK ( CNT+1 == watch(*globalPool).numAllocs());
|
||||
CHECK ((CNT+1)*ALLOC == watch(*globalPool).bytesLeased());
|
||||
CHECK ( CNT == watch(*globalPool).numAllocs());
|
||||
CHECK ((CNT)*ALLOC == watch(*globalPool).bytesLeased());
|
||||
|
||||
// process in-queue and retrieve all allocations..
|
||||
SHOW_EXPR("Kaka")
|
||||
globalPool->processPendingRequests();
|
||||
CHECK (0 == watch(*globalPool).bytesLeased());
|
||||
SHOW_EXPR("Boom")
|
||||
SHOW_EXPR(watch(*globalPool).bytesLeased());
|
||||
// CHECK (0 == watch(*globalPool).bytesLeased());
|
||||
sleep_for(1000ms);
|
||||
SHOW_EXPR("bye")
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -145055,7 +145055,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1779140507822" ID="ID_1541520016" MODIFIED="1779140836908" TEXT="weitere Komplikation: Race im Shutdown">
|
||||
<arrowlink COLOR="#e5024c" DESTINATION="ID_241014081" ENDARROW="Default" ENDINCLINATION="-1049;0;" ID="Arrow_ID_1884336660" STARTARROW="None" STARTINCLINATION="-946;65;"/>
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1779140860563" ID="ID_187364117" MODIFIED="1779140879122" TEXT="das zeigt: ein explizitesr Schutzmechanismus ist notwendig"/>
|
||||
<node CREATED="1779140860563" ID="ID_187364117" MODIFIED="1779216224446" TEXT="das zeigt: ein explizitesr Schutzmechanismus ist notwendig">
|
||||
<arrowlink COLOR="#ad0057" DESTINATION="ID_1356813075" ENDARROW="Default" ENDINCLINATION="114;-220;" ID="Arrow_ID_1737519596" STARTARROW="None" STARTINCLINATION="-1063;52;"/>
|
||||
</node>
|
||||
<node CREATED="1779140879780" ID="ID_418804808" MODIFIED="1779140902765" TEXT="dieser muß bereits in der Basisklasse AllocReceiver sitzen"/>
|
||||
<node CREATED="1779140904161" ID="ID_292316056" MODIFIED="1779140919843" TEXT="der darf auf keinerlei dynamischen Speicher zugreifen"/>
|
||||
<node CREATED="1779140953120" ID="ID_475594715" MODIFIED="1779140969596" TEXT="die Objekt-Identität muß aus der Speicheradresse gewonnen werden"/>
|
||||
|
|
@ -145063,6 +145065,247 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1779216016773" ID="ID_1356813075" MODIFIED="1779328952013" TEXT="Schutzmechanismus für die Async-Kommunikation">
|
||||
<linktarget COLOR="#ad0057" DESTINATION="ID_1356813075" ENDARROW="Default" ENDINCLINATION="114;-220;" ID="Arrow_ID_1737519596" SOURCE="ID_187364117" STARTARROW="None" STARTINCLINATION="-1063;52;"/>
|
||||
<linktarget COLOR="#5000ad" DESTINATION="ID_1356813075" ENDARROW="Default" ENDINCLINATION="-430;843;" ID="Arrow_ID_1277922642" SOURCE="ID_1697479221" STARTARROW="None" STARTINCLINATION="-1130;69;"/>
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node CREATED="1779224920108" ID="ID_909246085" MODIFIED="1779328938586" TEXT="Anforderungen">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1779224928540" ID="ID_117163407" MODIFIED="1779224938270" TEXT="hat einen aktiv / geblockt-State"/>
|
||||
<node CREATED="1779224951104" ID="ID_1266800565" MODIFIED="1779224964148" TEXT="State muß threadsafe-lesbar sein"/>
|
||||
<node CREATED="1779224973409" ID="ID_767873382" MODIFIED="1779225032550" TEXT="State ist an den Instanz-Lebenszyklus gekoppelt"/>
|
||||
<node CREATED="1779225034381" ID="ID_710084049" MODIFIED="1779225044408" TEXT="aber State darf nicht in der Instanz gespeichert sein"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1779225160990" ID="ID_1932610329" MODIFIED="1779328935367" TEXT="Ansatz">
|
||||
<icon BUILTIN="info"/>
|
||||
<node CREATED="1779225165484" ID="ID_1205009248" MODIFIED="1779225172950" TEXT="verwende einen Monostate-artigen Zugang"/>
|
||||
<node CREATED="1779225174394" ID="ID_964352880" MODIFIED="1779225199891" TEXT="dieser erzeugt aber einen Key, der an die Instanz gebunden ist"/>
|
||||
<node CREATED="1779225204246" ID="ID_773629611" MODIFIED="1779227167103" TEXT="darüber wird in einen speziellen Marker zugegriffen"/>
|
||||
<node CREATED="1779225216669" ID="ID_1883744470" MODIFIED="1779225232126" TEXT="Lebenszyklus-Kopplung über Ctor/Dtor realisiert"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1779227171937" ID="ID_857696487" MODIFIED="1779328111248" TEXT="Problem der Synchronisation">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1779227189574" ID="ID_697877195" MODIFIED="1779227476829">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
eine statische Map als Lösung ist deshalb <b>problematisch</b>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<node CREATED="1779227525202" ID="ID_285171260" MODIFIED="1779227536124" TEXT="denn concurrent-updates bedingen ein tatsächliches Lock"/>
|
||||
<node CREATED="1779227544752" ID="ID_1800677649" MODIFIED="1779227597579" TEXT="und hierbei ist mit echter Lock-Contention zu rechnen">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
...da die Worker-Threads normalerweise gleichzeitig starten / stoppen, also sehr wahrscheinlich mit dem Zugriff der anderen Worker kollidieren
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1779234317235" ID="ID_1854090554" MODIFIED="1779234355283" TEXT="weiteres Problem: die Lesezugriffe sehen u.U. ein Update nicht / nur partiell">
|
||||
<icon BUILTIN="clanbomber"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1779227244643" ID="ID_832890047" MODIFIED="1779227262336" TEXT="zunächst einmal: jedes Auslesen muß über einen atomic-Read gehen">
|
||||
<node CREATED="1779227266487" ID="ID_542363079" MODIFIED="1779227279926" TEXT="so daß der Lesende gezwungen ist, seinen Cach zu refreshen"/>
|
||||
<node CREATED="1779227283799" ID="ID_921394030" MODIFIED="1779227327440" TEXT="natürlich bedingt dann jede Status-Änderung des überwachten Objekts einen atomic-Write"/>
|
||||
<node CREATED="1779227333371" ID="ID_268505194" MODIFIED="1779227435627" TEXT="bezüglich Performance wäre das irrelevant">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
...denn der nächste Schritt nach erfolgreichem Passieren des Schutzmechanismus wäre ohnehin eine Einfügung in eine lock-free-Queue, also sogar atomic-Read/Write or CAS. Und <i>das</i> machen wir nur wenn Allokationen global angefordert / zugestellt werden.
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#d3b4a8" COLOR="#990033" CREATED="1779234491711" ID="ID_1387632637" MODIFIED="1779235165552" TEXT="das löst aber leider noch nicht das Sichtbarkeits-Problem für eine Map">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
...denn die Sichtbarkeits-Garantie wird nur über eine happens-before-Ordnung hergestellt: <i>wenn</i> ich die Änderung im Atomic sehe, dann sehe ich <i>garantiert auch alle happens-before-Änderungen.</i>  Wenn ich die Änderung allerdings nicht sehe, ist nichts weiter über die sonstige Sichtbarkeit bekannt. In diesem Fall müßte ich weitere Rnden lokal drehen, und dürfte nicht auf anderweitig synchronisierten globalen Speicher zugreifen, <i>bis ich die Änderung auf dem Atomic</i> sehe. Leider paßt dieses Muster <b>überhaupt nicht zur Anforderung</b>, daß ich »<b>jetzt und hier</b>« für den nächsten Zugriff die korrekten Werte sehe (und sonst passiert eine schröckliche Katastrofe)
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1779227720661" ID="ID_1207863358" MODIFIED="1779227735129" TEXT="alternativer Ansatz: ein Speicher-Trick">
|
||||
<node CREATED="1779227737273" ID="ID_1151704840" MODIFIED="1779227745577" TEXT="der Marker liegt im Objekt selber"/>
|
||||
<node CREATED="1779227746583" ID="ID_827606725" MODIFIED="1779227764050" TEXT="er enthält einen eindeutig verifizierbaren Hash"/>
|
||||
<node CREATED="1779227764967" ID="ID_1341800059" MODIFIED="1779227783624" TEXT="und die Flag steht im gleichen Datenwort, im LSB"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1779228859352" ID="ID_1992869770" MODIFIED="1779228994364" TEXT="Problem dabei: fälschliche Freigabe per Koinzidenz">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Wenn dummerweise an genau der gleichen Speicheradresse wieder ein solches Objekt mit diesem Mechanismus landet. Selbst wenn es sich um einen anderen Objekttyp handelt, oder eben um die Instanz für einen anderen, inzwischen neu gestarteten Thread. Das ist auf den ersten Blick <i>extrem unwahrscheinlich,</i> aber ich könnte mir durchaus Situationen denken, die sowas gradezu provozieren (z.B. custom pooling Allocator)
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1779236363484" ID="ID_1623305116" MODIFIED="1779238992369" TEXT="Nach einigem Nachdenken: weiß nicht ob das Sichtbarkeitsproblem ohne Lock lösbar ist">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Im gegebenen Kontext ist das problematisch, <i>weil der Zweck der ganzen Veranstaltung ist, daß sich Worker möglichst wenig gegenseitig stören.</i>  Als Solches mak dieses Ziel debattierbar sein, aber wenn man es akzeptiert, dann wäre ein volle Lese/Schreib-Barriere oder gar eine Mutex-Sektion bereits genug, um die lock-free-Queue und das komplizierte asynchrone Austausch-System komplett ad absurdum zu führen. Denn dann könnten sich die Worker bei Bedarf auch gleich selber neuen Speicher vom Heap holen....
|
||||
</p>
|
||||
<p>
|
||||
Leider sind aber die offensichtlichen, alternativen Lösungen mit Atomics immer darauf angewiesen, daß irgendwo ein zeitlicher Spielraum ist, also z.B. ein Consumer, der im Zweifelsfall "mal eben eine weitere Runde drehen" kann. Denn die Atomics geben nur eine Positiv-Garantie: <i>wenn</i>  ich die Änderung auf dem Atomic sehe, <i>dann sehe ich konsistent</i>  auch alle anderen happens-before-Änderungen. Wir brauchen aber ziemlich genau das Gegenteil hier: wenn wir zugreifen, müssen wir sicher sein, daß der Empfänger noch lebt, sonst staubt's.
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1779239004500" ID="ID_1301042348" MODIFIED="1779239024082" TEXT="jetzt bin ich ziemlich ratlos">
|
||||
<icon BUILTIN="smily_bad"/>
|
||||
<node CREATED="1779239032895" ID="ID_1565211003" MODIFIED="1779239059936" TEXT="der Speichertrick hätte diese Probleme nicht, aber er hat ein unbekanntes »Restrisiko«"/>
|
||||
<node CREATED="1779239066139" ID="ID_1333334571" MODIFIED="1779239416970" TEXT="mit einer Nonce im Hash läßt sich das nicht beheben, und ein Type-Seed hilft nur bedingt">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Denn eine Nonce müßte ja wieder irgendwo stehen, also entweder im statischen Speicher (back to square one), oder im Objekt, und im letzteren Fall würde sie für ein neues Objekt geändert, zusammen mit dem Hash, so daß alles wieder konsistent wäre, und damit das Problem nicht bemerkt würde. Sicher wäre das nur, wenn man die Nonce auf der anderen Seite des Zugriffs hinterlegen würde, also beim zugreifenden Client. Das wäre dann aber hässlich, komplex und verwirrend. Alternativ könnte man einen Typ-Hash verwenden. Der würde verhindern, daß man auf ein neues Objekt eines ganz anderen Typs zugreift (ein Fall, der konkret gar nicht auftreten kann). Aber er würde nicht verhindern, daß man versehentlich auf einen Pool eines neu gestarteten, anderen Threads zugreift, der zufällig an der gleichen Speicheradresse liegt. Zwar würde das keinen Schaden anrichten, aber diese Art »harmlose Glitches« sind etwas, auf das man sich besser nicht verlassen sollte.
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node COLOR="#5b280f" CREATED="1779239419156" ID="ID_1089705414" MODIFIED="1779240425918" TEXT="was gehen würde, ist eine lineare Liste, mit statischem atomarem Einstiegspunkt">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Jeder Zugriff müßte dann mit einer Read-Barriere über den Atomic gehen (was grade noch akzeptabel ist, da man kurz darauf genau das Gleiche auch mit der lock-free-Queue macht). Man würde dann neue Objekte vorne anfügen, und würde die Liste linear durchsuchen nach einem Eintrag für das aktuelle Objekt. Denn wenn bei Änderungen eine Write-Barriere genutzt wird, ist die Änderung garantiert im Speicher; wenn außerdem jeder Zugriff eine Lese-Barriere verwendet, dann sieht er garantiert den Wert aus dem Hauptspeicher, und garantiert auch alle vorher gemachten Änderungen. <i>Allerdings schützt das <b>wieder nicht</b> gegen ein concurrent Update der Map.</i>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="button_cancel"/>
|
||||
</node>
|
||||
<node CREATED="1779240431389" ID="ID_1958970349" MODIFIED="1779241337090" TEXT="einen Schritt weiter gedacht: ein atomic Lock würde funktionien und wäre akzeptabel">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Also ein Konstrukt wie ich es für das »Grooming-Token« im Scheduler verwendet habe:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
vor <i>jedwedem Zugriff</i> (Scheiben <i>und</i> Lesen) muß man ein atomic-Token setzen (auf die Thread-ID)
|
||||
</li>
|
||||
<li>
|
||||
wenn es bereits gesetzt ist, muß man »spinnen«
|
||||
</li>
|
||||
<li>
|
||||
die dahinter liegende Datenstruktur dagegen bedarf keines weiteren Schutzes, denn durch das Schreiben/Lesen des atomic-Lock hat man bereits die Garantie, daß man den aktuellen Stand sieht.
|
||||
</li>
|
||||
<li>
|
||||
effektiv ist das ein MUTEX, aber ohne Kernel-Switch und ohne die Gefahr, schlafen gelegt zu werden.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Leider bedingt das eine Schreib-Barriere auch für jeden Lesezugriff, da man das Lock auch zum Lesen erlangen muß. Das kostet 20µs und möglicherweise einen Pipeline-Stall, was aber hier vielleicht grade noch akzeptabel wäre, da es nur bei Austausch von Allokationen notwendig ist. Allerdings: diese passieren durchaus regelmäßig, und wenn die EngineBufferManager dann mal eben 30 Requests zuzustellen hat, ist das auch nicht mehr lustig. <b>Also fragwürdig</b>.
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="help"/>
|
||||
</node>
|
||||
<node CREATED="1779241271672" ID="ID_826264580" MODIFIED="1779328158312" TEXT="demgegenüber erscheint nun ein Speichertrick mit client-seitigem Prüfwert doch attraktiv">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Man müßte dazu also den AllocReceiver umformulieren und zudem auch noch die Requests im EngineBufferManager. Vermutlich läuft das auf einen speziellen smart-Handle-Typ hinaus. Dieser würde eine Prüfsumme beinhalten, die dann auf der Empfängerseite genauso im Speicher vorzufinden sein muß. Die Prüfsumme würde auf einer Nonce+der Ziel-Addresse beruhen, und könnte damit so nur ein einziges Mal pro Programmlauf auftreten (modulo 63-bit Hash-Kollision). Die Sperr-Flag bestünde dann einfach darin, die empfängerseitige Prüfsumme auf Null zu setzen (kein Match, kein Zugriff). Diese empfängerseitige Prüfsumme müßte ein Atomic sein, und beim Zugriff würde eine Lese-Barriere angewendet, beim Ändern eine Schreibbarriere. Das wäre völlig akzeptabel. Darüberhinaus gäbe es keine Contention (außer beim Neuerzeugen eines Objekts, weil dann die Nonce inkrementiert werden muß).
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<arrowlink COLOR="#effec1" DESTINATION="ID_86232371" ENDARROW="Default" ENDINCLINATION="590;70;" ID="Arrow_ID_1616330137" STARTARROW="None" STARTINCLINATION="89;231;"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1779225244553" ID="ID_33299173" MODIFIED="1779328092186" TEXT="Implementierung">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#435e98" CREATED="1779302502523" ID="ID_332907520" MODIFIED="1779328099815">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Hilfsklasse: <b>LiveMark</b>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1779302523581" ID="ID_272378166" MODIFIED="1779302536450" TEXT="wird inkorporiert"/>
|
||||
<node CREATED="1779302591078" ID="ID_1400105754" MODIFIED="1779302599488" TEXT="initialisiert sich automatisch mit einer Nonce"/>
|
||||
<node CREATED="1779302600351" ID="ID_119289264" MODIFIED="1779302630803" TEXT="kann explizit disengaged werden (unumkehrbar)"/>
|
||||
<node CREATED="1779302636830" ID="ID_424340598" MODIFIED="1779302657688" TEXT="und disengaged im Destruktor"/>
|
||||
<node CREATED="1779302661463" ID="ID_685500830" MODIFIED="1779302678340" TEXT="man kann daraus ein SafeHandle<TAR> erzeugen"/>
|
||||
</node>
|
||||
<node CREATED="1779302768803" ID="ID_1441984984" MODIFIED="1779316440364" TEXT="Details">
|
||||
<icon BUILTIN="idea"/>
|
||||
<node CREATED="1779302772812" ID="ID_267911667" MODIFIED="1779302787022" TEXT="der Client muß auf die LiveMark zugreifen können"/>
|
||||
<node COLOR="#5b280f" CREATED="1779302826315" ID="ID_918422403" MODIFIED="1779302959278" TEXT="dazu brauchen wir ein Lambda oder einen Funktor">
|
||||
<icon BUILTIN="button_cancel"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1779302960427" ID="ID_783330097" MODIFIED="1779316428169" TEXT="oder der Träger muß ein Concept erfüllen">
|
||||
<icon BUILTIN="forward"/>
|
||||
<node CREATED="1779303004975" ID="ID_946970515" MODIFIED="1779303018640" TEXT="alive_marked"/>
|
||||
<node CREATED="1779303022339" ID="ID_1505569542" MODIFIED="1779303038533" TEXT="TAR::getLiveMark()"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1779303082464" ID="ID_1818678384" MODIFIED="1779316434225" TEXT="Redundanz ist akzeptabel ⟹ speichere den LiveMark-Hash auf beiden Seiten">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1779305086437" ID="ID_1154074030" MODIFIED="1779316393076" TEXT="das Handle ist ein typischer smart-ptr mit bool-check und exception">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1779303131609" ID="ID_1623611953" MODIFIED="1779316395955" TEXT="Nonce inkrementieren">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1779328129353" ID="ID_86232371" MODIFIED="1779328147143" TEXT="die Lösung gefällt mir">
|
||||
<linktarget COLOR="#effec1" DESTINATION="ID_86232371" ENDARROW="Default" ENDINCLINATION="590;70;" ID="Arrow_ID_1616330137" SOURCE="ID_826264580" STARTARROW="None" STARTINCLINATION="89;231;"/>
|
||||
<icon BUILTIN="ksmiletris"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1779225249759" ID="ID_744341688" MODIFIED="1779328088646" TEXT="Test">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node COLOR="#435e98" CREATED="1779303148844" ID="ID_1026277820" MODIFIED="1779324021489" TEXT="lib::LiveMark_test">
|
||||
<icon BUILTIN="info"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1779316458172" ID="ID_466259903" MODIFIED="1779316471356" TEXT="einfache Verwendung eines Handle">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1779316472296" ID="ID_1937415890" MODIFIED="1779316492345" TEXT="demonstrieren daß ein dangling pointer erkannt wird">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1779324030592" ID="ID_1681185093" MODIFIED="1779328057631" TEXT="Kommunikation über Thread-Grenzen zeigen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1779324880269" ID="ID_1402037409" MODIFIED="1779324907661" TEXT="schwierig — wenig zu greifen"/>
|
||||
<node COLOR="#435e98" CREATED="1779324908499" ID="ID_1843707764" MODIFIED="1779328060422" TEXT="Idee: empfange über eine lock-free queue">
|
||||
<icon BUILTIN="idea"/>
|
||||
<node CREATED="1779324952821" ID="ID_1297928668" MODIFIED="1779324974400" TEXT="Sender prüft ob Empfänger noch zugreifbar ist"/>
|
||||
<node CREATED="1779324980129" ID="ID_374081410" MODIFIED="1779325011161" TEXT="Empfänger deaktiviert die LiveMark und leert die Queue"/>
|
||||
<node CREATED="1779325012566" ID="ID_210063040" MODIFIED="1779325018696" TEXT="danach darf nichts mehr ankommen"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1779325042993" ID="ID_344181114" MODIFIED="1779327324878" TEXT="das zeigt: hier besteht immer noch ein Race">
|
||||
<arrowlink COLOR="#b00581" DESTINATION="ID_1664117186" ENDARROW="Default" ENDINCLINATION="-937;0;" ID="Arrow_ID_1984536453" STARTARROW="None" STARTINCLINATION="-349;22;"/>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1779325064012" ID="ID_266392583" MODIFIED="1779328078960" TEXT="zwischen dem Check und dem Zugriff auf die Queue">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
</node>
|
||||
<node CREATED="1779325191221" ID="ID_1731541361" MODIFIED="1779325216960" TEXT="kaum zu verhindern ohne ein Lock oder einen atomaren Bestätigungs-Mechanismus"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1778802491003" ID="ID_1880673250" MODIFIED="1778979225756" TEXT="brauche noch einen Trigger-Mechanismus für das heuristische Clean-up">
|
||||
<icon BUILTIN="yes"/>
|
||||
|
|
@ -145240,22 +145483,90 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1778948939771" ID="ID_1407930950" MODIFIED="1778948943134" TEXT="Thread beenden"/>
|
||||
<node CREATED="1778979132916" ID="ID_393345688" MODIFIED="1778979147235" TEXT="Anzahl tatsächlicher Allokationen prüfen"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1779149341620" ID="ID_1844089253" MODIFIED="1779149360400" TEXT="preparBuffers beachtet Reservierungen nicht">
|
||||
<node COLOR="#435e98" CREATED="1779149341620" FOLDED="true" ID="ID_1844089253" MODIFIED="1779215841493" TEXT="prepareBuffers beachtet Reservierungen nicht">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1779149362001" ID="ID_674281546" MODIFIED="1779149386315" TEXT="eigentlich sollten von den 5 angeforderten Buffern 4 noch »reserved« sein"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1779212779895" ID="ID_1728903949" MODIFIED="1779212904879" TEXT="das war ein Denkfehler im Test-Setup...">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
als "reserviert" markieren wir nur Blöcke, die bereits unmittelbar im lokalen Pool verfügbar sind; vom EngineBufferManager angeforderte Blöcke dagegen sind nicht "reserviert", wenn sie ankommen
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="smiley-oh"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1779148203212" ID="ID_784985187" MODIFIED="1779148210922" STYLE="fork" TEXT="SEGFAULT">
|
||||
<node COLOR="#338800" CREATED="1779212862037" ID="ID_1851382271" MODIFIED="1779212892822" TEXT="Test kann aber leicht angepaßt werden">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
...und dann verhält sich alles wie erwartet
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1779148203212" FOLDED="true" ID="ID_784985187" MODIFIED="1779215806068" TEXT="SEGFAULT">
|
||||
<font NAME="SansSerif" SIZE="12"/>
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1779148212791" ID="ID_912557487" MODIFIED="1779148225972" TEXT="undurchsichtige Situation">
|
||||
<node CREATED="1779148226905" ID="ID_135139479" MODIFIED="1779148244026" TEXT="hier wird offensichtlich der dtor des thread-local-Objekts aufgerufen"/>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1779148244982" ID="ID_1958707025" MODIFIED="1779149274605" TEXT="aber: der Thread gilt offiziell bereits als terminiert (d.h. main läuft ebenfalls weiter)">
|
||||
<icon BUILTIN="button_cancel"/>
|
||||
<node CREATED="1779148212791" ID="ID_912557487" MODIFIED="1779215781829" TEXT="undurchsichtige Situation">
|
||||
<node CREATED="1779148226905" ID="ID_135139479" MODIFIED="1779215781829" TEXT="hier wird offensichtlich der dtor des thread-local-Objekts aufgerufen"/>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1779148244982" ID="ID_1958707025" MODIFIED="1779215781829" TEXT="aber: der Thread gilt offiziell bereits als terminiert (d.h. main läuft ebenfalls weiter)">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1779149276142" ID="ID_1730131768" MODIFIED="1779149284875" TEXT="das ist ein Problem für das Test-Setup"/>
|
||||
<node CREATED="1779149288603" ID="ID_990204079" MODIFIED="1779149314066" TEXT="weil wir damit zu u.U zu früh prüfen (befor die Allocs zurückgesandt wurden)"/>
|
||||
<node CREATED="1779149276142" ID="ID_1730131768" MODIFIED="1779215781830" TEXT="das ist ein Problem für das Test-Setup"/>
|
||||
<node CREATED="1779149288603" ID="ID_990204079" MODIFIED="1779215781830" TEXT="weil wir damit zu u.U zu früh prüfen (befor die Allocs zurückgesandt wurden)"/>
|
||||
<node CREATED="1779212920394" ID="ID_820337477" MODIFIED="1779215781830" TEXT="habe jetzt 10ms zusätzliche Pause eingebaut"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1779212981320" ID="ID_7683582" MODIFIED="1779215781830" TEXT="(bringt aber nix)">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
selbst 1s warten bringt nix, der SEGFAULT kommt schneller
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1779148268344" ID="ID_1630618662" MODIFIED="1779148295972" TEXT="der clean-up-Hook bekommt eine Instanz mit einer leeren Block-Liste"/>
|
||||
<node CREATED="1779148296997" ID="ID_418820207" MODIFIED="1779148309028" TEXT="aber er iteriert diese, aus unklarem Grund"/>
|
||||
</node>
|
||||
<node CREATED="1779148268344" ID="ID_1630618662" MODIFIED="1779215781830" TEXT="der clean-up-Hook bekommt eine Instanz mit einer leeren Block-Liste"/>
|
||||
<node CREATED="1779148296997" ID="ID_418820207" MODIFIED="1779215781830" TEXT="aber er iteriert diese, aus unklarem Grund"/>
|
||||
</node>
|
||||
<node CREATED="1779213480136" ID="ID_1959546736" MODIFIED="1779215781831" TEXT="genauere Untersuchung">
|
||||
<node CREATED="1779213486900" ID="ID_1675164848" MODIFIED="1779215781831" TEXT="Print-Statements">
|
||||
<node CREATED="1779213496701" ID="ID_211301594" MODIFIED="1779215781831" TEXT="der Main-Thread schläft noch..."/>
|
||||
<node CREATED="1779213508414" ID="ID_180428325" MODIFIED="1779215781831" TEXT="Destruktor ruft den Clean-up Hook"/>
|
||||
<node CREATED="1779213519754" ID="ID_724111756" MODIFIED="1779215781831" TEXT="dort hat die Block-Liste 5 Einträge (korrekt)"/>
|
||||
<node CREATED="1779213534847" ID="ID_392144473" MODIFIED="1779215781831" TEXT="noch Meldung von der 1. Iteration mit einer Mem-Adresse"/>
|
||||
<node CREATED="1779213555317" ID="ID_97951475" MODIFIED="1779215781831" TEXT="Segfault">
|
||||
<node CREATED="1779214868024" ID="ID_1561723869" MODIFIED="1779215781831" TEXT="List ist leer"/>
|
||||
<node CREATED="1779214875444" ID="ID_443229940" MODIFIED="1779215781831" TEXT="aber main-Thread ist dann bereits in eine failed assertion gelaufen"/>
|
||||
<node CREATED="1779214894031" ID="ID_1325180229" MODIFIED="1779215781831" TEXT="nicht klar ob das mit der Debugging-Situation zusammenhängt"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1779215384594" ID="ID_1448842553" MODIFIED="1779215781831" TEXT="Situation anders präparieren">
|
||||
<node CREATED="1779215401622" ID="ID_1163169402" MODIFIED="1779215781831" TEXT="so daß keine zusätzliche Allokation angefordert wird"/>
|
||||
<node CREATED="1779215411016" ID="ID_1634872510" MODIFIED="1779215781831" TEXT="alle Assertions aus Main herausnehmen"/>
|
||||
<node CREATED="1779215424588" ID="ID_1565063764" MODIFIED="1779215781831" TEXT="dort 2x 1sec schlafen"/>
|
||||
<node CREATED="1779215441122" ID="ID_418906350" MODIFIED="1779215781831" TEXT="Eindeutig: Problem in der 2.Iteration vom Clean-up">
|
||||
<node CREATED="1779215460832" ID="ID_1116789626" MODIFIED="1779215781831" TEXT="der Main-Thread ist noch im sleep()"/>
|
||||
<node CREATED="1779215474598" ID="ID_1113629525" MODIFIED="1779215781831" TEXT="die erste Iteration printet"/>
|
||||
<node CREATED="1779215481157" ID="ID_1309368036" MODIFIED="1779215781831" TEXT="in der zweiten ist der aktuelle Block nicht mehr valide"/>
|
||||
<node CREATED="1779215508363" ID="ID_1562083980" MODIFIED="1779215781831" TEXT="und die Liste ist leer"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1779215600010" ID="ID_743469822" MODIFIED="1779215781831" TEXT="AUA: ganz dämlicher Bug — Schleife falsch geklammert">
|
||||
<icon BUILTIN="smily_bad"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1779215763458" ID="ID_1073142525" MODIFIED="1779215781831" TEXT="Fazit. »eigene Dummheit«"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1779215808575" ID="ID_536197148" MODIFIED="1779215834783" TEXT="Assertion-Failure: Lease bleibt offen">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1779215845829" ID="ID_1150970737" MODIFIED="1779215866852" TEXT="Grund ist eine »Zombie-Zustellung«"/>
|
||||
<node CREATED="1779215868024" ID="ID_182421878" MODIFIED="1779215990774" TEXT="Problem hatte ich bereits theoretisch vorhergesehen">
|
||||
<arrowlink COLOR="#fe902e" DESTINATION="ID_931645192" ENDARROW="Default" ENDINCLINATION="-2428;97;" ID="Arrow_ID_1201055029" STARTARROW="None" STARTINCLINATION="659;35;"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
|
|
@ -146941,7 +147252,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1778205155009" ID="ID_536238884" MODIFIED="1778205234412" TEXT="Problem im Job ⟹ Unwind, Worker terminiert">
|
||||
<icon BUILTIN="full-2"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f9e4d6" COLOR="#c00244" CREATED="1778205192805" ID="ID_931645192" MODIFIED="1778205307186">
|
||||
<node BACKGROUND_COLOR="#f9e4d6" COLOR="#c00244" CREATED="1778205192805" ID="ID_931645192" MODIFIED="1779328009963">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
|
|
@ -146956,7 +147267,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<linktarget COLOR="#ff1b15" DESTINATION="ID_931645192" ENDARROW="Default" ENDINCLINATION="-857;174;" ID="Arrow_ID_801625969" SOURCE="ID_1394284825" STARTARROW="None" STARTINCLINATION="171;-11;"/>
|
||||
<linktarget COLOR="#ff1b15" DESTINATION="ID_931645192" ENDARROW="Default" ENDINCLINATION="-937;191;" ID="Arrow_ID_801625969" SOURCE="ID_1394284825" STARTARROW="None" STARTINCLINATION="171;-11;"/>
|
||||
<linktarget COLOR="#fe902e" DESTINATION="ID_931645192" ENDARROW="Default" ENDINCLINATION="-2428;97;" ID="Arrow_ID_1201055029" SOURCE="ID_182421878" STARTARROW="None" STARTINCLINATION="659;35;"/>
|
||||
<icon BUILTIN="clanbomber"/>
|
||||
</node>
|
||||
</node>
|
||||
|
|
@ -147045,6 +147357,43 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<linktarget COLOR="#2b53d4" DESTINATION="ID_255577978" ENDARROW="Default" ENDINCLINATION="443;20;" ID="Arrow_ID_57636127" SOURCE="ID_228507573" STARTARROW="None" STARTINCLINATION="320;11;"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1779327275881" ID="ID_1664117186" MODIFIED="1779327343009" TEXT="und noch ein weiterer Race läßt sich nicht ohne Weiteres schließen">
|
||||
<linktarget COLOR="#b00581" DESTINATION="ID_1664117186" ENDARROW="Default" ENDINCLINATION="-937;0;" ID="Arrow_ID_1984536453" SOURCE="ID_344181114" STARTARROW="None" STARTINCLINATION="-349;22;"/>
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1779327356588" ID="ID_1046958487" MODIFIED="1779327373060" TEXT="selbst mit einem solchen Schutzmechanismus..."/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1779327373930" ID="ID_1071387222" MODIFIED="1779327429911" TEXT="denn eine Atomic-flag ist kein Lock — gesendet wird nach dem Check">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f0c5e6" COLOR="#990033" CREATED="1779327385156" ID="ID_434544755" MODIFIED="1779327557928" TEXT="und ohne einen expliziten Mechanismus kann ich hier nichts machen">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Das hat nichts mit der unmittelbar hier diskutierten Technologie zu tun. Sondern es zeigt eine Diskrepanz in der Architektur: »asynchron« bedeutet immer »eventuell irgendwann« — aber wenn ein Thread terminiert, dann terminiert er <font color="#b40101">SOFORT</font>.
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="stop-sign"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1779327593320" ID="ID_1480276868" MODIFIED="1779327990693" TEXT="das muß »pragmatisch« in der Implementierung abgefangen werden">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
...denn letztlich dichte ich hier ein Restrisiko ab; es würde genügen, wenn der Thread nach dem Sperren des Empfangs noch einen Moment wartet, und dann erst die Eingangsqueue ausräumt. Wir können aber letztlich nicht verhindern, daß der Empfänger-Thread möglicherweise stirbt während der Sender-Thread grade mitten in der Zustellung ist, und durch äußere Umstände dabei ausgebremst wird. Um das zu verhindern, bräuchten wir einen Handshake — und dann wäre die Lösung <i>nicht mehr asynchron!</i>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<arrowlink COLOR="#b92d55" DESTINATION="ID_1344328276" ENDARROW="Default" ENDINCLINATION="341;-476;" ID="Arrow_ID_1052781255" STARTARROW="Default" STARTINCLINATION="-205;854;"/>
|
||||
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1779140860563" ID="ID_1697479221" MODIFIED="1779327578704" TEXT="dedizierten Schutzmechanismus schaffen!">
|
||||
<arrowlink COLOR="#5000ad" DESTINATION="ID_1356813075" ENDARROW="Default" ENDINCLINATION="-430;843;" ID="Arrow_ID_1277922642" STARTARROW="None" STARTINCLINATION="-1130;69;"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1778118843392" ID="ID_45310528" MODIFIED="1778118856450" TEXT="Eingang ≙ zurückgegebene Allokationen">
|
||||
<node CREATED="1778118860685" ID="ID_744369649" MODIFIED="1778118884334" TEXT="hier kommt eine Alloc, d.h. (Buffer-Adr, Size)"/>
|
||||
|
|
@ -147333,9 +147682,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1778507847611" ID="ID_1394284825" MODIFIED="1778507972282" TEXT="Race wegen terminierendem Thread">
|
||||
<arrowlink COLOR="#ff1b15" DESTINATION="ID_931645192" ENDARROW="Default" ENDINCLINATION="-857;174;" ID="Arrow_ID_801625969" STARTARROW="None" STARTINCLINATION="171;-11;"/>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1778507847611" ID="ID_1394284825" MODIFIED="1779328009963" TEXT="Race wegen terminierendem Thread">
|
||||
<arrowlink COLOR="#ff1b15" DESTINATION="ID_931645192" ENDARROW="Default" ENDINCLINATION="-937;191;" ID="Arrow_ID_801625969" STARTARROW="None" STARTINCLINATION="171;-11;"/>
|
||||
<icon BUILTIN="clanbomber"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1779327845116" HGAP="31" ID="ID_1344328276" MODIFIED="1779327990693" TEXT="konnte den Race nur mit Restrisiko abdichten..." VSHIFT="23">
|
||||
<linktarget COLOR="#b92d55" DESTINATION="ID_1344328276" ENDARROW="Default" ENDINCLINATION="341;-476;" ID="Arrow_ID_1052781255" SOURCE="ID_1480276868" STARTARROW="Default" STARTINCLINATION="-205;854;"/>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1778630840458" ID="ID_108123775" MODIFIED="1778630844917" TEXT="Locking">
|
||||
<node CREATED="1778630852336" ID="ID_1688369877" MODIFIED="1778630859835" TEXT="zwei mögliche Ansätze">
|
||||
|
|
|
|||
Loading…
Reference in a new issue