Finalizers: conclusion?

Alastair Reid alastair at reid-consulting-uk.ltd.uk
Mon Oct 21 20:32:55 EDT 2002


Hi Antony,

[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.]

> (My code currently uses touchForeignPtr in a Haskell
> finalizer to express a liveness dependency, but this is apparently
> not supported by the latest FFI spec. and won't be.)

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.

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

  -- | 
  -- 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? :-)

> 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:

>> [...]

It turns out that the real killer is shared thunks.

Suppose the main thread is evaluating a thunk and is interrupted by a
finalizer which needs to evaluate the same thunk.  What should you do?
The choices are:

1) Report an error.

   This is easy but semantically wrong.

2) Block the finalizer until the main thread completes evaluation
   just as GHC does.

   Blocking the finalizer would require all Haskell implementations to
   implement preemptive concurrency.  This is thought to be an excessive
   burden.

3) Let the finalizer evaluate it.
   Eventually both finalizer and main thread will produce equivalent
   results and update the thunk with the same result.

   Sounds easy but:
   - it's quite delicate to achieve this goal.
   - it would require us to turn off blackholing - an implementation
     technique which eliminates some serious space leaks.
   
Given the choice of poor semantics, huge implementation effort and
reduced portability, and space leaks, we decided to redefine our
goals and declare C finalizers adequate.

[I tried to describe this problem in finalizers.txt this morning but 
I think the above is clearer.]

>> We want to be able to use mutable Haskell state from a Haskell
>> finalizer, but we clearly can't use IORefs.  Finalizers which
>> modify IORefs will always contain race conditions.

> Although I suspect that the reason why we "clearly can't use IORefs"
> or why such code will "always contain race conditions" is clear and
> obvious to everyone else involved in this discussion, it is not at
> all clear to me as an outsider.  If someone would be kind enough to
> write just a sentence or two clarifying the "obvious" problem, I
> would be very grateful.

Finalizers in Hugs and NHC behave like interrupts: at some random part
of the execution of your program, the finalizer is run.  Just as you
worry about what happens to your money when interrupting C code like:

  int old = account.balance;
  int new = old + 10000;
  account.balance = new;

so you should worry about what happens to the corresponding Haskell code
which uses IORefs.

One way to fix this instance of the problem in Haskell is to provide
an atomic operation to modify the value of an IORef.

[Finalizers behave like threads in GHC.  The same problem results.]


-- 
Alastair Reid                 alastair at reid-consulting-uk.ltd.uk  
Reid Consulting (UK) Limited  http://www.reid-consulting-uk.ltd.uk/alastair/




More information about the FFI mailing list