LUMIERA.clone/src/common/advice/advice.cpp

371 lines
13 KiB
C++
Raw Normal View History

/*
Advice - generic loosely coupled interaction guided by symbolic pattern
2010-12-17 23:28:49 +01:00
Copyright (C) Lumiera.org
2010, Hermann Vosseler <Ichthyostega@web.de>
2010-12-17 23:28:49 +01:00
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
2010-12-17 23:28:49 +01:00
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.
2010-12-17 23:28:49 +01:00
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.
2010-12-17 23:28:49 +01:00
* *****************************************************/
/** @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.
**
2010-06-12 19:06:56 +02:00
** @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*);
2010-06-12 19:06:56 +02:00
namespace lumiera{
namespace advice {
namespace { // ======= implementation of the AdviceSystem ============
2010-06-12 19:06:56 +02:00
/**
* 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_;
2010-06-12 19:06:56 +02:00
Index<PointOfAdvice> index_;
public:
AdviceSystem()
2010-06-12 19:06:56 +02:00
: index_()
{
INFO (library, "Initialising Advice Index tables.");
}
~AdviceSystem()
{
INFO (library, "Shutting down Advice system.");
}
2010-06-12 19:06:56 +02:00
/* == 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!
2010-06-12 19:06:56 +02:00
*/
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");
2010-06-12 19:06:56 +02:00
}
void
releaseBuffer (void* buff, size_t) /////////////////////////////////TICKET #609
{
delete[] (char*)buff;
}
void
manageAdviceData (PointOfAdvice* entry, DeleterFunc* how_to_delete)
{
Lock sync (this);
2010-06-12 19:06:56 +02:00
adviceDataRegistry_.manage (entry, how_to_delete);
}
private:
2010-06-12 19:06:56 +02:00
void
discardEntry (PointOfAdvice* storedProvision) ///< @note to be invoked from a locked scope
2010-06-12 19:06:56 +02:00
{
if (storedProvision)
{
adviceDataRegistry_.kill (storedProvision);
} }
public:
2010-06-12 19:06:56 +02:00
/* == forward additions and retractions to the index == */
void
publishRequestBindingChange(PointOfAdvice & req,
HashVal previous_bindingKey)
{
Lock sync (this);
2010-06-12 19:06:56 +02:00
index_.modifyRequest(previous_bindingKey, req);
}
void
registerRequest(PointOfAdvice & req)
{
Lock sync (this);
2010-06-12 19:06:56 +02:00
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());
}
2010-06-12 19:06:56 +02:00
}
void
publishProvision (PointOfAdvice* newProvision, const PointOfAdvice* previousProvision)
{
Lock sync (this);
2010-06-12 19:06:56 +02:00
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);
2010-06-12 19:06:56 +02:00
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.
2010-06-12 19:06:56 +02:00
* 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)
{
2010-06-12 19:06:56 +02:00
return aSys().allocateBuffer(siz);
}
void
2010-06-12 19:06:56 +02:00
AdviceLink::releaseBuffer (void* buff, size_t siz)
{
2010-06-12 19:06:56 +02:00
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);
}
2010-06-12 19:06:56 +02:00
/** 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);
2010-06-12 19:06:56 +02:00
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 );
2010-06-12 19:06:56 +02:00
aSys().discardSolutions (existingProvision);
}
void
AdviceLink::publishRequestBindingChange(HashVal previous_bindingKey)
{
2010-06-12 19:06:56 +02:00
aSys().publishRequestBindingChange (*this, previous_bindingKey);
}
void
AdviceLink::registerRequest()
{
2010-06-12 19:06:56 +02:00
aSys().registerRequest (*this);
}
void
AdviceLink::deregisterRequest()
{
2010-06-12 19:06:56 +02:00
aSys().deregisterRequest (*this);
}
}} // namespace lib::advice