[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
component.
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
complications:
* 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