Added warning tags to several headers of the first config system draft from 2008 since this effort is stalled and likely to be implemented differently
370 lines
13 KiB
C++
370 lines
13 KiB
C++
/*
|
|
Advice - generic loosely coupled interaction guided by symbolic pattern
|
|
|
|
Copyright (C) Lumiera.org
|
|
2010, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
This program 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.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
* *****************************************************/
|
|
|
|
|
|
/** @file advice.cpp
|
|
** Implementation of the AdviceSystem, to support the advice collaboration.
|
|
** 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/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"
|
|
|
|
#include <boost/noncopyable.hpp>
|
|
|
|
using lib::Symbol;
|
|
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 \em 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<>
|
|
, boost::noncopyable
|
|
{
|
|
|
|
DelStash adviceDataRegistry_;
|
|
Index<PointOfAdvice> index_;
|
|
|
|
public:
|
|
AdviceSystem()
|
|
: index_()
|
|
{
|
|
INFO (library, "Initialising Advice Index tables.");
|
|
}
|
|
|
|
~AdviceSystem()
|
|
{
|
|
INFO (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]; }
|
|
|
|
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(...)
|
|
{
|
|
Symbol 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 \c 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
|