* Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
363 lines
13 KiB
C++
363 lines
13 KiB
C++
/*
|
||
Advice - generic loosely coupled interaction guided by symbolic pattern
|
||
|
||
Copyright (C)
|
||
2010, 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 advice.cpp
|
||
** Implementation of the AdviceSystem, to support the advice collaboration.
|
||
**
|
||
** # Implementation notes
|
||
** The AdviceSystem is implemented as singleton, but is never accessed directly
|
||
** by clients participating in an advice collaboration. Rather, they use the
|
||
** advice::Request and advice::Provision value classes as a frontend. While
|
||
** these frontend classes are templated on the concrete advice type, the common
|
||
** baseclass AdviceLink isn't, allowing the AdviceSystem to operate on type erased
|
||
** PointOfAdvice entries. The protected access functions within AdviceLink are
|
||
** implemented in this compilation unit and access the AdviceSystem singleton
|
||
** defined here locally.
|
||
**
|
||
** ## memory management
|
||
** Advice data, when added by an advice::Provision, is copied into a ActiveProvision,
|
||
** which acts as a value holding buffer. This way, the provided advice data is copied
|
||
** into storage managed by the AdviceSystem, allowing to access the data even after the
|
||
** original advice::Provision went out of scope.
|
||
**
|
||
** But while the Provision is still alive, it may be used to set new advice, modify the
|
||
** binding or even retract the given piece of advice. Thus we need a mechanism to link
|
||
** the ActiveProvision (value holder) to its origin while the latter is still there.
|
||
** Basically we'll use the PointOfAdvice::resolution_ pointer embedded within advice::Provision
|
||
** to point to the ActiveProvision entry incorporated into the advice system.
|
||
** (For advice::Request, the same pointer is used to link to the ActiveProvision yielding
|
||
** the advice solution, if any). Handling the relation between Provision and ActiveProvision
|
||
** this way entails some kind of "unofficial" ownership and is slightly incorrect, but seems
|
||
** the most straight forward implementation. Thus each Provision cares for "his" advice and
|
||
** just detaches when going away. Consequently, by default, advice provisions remain active
|
||
** during the lifetime of the application. We'll see if this causes problems.
|
||
**
|
||
** @note when a Provision is copied, this hidden link is not shared with the copy, which
|
||
** therefore behaves as if newly created with the same binding, but without providing Advice.
|
||
**
|
||
** ## implementing the allocations
|
||
** The problem with copying and incorporating the ActiveProvision objects is the undetermined
|
||
** size of these value holders, because the frontend objects are templated on the advice type,
|
||
** while the AdviceSystem doesn't have any knowledge of the specific advice type. This advice
|
||
** type is used to set a type guard predicate into each binding, but there is no way to
|
||
** re-discover the specifically typed context; the type guards can only be checked for a match.
|
||
** Thus we need the help of the frontend objects, which need to provide a deleter function
|
||
** when providing concrete advice data; this deleter function will be saved as function pointer
|
||
** so to be able to deallocate all advice data when the AdviceSystem is shut down.
|
||
**
|
||
** @todo This approach is closely related to the decision to use a single global index table
|
||
** for managing all advice collaborations. An alternative would be to use either a separate
|
||
** table for each type, or to store an additional type descriptor with this memory management
|
||
** information into each "bucket", which can be assumed to manage entries dealing with the
|
||
** same kind of advice data, because each binding automatically includes a type guard.
|
||
** If we ever happen to get a significant amount of advice data, but only a small
|
||
** number of different advice types, we should reconsider this optimisation.
|
||
**
|
||
** @todo rewrite the allocation to use Lumiera's MPool instead of heap allocations //////TICKET #609
|
||
**
|
||
** ## synchronisation
|
||
** While the frontend objects are deliberately \em not threadsafe, the lookup implementation
|
||
** within the AdviceSystem uses a system wide advice::Index table and thus needs locking.
|
||
** Besides the protection against corrupting the index, this also serves as memory barrier,
|
||
** so that when a new advice solution is determined and set as a pointer within the matching
|
||
** requests, this change is actually "committed" from cache to memory. But note, when using
|
||
** advice::Request concurrently, you need to employ an additional read barrier to ensure
|
||
** your thread/CPU picks up such newly determined solutions from main memory. Otherwise
|
||
** you might even try to access superseded and already deleted advice data.
|
||
**
|
||
** @see advice.hpp
|
||
** @see advice::Index
|
||
**
|
||
*/
|
||
|
||
|
||
#include "lib/error.hpp"
|
||
#include "lib/nocopy.hpp"
|
||
#include "lib/del-stash.hpp"
|
||
#include "lib/depend.hpp"
|
||
#include "lib/symbol.hpp"
|
||
#include "lib/sync.hpp"
|
||
#include "lib/util.hpp"
|
||
#include "include/logging.h"
|
||
#include "common/advice.hpp"
|
||
#include "common/advice/index.hpp"
|
||
|
||
|
||
using lib::Literal;
|
||
using lib::DelStash;
|
||
using util::unConst;
|
||
|
||
typedef void (DeleterFunc)(void*);
|
||
|
||
|
||
namespace lumiera{
|
||
namespace advice {
|
||
|
||
namespace { // ======= implementation of the AdviceSystem ============
|
||
|
||
/**
|
||
* the system-wide service to support the implementation
|
||
* of _advice collaborations_. Manages storage for
|
||
* provided advice data and maintains an index table
|
||
* to determine the advice solutions on request.
|
||
*/
|
||
class AdviceSystem
|
||
: public lib::Sync<>
|
||
, util::NonCopyable
|
||
{
|
||
|
||
DelStash adviceDataRegistry_;
|
||
Index<PointOfAdvice> index_;
|
||
|
||
public:
|
||
AdviceSystem()
|
||
: index_()
|
||
{
|
||
TRACE (library, "Initialising Advice Index tables.");
|
||
}
|
||
|
||
~AdviceSystem()
|
||
{
|
||
TRACE (library, "Shutting down Advice system.");
|
||
}
|
||
|
||
|
||
|
||
/* == Advice data storage management == */
|
||
|
||
/** low-level allocation of storage to hold advice data
|
||
* @todo rewrite to use Lumiera's block allocator / memory pool /////////////////////////////////TICKET #609
|
||
* @warning the raw allocation and deallocation is \em not protected
|
||
* by the AdviceSystem monitor. Currently we don't need
|
||
* locking (heap allocation), but any custom allocator
|
||
* will have to care for its own locking!
|
||
*/
|
||
void*
|
||
allocateBuffer(size_t siz)
|
||
{
|
||
try { return new char[siz]; } /////////////////////////////////////////////////////////////////////TICKET #1204 potentially misaligned
|
||
|
||
catch(std::bad_alloc&)
|
||
{
|
||
throw error::Fatal("Unable to store Advice due to memory exhaustion");
|
||
}
|
||
ERROR_LOG_AND_IGNORE (memory, "Storing a piece of Advice")
|
||
throw error::Fatal("Unable to store Advice data");
|
||
}
|
||
|
||
void
|
||
releaseBuffer (void* buff, size_t) /////////////////////////////////TICKET #609
|
||
{
|
||
delete[] (char*)buff;
|
||
}
|
||
|
||
void
|
||
manageAdviceData (PointOfAdvice* entry, DeleterFunc* how_to_delete)
|
||
{
|
||
Lock sync{this};
|
||
adviceDataRegistry_.manage (entry, how_to_delete);
|
||
}
|
||
|
||
private:
|
||
void
|
||
discardEntry (PointOfAdvice* storedProvision) ///< @note to be invoked from a locked scope
|
||
{
|
||
if (storedProvision)
|
||
{
|
||
adviceDataRegistry_.kill (storedProvision);
|
||
} }
|
||
|
||
|
||
|
||
public:
|
||
/* == forward additions and retractions to the index == */
|
||
|
||
void
|
||
publishRequestBindingChange(PointOfAdvice & req,
|
||
HashVal previous_bindingKey)
|
||
{
|
||
Lock sync{this};
|
||
index_.modifyRequest(previous_bindingKey, req);
|
||
}
|
||
|
||
void
|
||
registerRequest(PointOfAdvice & req)
|
||
{
|
||
Lock sync{this};
|
||
index_.addRequest (req);
|
||
}
|
||
|
||
void
|
||
deregisterRequest(PointOfAdvice const& req)
|
||
{
|
||
try
|
||
{
|
||
Lock sync{this};
|
||
index_.removeRequest (req);
|
||
}
|
||
|
||
catch(...)
|
||
{
|
||
Literal errID = lumiera_error();
|
||
WARN (library, "Problems on deregistration of advice request: %s", errID.c());
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
publishProvision (PointOfAdvice* newProvision, const PointOfAdvice* previousProvision)
|
||
{
|
||
Lock sync{this};
|
||
|
||
if (!previousProvision && newProvision)
|
||
index_.addProvision (*newProvision);
|
||
else
|
||
if (previousProvision && newProvision)
|
||
index_.modifyProvision (*previousProvision, *newProvision);
|
||
else
|
||
if (previousProvision && !newProvision)
|
||
index_.removeProvision (*previousProvision);
|
||
|
||
discardEntry (unConst(previousProvision));
|
||
}
|
||
|
||
void
|
||
discardSolutions (const PointOfAdvice* existingProvision)
|
||
{
|
||
Lock sync{this};
|
||
|
||
if (existingProvision)
|
||
index_.removeProvision (*existingProvision);
|
||
|
||
discardEntry (unConst(existingProvision));
|
||
}
|
||
};
|
||
|
||
|
||
/** hidden implementation-level access to the AdviceSystem */
|
||
lib::Depend<AdviceSystem> aSys;
|
||
|
||
|
||
} //(End) AdviceSystem implementation
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/* ====== AdviceLink : access point for Provisions and Requests ====== */
|
||
|
||
|
||
/** allocate raw storage for a buffer holding the actual piece of advice.
|
||
* We need to manage this internally, as the original advice::Provision
|
||
* may go out of scope, while the advice information as such remains valid.
|
||
* @note the special twist is the size of the buffer depending on the actual
|
||
* advice type, which type information we need to erase for tracking all
|
||
* advice provisions and requests through an generic index datastructure.
|
||
* @throws error::Fatal on allocation failure
|
||
*/
|
||
void*
|
||
AdviceLink::getBuffer(size_t siz)
|
||
{
|
||
return aSys().allocateBuffer(siz);
|
||
}
|
||
|
||
|
||
void
|
||
AdviceLink::releaseBuffer (void* buff, size_t siz)
|
||
{
|
||
aSys().releaseBuffer(buff, siz);
|
||
}
|
||
|
||
|
||
/** Store a descriptor record to take ownership of the given allocation.
|
||
* Problem is we need to know the exact size of the advice value holder,
|
||
* which information is available initially, when the advice data is
|
||
* copied into the system. The knowledge about the size of the allocation
|
||
* is embodied into the deleter function. This allows later to discard
|
||
* entries without needing to know their exact type.
|
||
*/
|
||
void
|
||
AdviceLink::manageAdviceData (PointOfAdvice* entry, DeleterFunc* how_to_delete)
|
||
{
|
||
aSys().manageAdviceData (entry,how_to_delete);
|
||
}
|
||
|
||
|
||
|
||
|
||
/** when the Provision actually sets advice data, this is copied
|
||
* into an internal buffer within the AdviceSystem. We then use the
|
||
* Index to remember the presence of this advice data and to detect
|
||
* possible matches with existing advice::Request entries.
|
||
* @param adviceData pointer to the copied data,
|
||
* actually pointing to an ActiveProvision<AD>
|
||
* @return pointer to an superseded old provision entry,
|
||
* which the caller then needs to de-allocate.
|
||
* The caller is assumed to know the actual type
|
||
* and thus the size of the entry to deallocate.
|
||
* Returning `NULL` in case no old entry exists.
|
||
*/
|
||
void
|
||
AdviceLink::publishProvision (PointOfAdvice* newProvision)
|
||
{
|
||
const PointOfAdvice* previousProvision (getSolution());
|
||
this->setSolution (newProvision);
|
||
|
||
aSys().publishProvision (newProvision, previousProvision);
|
||
}
|
||
|
||
|
||
/** when advice is retracted explicitly,
|
||
* after removing the provision index entry
|
||
* we also need to re-process any requests
|
||
* which happen to match our binding...
|
||
* @return pointer to the existing provision entry,
|
||
* to be deallocated by the caller, which
|
||
* is assumed to know it's exact type.
|
||
*/
|
||
void
|
||
AdviceLink::discardSolutions ()
|
||
{
|
||
const PointOfAdvice* existingProvision (getSolution());
|
||
this->setSolution ( NULL );
|
||
aSys().discardSolutions (existingProvision);
|
||
}
|
||
|
||
|
||
void
|
||
AdviceLink::publishRequestBindingChange(HashVal previous_bindingKey)
|
||
{
|
||
aSys().publishRequestBindingChange (*this, previous_bindingKey);
|
||
}
|
||
|
||
|
||
void
|
||
AdviceLink::registerRequest()
|
||
{
|
||
aSys().registerRequest (*this);
|
||
}
|
||
|
||
|
||
void
|
||
AdviceLink::deregisterRequest()
|
||
{
|
||
aSys().deregisterRequest (*this);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
}} // namespace lib::advice
|