On Nov 2, 2004, at 12:41 AM, Leopold Toetsch wrote:
> When we have objects with finalizers, we have to run the finalizers in
> order from most derived down the parent chain.
Maybe, but not necessarily. The case of loops means that we cannot
always do this cleanly (no "top" of the chain), and the fact that
finalizers may modify the topology of the object graph also makes this
a bit ill-defined. There are several possible approaches, ranging from
very conservative to very aggresive. Two real-world approaches already
used are: make no guarantees about finalization order--only guarantee
that a finalizer will be called at some point after an object becomes
unreferenced, with no guarantees about ordering, and no guarantees that
it will not be called if the object has already be re-rooted (Java);
or, do graph-ordered finalization, and never call the finalizers of
objects which are in loops (the Boehm collector does something along
these lines, I believe).
> So as you state, we've to
> defer destruction, store these objects with finalizers somewhere, sort
> them, run user code to finalize objects and so on.
Other objects are impacted as well--any object reachable from a
finalizable object must wait for reclamation until finalizers have
fired.
> Doing that all in the destroy vtable is tedious especially, when user
> code is involved too. The finalize vtable is overridable, the destroy
> isn't. A split makes this functionality much cleaner.
I think what's bothering me about this is that I think part of the idea
is to move closing of filehandles and such into destroy()--right? That
sounds good--don't get rid of this resource until it's certain nothing
will try to use it (eg, things which want to log a message during their
finalization would use a filehandle). But I don't think this really
solves it. For one thing, there will be similar-in-functionality code
coming up in HLL's (doing some orderly shutdown of connections, which
involves more than just closing the filehandle), and this will have to
happen in a finalizer (since that's the only HLL option), which will
feel "too early" in some cases. And even in a destroy(), you might want
to do some I/O, and whether this will work will depend on whether the
filehandles have been closed yet, and the same ordering concerns
re-emerge.
So I think the split would help, in a sense, but only be a partial
solution to an unsolvable problem, and therefore maybe is not very
elegant. So I'm a bit on the fence.
(And in my parlance, just to be clear: "finalizers" are called after an
object becomes un-referenced, and could re-root an object;
"destructors" are called when the object is actually having its memory
re-claimed--when it is going away for sure. No user code can be called
from a destructor in the Parrot case, because of the possibility of
re-rooting, and because of other constraints which may exist at
destroy-time.)
JEff