Advice colaboration: implemented and passes basic unit test
This commit is contained in:
parent
7d93dae8ea
commit
2360f9b4c0
5 changed files with 154 additions and 95 deletions
|
|
@ -32,7 +32,7 @@
|
|||
** Lumiera. Creating this abstraction was partially inspired by aspect oriented programming, especially
|
||||
** the idea of cross-cutting the primary dependency hierarchy. Another source of inspiration where the
|
||||
** various incarnations of properties with dynamic binding. For defining the actual binding, we rely
|
||||
** on predicate notation and matching (later unification) as known from rule based systems.
|
||||
** on predicate notation and matching (planned: unification) as known from rule based systems.
|
||||
**
|
||||
** <b>Definition</b>: Advice is an optional, mediated collaboration between entities taking on
|
||||
** the roles of advisor and advised, thereby passing a custom piece of advice data, managed by
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
** advisor need to share knowledge about the meaning of this advice data. The actual advice collaboration
|
||||
** happens at a \em point-of-advice, which needs to be derived first. To this end, the advised puts up an
|
||||
** \em request by providing his \em binding, which is a pattern for matching. An entity about to give advice
|
||||
** opens possible \advice \em channels by putting up an advisor binding, which similarly is a pattern. The
|
||||
** opens possible advice \em channels by putting up an advisor binding, which similarly is a pattern. The
|
||||
** advice \em system as mediator resolves both sides, by matching (which in the most general case could be
|
||||
** an unification). This process creates an advice point \em solution -- allowing the advisor to fed the
|
||||
** piece of advice into the advice channel, causing it to be placed into the point of advice. After passing
|
||||
|
|
@ -64,11 +64,13 @@
|
|||
** form of advice available, thereby completely decoupling the advised entity from the timings related
|
||||
** to this collaboration.
|
||||
**
|
||||
** TODO WIP-WIP
|
||||
**
|
||||
** @note as of 4/2010 this is an experimental setup and implemented just enough to work out
|
||||
** the interfaces. Ichthyo expects this collaboration service to play a central role
|
||||
** at various places within proc-layer.
|
||||
** @note as of 6/2010 this is an experimental setup and implemented just enough to work out
|
||||
** the interfaces and gain practical usage experiences. Ichthyo expects this collaboration
|
||||
** service to play a central role at various places within proc-layer.
|
||||
** @todo allow variables in binding patterns
|
||||
** @todo use the lumiera MPool instead of heap allocations
|
||||
** @todo consider to provide variations of the basic behaviour by policy classes
|
||||
** @todo the implementation is generic/defensive, and could be improved and optimised
|
||||
**
|
||||
** @see configrules.hpp
|
||||
** @see typed-lookup.cpp corresponding implementation
|
||||
|
|
@ -82,20 +84,17 @@
|
|||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
//#include "proc/asset.hpp"
|
||||
//#include "proc/asset/struct-scheme.hpp"
|
||||
//#include "lib/hash-indexed.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
#include "lib/null-value.hpp"
|
||||
#include "lib/symbol.hpp"
|
||||
#include "lib/advice/binding.hpp"
|
||||
#include "lib/symbol.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
//#include <boost/operators.hpp>
|
||||
//#include <tr1/memory>
|
||||
//#include <iostream>
|
||||
//#include <string>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace lib { ///////TODO: how to arrange the namespaces best?
|
||||
using util::isSameObject;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace advice {
|
||||
|
||||
/**
|
||||
|
|
@ -105,12 +104,12 @@ namespace advice {
|
|||
{
|
||||
Binding::Matcher pattern_;
|
||||
PointOfAdvice* resolution_;
|
||||
|
||||
|
||||
protected:
|
||||
/** define or re-define the binding, which
|
||||
* specifically labels this attachment to the advice system.
|
||||
* @note issuing this on an existing connection is equivalent
|
||||
* to re-connecting with the new binding.
|
||||
* to re-connecting with the new binding.
|
||||
*/
|
||||
void setBindingPattern (Binding const& binding)
|
||||
{
|
||||
|
|
@ -187,7 +186,7 @@ namespace advice {
|
|||
|
||||
// using default copy/assignment
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -196,12 +195,6 @@ namespace advice {
|
|||
* Access point for the advising entity (server).
|
||||
* TODO type comment
|
||||
*
|
||||
* TODO planned implementation of memory handling: see notes in TiddlyWiki
|
||||
* Basically I'll use this->resolution_ to point to the copy incorporated into the advice system.
|
||||
* This is some kind of "unofficial" ownership and 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
|
||||
* aren't freed during the lifetime of the application. We'll see if this causes problems.
|
||||
*
|
||||
* @todo currently leaking buffer storage for all the advice data which isn't explicitly retracted.
|
||||
*/
|
||||
template<class AD>
|
||||
|
|
@ -230,6 +223,22 @@ namespace advice {
|
|||
this->deregistrate();
|
||||
}
|
||||
|
||||
Provision (Provision const& o)
|
||||
: AdviceLink(o)
|
||||
{
|
||||
setSolution (this, NULL );
|
||||
}
|
||||
|
||||
Provision&
|
||||
operator= (Provision const& o)
|
||||
{
|
||||
if (!isSameObject(*this, o))
|
||||
{
|
||||
AdviceLink::operator= (o);
|
||||
setSolution (this, NULL );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void setAdvice (AD const& pieceOfAdvice)
|
||||
{
|
||||
|
|
@ -267,11 +276,12 @@ namespace advice {
|
|||
*
|
||||
* @note the ptr-to-solution in the inherited PointOfAdvice
|
||||
* is currently (5/10) not used, because this \em is
|
||||
* already the solution.
|
||||
* already the solution.
|
||||
*/
|
||||
template<class AD>
|
||||
class ActiveProvision
|
||||
: public PointOfAdvice
|
||||
, boost::noncopyable
|
||||
{
|
||||
AD theAdvice_;
|
||||
|
||||
|
|
@ -343,12 +353,12 @@ namespace advice {
|
|||
storeCopy (solution->getAdvice())));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Access point for the advised entity (client).
|
||||
* TODO type comment
|
||||
|
|
@ -362,7 +372,11 @@ namespace advice {
|
|||
|
||||
/* == policy definitions == */ ////TODO: extract into policy classes
|
||||
|
||||
AD const& handleMissingSolution() const { return NullValue<AD>::get(); } ///< @warning might segfault when used during shutdown
|
||||
AD const&
|
||||
handleMissingSolution() const ///< @warning might segfault when used during shutdown
|
||||
{
|
||||
return NullValue<AD>::get();
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
|
|
@ -378,6 +392,8 @@ namespace advice {
|
|||
deregisterRequest();
|
||||
}
|
||||
|
||||
// copying Requests is allowed, using default
|
||||
|
||||
|
||||
AD const&
|
||||
getAdvice() const
|
||||
|
|
@ -399,7 +415,7 @@ namespace advice {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,67 @@
|
|||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file advice.cpp
|
||||
** Implementation 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.
|
||||
**
|
||||
** \par 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.
|
||||
**
|
||||
** \par 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 currently this is unimplemented and we happily leak memory....
|
||||
** @todo rewrite the allocation to use Lumiera's mpool instead of heap allocations
|
||||
**
|
||||
** \par 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/advice.hpp"
|
||||
#include "lib/advice/index.hpp"
|
||||
#include "lib/singleton.hpp"
|
||||
|
|
@ -33,7 +94,6 @@ using lib::Singleton;
|
|||
namespace lib {
|
||||
namespace advice {
|
||||
|
||||
// LUMIERA_ERROR_DEFINE (MISSING_INSTANCE, "Existing ID registration without associated instance");
|
||||
|
||||
namespace { // ======= implementation of the AdviceSystem ============
|
||||
|
||||
|
|
@ -60,12 +120,13 @@ namespace advice {
|
|||
|
||||
|
||||
} //(End) AdviceSystem implementation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ====== AdviceLink : access point for Provisions and Requests ====== */
|
||||
|
||||
|
||||
/* ====== AdviceLink : access point for Provisions and Requests ====== */
|
||||
|
||||
|
||||
/** allocate raw storage for a buffer holding the actual piece of advice.
|
||||
|
|
@ -92,7 +153,7 @@ namespace advice {
|
|||
{
|
||||
delete[] (char*)buff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** when the Provision actually sets advice data, this is copied
|
||||
|
|
@ -105,7 +166,7 @@ namespace advice {
|
|||
* 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.
|
||||
* Returning \c NULL in case no old entry exists.
|
||||
*/
|
||||
const PointOfAdvice*
|
||||
AdviceLink::publishProvision (PointOfAdvice* newProvision)
|
||||
|
|
@ -164,8 +225,8 @@ namespace advice {
|
|||
{
|
||||
aSys().removeRequest (*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
PLANNED "Advice collaboration (basics)" AdviceBasics_test <<END
|
||||
TEST "Advice collaboration (basics)" AdviceBasics_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
PLANNED "Advice index implementation" AdviceIndex_test <<END
|
||||
TEST "Advice index implementation" AdviceIndex_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
|
|
|||
|
|
@ -22,35 +22,11 @@
|
|||
|
||||
|
||||
#include "lib/test/run.hpp"
|
||||
//#include "lib/test/test-helper.hpp"
|
||||
|
||||
#include "lib/advice.hpp"
|
||||
//#include "lib/p.hpp"
|
||||
//#include "proc/assetmanager.hpp"
|
||||
//#include "proc/asset/inventory.hpp"
|
||||
//#include "proc/mobject/session/clip.hpp"
|
||||
//#include "proc/mobject/session/track.hpp"
|
||||
//#include "lib/meta/trait-special.hpp"
|
||||
//#include "lib/util-foreach.hpp"
|
||||
//#include "lib/symbol.hpp"
|
||||
|
||||
//#include <iostream>
|
||||
//#include <string>
|
||||
#include <cstdlib>
|
||||
|
||||
//using lib::test::showSizeof;
|
||||
//using lib::test::randStr;
|
||||
//using util::isSameObject;
|
||||
//using util::and_all;
|
||||
//using util::for_each;
|
||||
//using util::isnil;
|
||||
//using lib::Literal;
|
||||
//using lib::Symbol;
|
||||
//using lumiera::P;
|
||||
//using std::string;
|
||||
using std::rand;
|
||||
//using std::cout;
|
||||
//using std::endl;
|
||||
|
||||
|
||||
|
||||
|
|
@ -58,7 +34,8 @@ namespace lib {
|
|||
namespace advice {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
namespace { // Some test classes using the advice system...
|
||||
|
||||
|
||||
class TheAdvised
|
||||
: private advice::Request<int>
|
||||
|
|
@ -108,7 +85,7 @@ namespace test {
|
|||
void
|
||||
clear()
|
||||
{
|
||||
link_.retractAdvice();
|
||||
link_.retractAdvice();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -122,8 +99,6 @@ namespace test {
|
|||
* typical situation: two unrelated entities exchange a piece of data
|
||||
* just by referring to a symbolic topic ID.
|
||||
*
|
||||
* @todo partially unimplemented and thus commented out ////////////////////TICKET #605
|
||||
*
|
||||
* @see advice.hpp
|
||||
* @see AdviceSituations_test
|
||||
* @see AdviceMultiplicity_test
|
||||
|
|
@ -202,8 +177,8 @@ namespace test {
|
|||
void
|
||||
overwriting_and_retracting()
|
||||
{
|
||||
TheAdvised client1 ("topic1");
|
||||
TheAdvised client2 ("topic2");
|
||||
TheAdvised client1 ("slot1");
|
||||
TheAdvised client2 ("slot2");
|
||||
CHECK (client1.got(0));
|
||||
CHECK (client2.got(0));
|
||||
|
||||
|
|
@ -211,7 +186,7 @@ namespace test {
|
|||
int r2 (1 + (rand() % 1000));
|
||||
|
||||
{
|
||||
TheAdvisor server("topic1()");
|
||||
TheAdvisor server("slot1()");
|
||||
CHECK (client1.got(0));
|
||||
CHECK (client2.got(0));
|
||||
|
||||
|
|
@ -223,7 +198,7 @@ namespace test {
|
|||
CHECK (client1.got(r2));
|
||||
CHECK (client2.got(0));
|
||||
|
||||
server.rebind("topic2()");
|
||||
server.rebind("slot2()");
|
||||
CHECK (client1.got(0));
|
||||
CHECK (client2.got(r2));
|
||||
}
|
||||
|
|
@ -232,7 +207,7 @@ namespace test {
|
|||
CHECK (client2.got(r2));
|
||||
|
||||
{
|
||||
TheAdvisor anotherServer("topic1");
|
||||
TheAdvisor anotherServer("slot1");
|
||||
CHECK (client1.got(0));
|
||||
CHECK (client2.got(r2));
|
||||
|
||||
|
|
@ -245,7 +220,7 @@ namespace test {
|
|||
CHECK (client2.got(r2));
|
||||
|
||||
{
|
||||
TheAdvisor yetAnotherServer("topic2");
|
||||
TheAdvisor yetAnotherServer("slot2");
|
||||
CHECK (client1.got(r1));
|
||||
CHECK (client2.got(r2));
|
||||
|
||||
|
|
@ -253,32 +228,32 @@ namespace test {
|
|||
CHECK (client1.got(r1));
|
||||
CHECK (client2.got(r1));
|
||||
|
||||
yetAnotherServer.rebind("topic1");
|
||||
yetAnotherServer.rebind("slot1");
|
||||
CHECK (client1.got(r1));
|
||||
CHECK (client2.got(0));
|
||||
|
||||
CHECK (client2.got(r2)); // ideally it should be 0, but actually we uncover the old provision
|
||||
// the decision was to err for a simple implementation /////////TICKET #623
|
||||
yetAnotherServer.clear();
|
||||
CHECK (client1.got(0));
|
||||
CHECK (client2.got(0));
|
||||
CHECK (client1.got(r1)); // should be 0, but again the existing provision is uncovered
|
||||
CHECK (client2.got(r2)); // should be 0
|
||||
|
||||
yetAnotherServer.rebind("topic2");
|
||||
CHECK (client1.got(0));
|
||||
CHECK (client2.got(0));
|
||||
yetAnotherServer.rebind("slot2"); // no effect, because it doesn't provide advice anymore
|
||||
CHECK (client1.got(r1));
|
||||
CHECK (client2.got(r2));
|
||||
|
||||
yetAnotherServer.publish (r1);
|
||||
CHECK (client1.got(0));
|
||||
CHECK (client2.got(r1));
|
||||
yetAnotherServer.publish (5);
|
||||
CHECK (client1.got(r1));
|
||||
CHECK (client2.got(5));
|
||||
}
|
||||
|
||||
CHECK (client1.got(0));
|
||||
CHECK (client2.got(r1));
|
||||
|
||||
client1.rebind("topic2");
|
||||
CHECK (client1.got(r1));
|
||||
CHECK (client2.got(r1));
|
||||
CHECK (client2.got(5));
|
||||
|
||||
client2.rebind("nonExistingTopic");
|
||||
CHECK (client1.got(r1));
|
||||
client1.rebind("slot2");
|
||||
CHECK (client1.got(5));
|
||||
CHECK (client2.got(5));
|
||||
|
||||
client2.rebind("nonExistingSlot");
|
||||
CHECK (client1.got(5));
|
||||
CHECK (client2.got(0));
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -540,7 +540,7 @@ In a more elaborate scheme, the advised entity could provide a signal to be invo
|
|||
&rarr; AdviceImplementation
|
||||
</pre>
|
||||
</div>
|
||||
<div title="AdviceImplementation" modifier="Ichthyostega" modified="201006041639" created="201004100056" tags="impl draft img" changecount="57">
|
||||
<div title="AdviceImplementation" modifier="Ichthyostega" modified="201006050152" created="201004100056" tags="impl draft img" changecount="58">
|
||||
<pre>[<img[Advice solution|uml/fig141573.png]]
|
||||
|
||||
|
||||
|
|
@ -580,6 +580,13 @@ Aside from the index, handling of the advice provisions turns out to be tricky.
|
|||
* but as this simple solution contradicts the general advice semantics in a subtle way (see previous paragraph), we could insist on really re-capturing and retracting previous advice automatically on each new advice provision or modification. In this case, due to the requirement of thread safety, each addition, binding modification, placing of new advice or retraction would require to do an index search to find an existing provision with equivalent binding (same binding definition, not just a matching binding pattern). As a later provision could stomp upon an existing provision without the original advisor noticing this, we can't use the internal references anymore; we really need to search each time and also need a global lock during the modification transaction.
|
||||
* an attempt to reduce this considerable overhead would be to use an back-link from the provision as added to the system to the original source (the ~AdviceProvision owned by the advisor). On modification, this original source would be notified and thus detached. Of course this is tricky to implement correctly, and also requires locking.
|
||||
The decision for the initial implementation is to use the first variant and just accept the slightly imprecise semantics.
|
||||
When copying a Provision, the hidden link to existing advice data is //not shared.//
|
||||
|
||||
!!!!de-allocation of advice data
|
||||
It is desirable that the dtors of each piece of advice data be called eventually. But ensuring this reliably is tricky, because advice
|
||||
data may be of various types and is added to the system to remain available, even after the original {{{advice::Provision}}} went out of scope. Moreover, the implementation decision was //not//&nbsp; to employ a vtable for the advice collaborators and data holders, so we're bound to invoke the dtor with the correct specific type.
|
||||
There are some special cases when de-allocation happens while the original provision is still alive (new advice, changed binding, retracting). But in any other case, responsibility for de-allocation has to be taken by the ~AdviceSystem, which unfortunately can't handle the specific type information. Thus the original provision needs to provide a deleter function, and there is no way to avoid storing a function pointer to this deleter within the ~AdviceSystem, together with the advice data holder.
|
||||
It seems reasonable to create this deleter right away and not to share the link to advice data, when copying a provision, to keep responsibilities straight. {{red{Question: does this even work?? }}} to be verified: does the address of the advice data buffer really determine alone what is found as "existing" provision?
|
||||
|
||||
!!!lifecycle considerations
|
||||
Behind the scenes, hidden within the {{{advice.cpp}}} implementation file, the ~AdviceSystem is maintained as singleton. According to a general lifecycle policy within Lumiera, no significant logic is allowed to execute in the shutdown phase of the application, once the {{{main()}}} has exited. Thus, any advice related operations might throw {{{error::Logic}}} after that point. The {{{~AdviceSystem()}}} also is a good place to free any buffers holding incorporated advice data, after having freed the index datastructure referring to these buffer storage, of course.
|
||||
|
|
|
|||
Loading…
Reference in a new issue