Finalizers: conclusion?

Antony Courtney antony at apocalypse.org
Tue Oct 22 15:27:14 EDT 2002


Hi Alastair,

Thanks for the concise summary of the problem raised by shared thunks, 
and for the "keepAlive" proposal.

 > [btw will you be in New Haven around 16-19 Nov?  I'm going to swing
 > through there on my next trip over and it'd be good to see you and
 > maybe ever humiliate myself again in the Gunks with you.]

[yes, I'll be around then, and look forward to much mutual humiliation 
during your visit! :)]

> I think it was agreed that we need to replace touchForeignPtr with
> something else to let us express liveness dependencies.  I hope you'll
> take part in that discussion since you and John Meacham are the only
> ones who seem to have used it so far.

Gladly.

You indicated that you were somewhat unclear why we need liveness 
dependencies.  I'll attempt to clarify by sketching some of the details 
of the particular C library for which I am writing FFI wrappers.

I have a C library for 2D vector graphics.  Two of the abstract types 
provided by this C library are:
    Pixmap -- A handle to an actual buffer of raster data
    RenderContext -- A handle that encapsulates all state associated 
with rendering, such as the current color, current font, target pixmap, etc.

Note that it is possible to create many RenderingContext's that all 
render on to the same underlying Pixmap.

To see why we need liveness dependencies, consider the following typical 
usage scenario in Haskell:
    do pm <- createPixmap               -- 1
       rc <- createRenderContext pm     -- 2
       drawBox rc                       -- 3
       ...

Note that, in the above, it's possible that the call to 
createRenderContext in line 2 could be the last Haskell reference to pm, 
making it a candidate for collection.  But we don't actually want the 
Pixmap to be collected (and its finalizer invoked) until both the Pixmap 
  *and* all associated rendering contexts which refer to the Pixmap 
become unreachable.

The reason we need liveness dependencies is because, internally, the 
RenderContext maintains a pointer to the target Pixmap.  But because 
this pointer exists only in the C heap, we need some way to inform 
Haskell's garbage collector that whenever a particular RenderContext is 
reachable, then its target pixmap is also reachable.

> As a strawman to get discussion rolling, would something like the
> following do the job?

Yes, almost.  See below for details.
>   -- | 
>   -- keepAlive x y ensures that the finalizer for y is not run
>   -- until after the finalizer for x has run to completion.
>   -- Of course, it might not even run then if y is still live
>   -- at that point.
>   keepAlive :: ForeignPtr a -> ForeignPtr b -> IO ()
> 
> Off the top of my head, I'd say the name sucks, the argument order is
> open to change and the semantics will cause headaches for GHC.  Any
> other objections? :-)

Henrik Nilsson and I had a good discussion about this over lunch, 
particularly the subsequent issue about whether or not:
	keepAlive p1 p2 >> keepAlive p2 p1
should create un-collectible garbage.

Our conclusion was that "finalization order" is a red herring, and that 
this primitive should not try to make any kind of guarantees about 
finalization order.  All we need is some way to convey to Haskell's 
collector that liveness of one particular ForeignPtr implies liveness of 
another.  I like your suggested "keepAlive" name, so I'd propose the 
following:

-- |
-- keepAlive x y informs Haskell's garbage collector that if x
-- is live, then y is also live.
keepAlive :: ForeignPtr a -> ForeignPtr b -> IO ()

Note that I have deliberately omitted any mention of the order in which 
the finalizers are invoked.

Note, too, that in this version of keepAlive, it is perfectly reasonable 
to do something like:
	keepAlive p1 p2 >> keepAlive p2 p1
This will result in a cyclic structure that will be collected just like 
any other cyclic structure, and p1 and p2's finalizers will both be 
invoked, in some indeterminate order.

I can't speak for other users who might need some guarantees about 
finalization order; I simply claim that the above definition is:
	(a) adequate for capturing foreign liveness dependencies,
and
	(b) allows for ForeignPtr's to objects that may have cyclic references in 
their foreign representation.

I think that (b) is more important than guaranteeing any particular 
finalization order, and that committing to a particular finalization 
order is both difficult to implement and not very robust.

>>I would really like to have just a little bit more in-depth
>>understanding of why Haskell finalizers are an impossibility.  In
>>particular, the current finalizers.txt document on cvs.haskell.org
>>states:
>>
> [...]

Thank you very much for the clear, articulate summary.  I think I 
understand the issue(s) a bit better now.

	-antony

-- 
Antony Courtney
Grad. Student, Dept. of Computer Science, Yale University
antony at apocalypse.org          http://www.apocalypse.org/pub/u/antony




More information about the FFI mailing list