[TransWarp] On the requirements for "Assembly Events"

Phillip J. Eby pje at telecommunity.com
Mon Apr 28 14:56:29 EDT 2003

PEAK's component architecture is based on a "declarative" model: you state 
what things *should* exist, but you do not "tell" them to exist.  They 
simply come into being when needed.

However, not all software can or should be developed in this fashion.  For 
example, transaction services in PEAK don't declare their participants and 
cause them to spring into being.  Instead, components that participate in 
transactions register themselves with the transaction.

In situations where the need for such a "registration" operation exists, 
there are two possibilities: either the registration should occur "on 
demand" (as with transactions), *or* the registration is "invariant".  That 
is, the registration should be as though it "always" exists.

In that latter case, there are currently two ways to address the issue in 
PEAK.  One way is to construct the component that uses the registrations to 
statically "know" who all the registrants will be.  This doesn't scale 
well, because we would like to be able to use components without knowing in 
detail what subcomponents they may have.

The second way is to hand-construct some mechanism of "jump starting" 
components to register themselves.  An example of this is the rough 
'setup()' method in 'peak.running.commands.EventDriven'.  It needs this 
'setup()' method so as to ensure that any subcomponents are registered with 
the "reactor" that is used to control the application event loop.

But this is messy.  It still requires that the component 1) have some 
notion of *which* of its components need to be "set up", 2) have a custom 
method written in each new class that might use components that might use 
components that need to be "set up", and 3) someone must call the first 
"setup" method.

I would like to provide PEAK components with a way to know when they should 
"set up", that avoids the three problems just mentioned.

It is important to note that using '__init__()' is *not* a solution, 
because of an inherent circularity in PEAK component trees.  A component 
needs to know its parent, and parents know many of their children.  In 
cases where this is so, it is not possible for both the parent and the 
child '__init__()' methods to have complete information about their 
relationship.  If the parent is created before the child, the parent may 
not use the child for setup.  If the child is created before the parent, 
then the reverse is true.  Thus, '__init__()' cannot be used as a trigger 
for setting up except under the very simplest circumstances (which are not 
really the use cases for this mechanism).

The next logical event to capture would be 'setParentComponent()', since 
when an object's parent component is set, we may then be certain that both 
the object and its parent exist.  Unfortunately, this does not guarantee 
that the object's parent's parent exists, so if for example our setup 
operation relies on the existence of a utility or property defined by 
'peak.ini', then we should not begin setup until there is a complete "path 
to the root" available from our hypothetical component.

So let us state our requirement thus: we would like for components to 
perform any necessary setup operations at the moment they have a complete 
path to a component that is *intended* to be their root component.  I say 
"intended" because any component at the moment it is created may 
temporarily be a root component, until it is attached to its parent.  (This 
makes it clear that to solve our problem we must know what the *intended* 
root component is.)

Let's do a thought experiment.  We could simply say that an "intended root 
component" is one whose "setup" method you call.  So calling 
"someRoot.setup()" would designate that object as an intended root, and 
thus propagate to its child components that they need to be set up.

But this is exactly where we started.  We still have to know *which* 
components to set up.  Not only that, but all of the components that need 
to be set up must be *findable* from the root component, which isn't always 
the case.

Clearly, we need to move the responsibility for knowing which components 
need setup, to the components themselves.  If they were to register 
themselves to receive a callback at the appropriate time, we could 
"eliminate the middleman", and thus avoid objects having to know each 
others' business.

We can't use a regular utility for this, however, since at the time an 
object would want to register, the hierarchy isn't complete and there is no 
way to *find* such a utility.  We can't use a singleton, because the 
notification needs to be specific to the component tree.  It appears to 
follow, then, that the only registry possible is one's immediate parent 

Either a component is a root, or it has a parent.  If it is a root, then at 
some point it will notice it is a root and be able to set itself up.  If it 
has a parent, then it must find out from its parent when it is time to be 
set up.  This condition appears to hold recursively.

Alright, so in principle we can do that.  In practice, there are some 

* Reference lifetime: it is possible that a component could be constructed, 
have its parent set, and then be discarded before the setup event takes 
place.  Unless the registration mechanism utilizes weak references, we may 
invoke unnecessary setups.

* Latecomers: what about components added to the tree after "the" setup 
event takes place?  We can't assume that all components that will ever 
exist, exist at the moment of setup.  So, registering for setup should 
result in immediate setup, if setup has already occurred for the component 
that accepts the registration.

* Merging or managing subtrees: should each parent keep a list only of its 
immediate children that need setup, or should they recursively accumulate 
the registrations of their children?  If each component registers itself 
with its parent, and notifies its children when it is called back, this 
simplifies the logic, but will it work with weak references?  As long as a 
child of a particular parent exists, it holds a reference to the parent, 
thus so long as any child weak reference would be valid, so will the 
parent.  Thus, we may be sure that we will never "lose" a child's 
registration by skipping its parent, since there is no way for the parent 
to be unreachable if the child is still reachable.  However, there will be 
a lot of Python-level function calls occurring to induce setup.  It might 
be better to accumulate registrations upward, from a performance point of 
view, *if* only a small subset of objects require the notification, *and* 
the hierarchy is deep.  If the hierarchy is shallow or most objects require 
the notification, there is little to be saved.

The above things seem to imply that each component needs to:

* Maintain a list of weak references to child components that need setting up

* Know whether its setup routine has been executed

* Accept requests to register a child component for setup

* Register itself for setup upon connection to a parent component

In addition, to address the other issues discussed previously, components 
should probably know which of their own attributes need "setting up", 
without need for the weak references.  Last, but not least, we need to 
formalize the means whereby the top-level "setup" event is triggered.

More information about the PEAK mailing list