draft: automatically invoke an attached ctor/dtor functor

This commit is contained in:
Fischlurch 2011-10-30 05:35:36 +01:00
parent ccd130966b
commit 931dc3f883
3 changed files with 157 additions and 55 deletions

View file

@ -395,10 +395,10 @@ namespace engine {
: public Key
{
BufferState state_;
const void* buffer_;
void* buffer_;
protected:
Entry (Key const& parent, const void* bufferPtr =0)
Entry (Key const& parent, void* bufferPtr =0)
: Key (Key::forEntry (parent, bufferPtr))
, state_(bufferPtr? LOCKED:NIL)
, buffer_(bufferPtr)
@ -435,8 +435,8 @@ namespace engine {
return state_;
}
const void*
access() const
void*
access()
{
__must_not_be_NIL();
__must_not_be_FREE();
@ -458,7 +458,8 @@ namespace engine {
||(state_ == BLOCKED && newState == FREE))
{
// allowed transition
if (newState == FREE) buffer_ = 0;
if (newState == FREE)
invokeEmbeddedDtor_and_clear();
state_ = newState;
return *this;
}
@ -466,6 +467,29 @@ namespace engine {
throw error::Fatal ("Invalid buffer state encountered.");
}
protected:
/** @internal maybe invoke a registered TypeHandler's
* constructor function, which typically builds some
* content object into the buffer by placement new. */
void
invokeEmbeddedCtor()
{
REQUIRE (buffer_);
if (nontrivial (instanceFunc_))
instanceFunc_.createAttached (buffer_);
}
/** @internal maybe invoke a registered TypeHandler's
* destructor function, which typically clears up some
* content object living within the buffer */
void
invokeEmbeddedDtor_and_clear()
{
REQUIRE (buffer_);
if (nontrivial (instanceFunc_))
instanceFunc_.destroyAttached (buffer_);
buffer_ = 0;
}
private:
void
@ -668,9 +692,9 @@ namespace engine {
* @note might create/register a new Entry as a side-effect
*/
Key const&
key (Key const& parentKey, const void* concreteBuffer)
key (Key const& parentKey, void* concreteBuffer)
{
return get (parentKey,concreteBuffer);
return lock (parentKey,concreteBuffer);
}
/** core operation to access or create a concrete buffer metadata entry.
@ -678,6 +702,11 @@ namespace engine {
* which denotes a buffer type, and the concrete buffer address. If yet
* unknown, a new concrete buffer metadata Entry is created and initialised
* to LOCKED state. Otherwise just the existing Entry is fetched.
* @note this function really \em activates the buffer.
* In case the type (Key) involves a TypeHandler (functor),
* its constructor function will be invoked, if actually a new
* entry gets created. Typically this mechanism will be used
* to placement-create an object into the buffer.
* @param parentKey a key describing the \em type of the buffer
* @param concreteBuffer storage pointer, must not be NULL
* @param onlyNew disallow fetching an existing entry
@ -691,7 +720,7 @@ namespace engine {
* buffer is released or re-used later.
*/
Entry&
get (Key const& parentKey, const void* concreteBuffer, bool onlyNew =false)
lock (Key const& parentKey, void* concreteBuffer, bool onlyNew =false)
{
if (!concreteBuffer)
throw error::Invalid ("Attempt to lock a slot for a NULL buffer"
@ -705,7 +734,7 @@ namespace engine {
, error::LUMIERA_ERROR_LIFECYCLE );
if (!existing)
return table_.store (newEntry);
return store_and_lock (newEntry); // actual creation
else
return *existing;
}
@ -743,10 +772,45 @@ namespace engine {
}
/* == memory management == */
Entry& markLocked (Key const& parentKey, const void* buffer);
void release (HashVal key);
/* == memory management operations == */
/** combine the type (Key) with a concrete buffer,
* thereby marking this buffer as locked. Store a concrete
* metadata Entry to account for this fact. This might include
* invoking a constructor function, in case the type (Key)
* defines a (nontrivial) TypeHandler.
* @throw error::Fatal when locking a NULL buffer
* @throw exceptions which might be raised by a TypeHandler's
* constructor function. In this case, the Entry remains
* created, but is marked as FREE
*/
Entry&
markLocked (Key const& parentKey, void* buffer)
{
if (!buffer)
throw error::Fatal ("Attempt to lock for a NULL buffer. Allocation floundered?"
, error::LUMIERA_ERROR_BOTTOM_VALUE);
return this->lock(parentKey, buffer, true); // force creation of a new entry
}
/** purge the bare metadata Entry from the metadata tables.
* @throw error::Logic if the entry isn't marked FREE already
*/
void
release (HashVal key)
{
Entry* entry = table_.fetch (key);
if (!entry) return;
if (entry && (FREE != entry->state()))
throw error::Logic ("Attempt to release a buffer still in use"
, error::LUMIERA_ERROR_LIFECYCLE);
table_.remove (key);
}
private:
@ -765,40 +829,28 @@ namespace engine {
if (isKnown (key)) return;
table_.store (Entry (key, NULL));
}
Entry&
store_and_lock (Entry const& metadata)
{
Entry& newEntry = table_.store (metadata);
try
{
newEntry.invokeEmbeddedCtor();
}
catch(...)
{
newEntry.mark(FREE);
throw;
}
return newEntry;
}
};
/** */
inline BufferMetadata::Entry&
BufferMetadata::markLocked (Key const& parentKey, const void* buffer)
{
if (!buffer)
throw error::Fatal ("Attempt to lock for a NULL buffer. Allocation floundered?"
, error::LUMIERA_ERROR_BOTTOM_VALUE);
return this->get (parentKey, buffer, true); // force creation of a new entry
}
inline void
BufferMetadata::release (HashVal key)
{
Entry* entry = table_.fetch (key);
if (!entry) return;
if (entry && (FREE != entry->state()))
throw error::Logic ("Attempt to release a buffer still in use"
, error::LUMIERA_ERROR_LIFECYCLE);
table_.remove (key);
}
} // namespace engine
#endif

View file

@ -37,13 +37,16 @@
//#include <boost/format.hpp>
#include <boost/scoped_ptr.hpp>
#include <cstdlib>
#include <cstring>
//#include <iostream>
//using boost::format;
//using std::string;
//using std::cout;
//using util::for_each;
using std::strncpy;
using boost::scoped_ptr;
using lib::test::randStr;
using util::isnil;
using util::isSameObject;
@ -66,13 +69,24 @@ namespace test {
const size_t SIZE_A = 1 + rand() % TEST_MAX_SIZE;
const size_t SIZE_B = 1 + rand() % TEST_MAX_SIZE;
const HashVal JUST_SOMETHING = 123;
const void* const SOME_POINTER = &JUST_SOMETHING;
HashVal JUST_SOMETHING = 123;
void* const SOME_POINTER = &JUST_SOMETHING;
// const uint TEST_SIZE = 1024*1024;
// const uint TEST_ELMS = 20;
}
template<typename TY>
TY&
accessAs (metadata::Entry& entry)
{
TY* ptr = reinterpret_cast<TY*> (entry.access());
ASSERT (ptr);
return *ptr;
}
}//(End) Test fixture and helpers
/*******************************************************************
@ -231,10 +245,6 @@ namespace test {
CHECK (LOCKED == r0.state());
CHECK (LOCKED == r1.state());
// for the TestFrame buffers, additionally we'd have to create/attach an object
attachTestFrame.createAttached (frames+0); ////////////////////////////////////////TODO: shouldn't this happen automatically??
attachTestFrame.createAttached (frames+1); //////////////////////////TODO: answer: yes. Metadata is exactly the entity which has all necessary information
attachTestFrame.createAttached (frames+2);
CHECK (f0.access() == frames+0);
CHECK (f1.access() == frames+1);
@ -242,6 +252,11 @@ namespace test {
CHECK (r0.access() == rawbuf+0);
CHECK (r1.access() == rawbuf+1);
TestFrame defaultFrame;
CHECK (defaultFrame == f0.access());
CHECK (defaultFrame == f1.access());
CHECK (defaultFrame == f2.access());
// at that point, we'd return BuffHandles to the client
HashVal handle_f0(f0);
HashVal handle_f1(f1);
@ -249,19 +264,31 @@ namespace test {
HashVal handle_r0(r0);
HashVal handle_r1(r1);
// client uses the buffers
// client uses the buffers---------------------(Start)
accessAs<TestFrame> (f0) = testData(1);
accessAs<TestFrame> (f1) = testData(2);
accessAs<TestFrame> (f2) = testData(3);
//////////////////TODO: access the storage through the metadata-key
//////////////////TODO: to a state transition on the metadata
f0.mark(FREE);
CHECK (TestFrame::isAlive (f0.access()));
CHECK (TestFrame::isAlive (f1.access()));
CHECK (TestFrame::isAlive (f2.access()));
strncpy (& accessAs<char> (r0), randStr(SIZE_B - 1).c_str(), SIZE_B);
strncpy (& accessAs<char> (r1), randStr(SIZE_B - 1).c_str(), SIZE_B);
// client might trigger some state transitions
f0.mark(EMITTED);
f1.mark(EMITTED);
f1.mark(BLOCKED);
// client uses the buffers---------------------(End)
f0.mark(FREE); // note: this implicitly invoked the embedded dtor
f1.mark(FREE);
f2.mark(FREE);
r0.mark(FREE);
r1.mark(FREE);
attachTestFrame.destroyAttached (frames+0); ////////////////////////////////////////TODO: shouldn't this happen automatically??
attachTestFrame.destroyAttached (frames+1);
attachTestFrame.destroyAttached (frames+2);
meta_->release(handle_f0);
meta_->release(handle_f1);
@ -269,6 +296,10 @@ namespace test {
meta_->release(handle_r0);
meta_->release(handle_r1);
CHECK (TestFrame::isDead (&frames[0]));
CHECK (TestFrame::isDead (&frames[1]));
CHECK (TestFrame::isDead (&frames[2]));
// manual cleanup of test allocations
delete[] frames;
delete[] rawbuf;

View file

@ -56,6 +56,25 @@ namespace test {
public:
/** Helper to verify a given memory location holds
* an active TestFrame instance (created, not yet destroyed)
* @return true if the TestFrame datastructure is intact and
* marked as still alive.
*/
static bool
isAlive (void* memLocation)
{
UNIMPLEMENTED ("access memory as TestFrame and check internal accounting");
}
/** Helper to verify a given memory location holds
* an already destroyed TestFrame instance */
static bool
isDead (void* memLocation)
{
UNIMPLEMENTED ("access memory as TestFrame and verify dtor invocation");
}
bool
operator== (void* memLocation)
{