[usocket-devel] SBCL/Win32: finalizer problem, etc.

Anton Kovalenko anton at sw4me.com
Tue Mar 22 06:38:40 UTC 2011


"Chun Tian (binghe)" <binghe.lisp at gmail.com> writes:

> I think I cannot understand why the closure must close over those
> components of WAIT-LIST but WAIT-LIST itself, but since this is a fact
> (as you confirmed), I'd like to adopt your patches and quote all your
> explanations and put with the new code together.  I hope, with your
> SBCL and USOCKET work, Hunchentoot (and other Lisp-based servers)
> could have a beautiful future on Windows platform.

Thank you!

The problem with a closure and a finalizer is not too complicated (i'm
now trying to rephrase my explanations to be easy to understand):

A queue of finalizers (sb-impl::**finalizer-store** in SBCL, but other
CL implementanions usually have some equivalent) is just a mundane
special variable (containing a list in SBCL case). Special variable
values (if we forget thread-local bindings and uninterned symbols) are
always reachable in Common Lisp: it's easy, for example, to write a loop
that iterates over packages and symbols and examines each SYMBOL-VALUE.

GC _doesn't collect reachable objects_. If there is a closure with an
object `inside', it's considered reachable too. And if an object isn't
collected, it's not the time (from GC's point of view) to run its
finalizers, so they are not run.


Anyone who wants to build a GC with other behavior will be confronted by
a very complicated picture: instead of two kinds of objects ("reachable"
and "unreachable" (from some roots)) we must consider _which code_ can
reach the object and which code cannot. If we introduce a single
"magical" exception into the simple dichotomy of live and dead object
(like finalizer queue -- if it were a "zombie place" of this kind),
complicated GC implementation will be only a half of our problems;
questions like "what if one finalizer refers to the object of another
one?"  will soon make us confused -- not only about how to implement the
GC, but about _what_ we should implement.

SBCL is not alone in taking the simple way; and, when we decide that
finalizers (that are notified about dead objects) shouldn't see those
objects, a beautiful thing happens: _weak pointers and finalizers_
become logically equivalent and mutually interchangeable.

In particular, SBCL's finalizer queue is just an alist of weak pointers
to objects in CAR and closures in CDR. After each "low-level" GC
invokation, SBCL looks for those weak pointers in **finalizer-store**
that became dead, and invokes the closures. 

[Just as a curiosity, it's possible to go in the opposite direction: to
 implement weak pointers when the finalizers are "given". Never seen it
 in real life, however: weak pointer support is likely a natural
 byproduct of low-level GC work, and finalizer support likely isn't].
 
If not weak pointers, weak hash tables provide the base for finalizer
support in the same way. [Having weak hash table support _inside_ is
almost a must for any Common Lisp implementation -- or else each
interned and uninterned symbol increases memory consumption
irreversibly].

Almost any programmer now has some experience with C++ destructors, so
it's especially important not to misapply that experience to
finalizers. The common trait of finalizers that is described above is
_opposed_ to the very definition and purpose of destructors.

Destructors are like some evil creditor, speaking continuously "I hope
you won't die in debt". Finalizers are like some relative notifying you
of the burial of other relative. Unsure which picture is more sad, but
the latter is more natural and less evil.

-- 
Regards, Anton Kovalenko
+7(916)345-34-02 | Elektrostal' MO, Russia




More information about the usocket-devel mailing list