[gclist] Finalizer flame wars.
Charles Fiterman
cef@geode.geodesic.com
Thu, 9 May 1996 08:01:47 -0500
> > Again I don't approve of finalizers.
>
> You're free to do so.
>
> > First a garbage collector deletes objects when they can't effect the program
>
> Almost. They can still effect the program through "out of memory" exceptions
> or heavy thrashing.
>
> > A finalizer effects the program.
>
> Nope. A finalizer *may* effect the program, but since you don't know when/if
> it will be called, you'll be much better off writing non-effecting finalizers
> to avoid subtle bugs.
If a finalizer does not effect the program it can safely be put in
#if 0 ... #endif. Why have it?
Here we have a great difference in philosophy. I regard rare and intermittent
bugs as worse than solid ones. You cannot reproduce the rare bugs. Enough
thousand year bugs and your program can never run. Suppose a program is busted
if finalizers for A, B and C don't run but still works if any one of them
runs. This is not far fetched, those finalizers could be closing files reducing
the file handle footprint. This is one of the common uses cited for finalizers.
Now we have a deliberately built in intermittent bug. With a conservative
collector the situation is worse objects can be kept alive by random numbers.
Still worse conservative collectors are being hooked to C++. The normal C++
destructor mechanism is now hooked to finalizers. People do this to avoid
intermittent loose pointer bugs and storage leaks.
> I'm fairly convinced that the huge majority of finalizers written is
> non-effecting.
If they are truely non effecting #if 0 ... #endif is the most efficient
and appropriate construct.
> > This is original sin.
>
> I'd call it "bad programming practice".
I prefer colorful language where appropriate.
> > Second finalizers must me prompt and sure.
>
> I'd say that differently: it is desirable for finalizers to be prompt and sure.
>
> > If you have a chain of finalizer objects they only get killed at
> > the rate of one per collection cycle.
>
> Not necessarily, it all depends on the specific semantics (and/or
> implementation) of finalizers.
Suppose A points to B. A must be finalized first. But A can save B's pointer
in something not scheduled for deletion. So if B is deleted the same cycle
you have a bug. The standard fix for this is to delete one object per cycle.
The standard algorithm is for finalizer objects to be treated as roots but
not marked just because they are finalizer objects. This results in deleting
one object from a chain per collection cycle.
The alternatives are so complex no one has ever implemented them. You would have
to take the finalizer objects, sort them in dependency order flagging loops, Knuth
has something on this in the Stanford Graph Base. Run their finalizers intrepetivly
watching for stores of the addresses of finalizer objects. If you had your own
language you could do this. If I was a Grad student I might do it as an interesting
highly original project but I wouldn't take it seriously.
> > A loop of finalizer objects can never die.
> Again not necessarily. Handling of circular dependencies between finalizable
> objects is an "as yet not clearly solved problem. There are known solutions
> already, even though they are not always satisfying.
The known solution is to put out an error message. A better solution is to
simply let the notion of finalizers (keeps invariants) and destructors
(breaks invariants) live on top of the object model. At this level you can
use some convention to avoid loops.
Collectors need to be efficient and transparent. Adding finalizers hurts their
efficiency and transparency. Finalizers need to be prompt and sure. Giving them
over to collectors destroys both. In encapsulation terms they are a match made
in hell.