[Persistence-sig] "Straw Baby" Persistence API
Phillip J. Eby
pje@telecommunity.com
Tue, 23 Jul 2002 12:00:11 -0400
At 11:37 AM 7/23/02 -0400, Kevin Jacobs wrote:
>On Tue, 23 Jul 2002, Phillip J. Eby wrote:
>
> > So, you're saying you want to alter the types, then? The interesting part
> > of that is how to alter them in such a way that your observing code
> doesn't
> > get re-entered when you're modifying both subclasses and base classes of
> > the objects. You'd need some kind of thread-specific collaboration stack,
> > I think.
>
>I suppose, though saying 'alter the types' implies slightly different things
I mean, if you're proxying methods, presumably you're doing so by altering
the methods provided by the type, unless you mean to change the type's type
so that the methods are altered on the fly. Either way, a change to the
type instance. :)
>to me. I don't see great difficulty in isolating subclass and superclass
>modifications, although performance is clearly an important issue. As for
>the thread-specific business, you've totally lost me. Can you provide a
>use-case so that I can better understand where you are coming from?
Consider a co-operative method that performs a super() call. If one
surrounds both the super and subclass with observer calls, they will take
place more than once. Perhaps that's what you mean by performance; I
suppose if you are strictly observing things, it may not be a big deal to
have the methods called more than once.
My comment about thread-specificness was about a way to ensure that the
wrapper method only gets called once. It's not relevant if you don't plan
to ensure that wrappers on co-operative methods are called only once.
I should note, however, that there is one possibly rather important use
case for not calling a wrapper more than once: object changes. Let's say
that class B is a subclass of class A. B had an invariant that attribute
"q" is always 3 times attribute "r", and has a setR() method that sets
"r". It uses a super() call to class A to do the actual setting of R, and
then sets the "q" attribute. Now, if there is a post-return observer
associated with the setR() method in both A and B, it will be called at a
point where it will announce a state that is valid for objects of type A,
but violates an invariant for the specific instance being
announced. (Also, even if you didn't care about publishing an invalid
state, it should be noted that use cases like Tim Peters' example of a
distributed cache would really multiply the performance issue, especially
if you're talking about a deep hierarchy of super() calls.)
Anyway, if we're strictly talking about observers, the simplest way to
address this might be to carry a per-instance "nesting count" that you
increment on entry to every proxy and decrement on exit. When the count
reaches zero on exit, fire any pending observation events. Downside to
this approach: if multiple threads enter overlapping calls on the object, a
sort of "livelock" can occur where the object never issues any events. To
address that, you would have to have at least per-thread counters for each
instance, which adds some more performance overhead for access. This is
where my comment about thread-specific collaboration stacks came from.