[gclist] Finalization and death notices

Boehm, Hans hans_boehm@hp.com
Mon, 8 Oct 2001 15:10:42 -0700

> From: Charles Fiterman [mailto:cef@geodesic.com]
> At 10:44 AM 10/8/01 -0700, you wrote:
> >I think it's fairly typical to find one use of finalization 
> in 50,000 lines
> >of code.  These are not C++ destructors.  They are not 
> intended to be nearly
> >as ubiquitous as the finalization discussion on this list.
> I think a fully robust construct would be a lot more useful than that.
> You're saying something about the size of the original Boehm 
> Collector will
> only have one use for finalizers.
If my recollection is correct, that was roughly the frequency of use in the
Xerox Cedar environment.  And I don't recall any complaints related to this.
The large majority of C++ destructors manage only ordinary heap memory.
Those destructors become unnecessary with a garbage collector.  The large
majority of the remaining objects die at a well-defined program point, e.g.
when leaving a lexical scope.  These can be easily destroyed using C++-like
destructors or explicit calls in e.g. a try-finally block.  I think it's the
left-overs we're talking about here.  There aren't many of them.
> The reason finalizers are so rarely used is that they are 
> neither prompt or
> sure and there are few uses for anything having those 
> defects. In addition
> they are effectively prohibited from using anything context dependent,
> thread local storage, locks, exceptions and forks. On top of 
> that they are
> prohibited from using anything order dependent.
Nontrivial finalizer usually need to, and can, use locks.  (I've seen Java
documentation that discouraged using locks in finalizers.  This is a bug in
the documentation.)  I've never had a need to access thread-local storage
for a specific thread from a finalizer.  Why would I want to?

Exceptions are a more complicated issue.  I don't think it makes sense to
raise them in the context of a thread that happens to be running at the
time, even if I could raise it exactly when the object is dropped.
Finalizers are used to handle resource deallocation for resources that are
dropped at unpredictable times, and I've thus decided to abstract away the
timing.  There is no reason to expect that any particular thread would be
prepared to deal with the exception.  You can always deal with exceptions by
catching them at the outer level in the finalizer itself, and explicitly
passing on the information.  Offhand, it also seems to me that's probably
the best you can do.

Java finalizers can be order dependent, though that's clumsy, and I disagree
with the Java design.  If object A must be finalized before object B, which
it depends on, A should store a pointer to B in a statically accessible data
structure.  When A's finalizer runs, it removes the pointer.  With the
Cedar/Modula-3 design this wasn't an issue.
> ...
> As a rule of thumb if the construct isn't the preferred 
> method of closing
> files something is terribly wrong with it at some level. I 
> expect there
> would be a wrapped use of notices which would be used for 
> such things as a
> preferred method.
I disagree.  If you know exactly when a file should be closed, say so.  If
the timing is hard to determine, and you want to abstract away the precise
execution timing (as we do with user-level threads), you use finalizers.
And they better run asynchronously to allow locking.  (I would describe the
scheme described by Nick Barnes as asynchronous, since there's a queue
involved.  It certainly works fine.)

I believe that with a reasonable Java implementation, you could actually use
finalizers as the standard way to close files, in most cases.  (This assumes
at least hundreds of file descriptors, and an IO library that invokes the
collector when it runs out of descriptors.)  But I think that's not the
right approach.
> Notices have uses beyond collectors, there are lots of 
> servers with similar
> encapsulation restrictions.
Notices ar fine.  But it still seems to me they're fundamentally equivalent
to any other kind of finalizer.  You run into all the same issues, but with
slightly different terminology.  You can process the outstanding ones at
process exit, but that code has to deal with the fact that death notices for
live objects may already have been processed.  Either way the process exit
code operates under different rules thatn the rest of the finalization code.
Similarly, notices are likely to need pointers to other objects that require
death notices, so you have the same issues with long chains or cycles,
though those are generally easy to avoid in both cases.