/* Advice - generic loosely coupled interaction guided by symbolic pattern Copyright (C) Lumiera.org 2010, Hermann Vosseler 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 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 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 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 * @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