The Revenge of Finalizers

Alastair Reid alastair at
Fri Oct 18 12:37:55 EDT 2002

> My impression is that the problem is more fundamental: the thunk for
> x is under evaluation when the finalizer begins to run,


> probably we shouldn't get a crash, but a blackhole instead.

Even a blackhole is wrong.  There's no cycle so it ought to evaluate

> Fixing it so that the evaluation of x is actually continued is what
> we want, but I can't see an easy way to do that.

I think GHC does the right thing (semantically) here.  From memory,
GHC works like this:

  on entering thunk: turn it from a thunk into a blocking queue
   (optimization: delay the transformation until context switch time)

  on entering a blocking queue:
   if cyclic structure (i.e., already under evaluation by same thread),
     report blackhole.
   otherwise, put this thread to sleep on the blocking queue.

Based on this, what does GHC do here when a finalizer tries to
evaluate a thunk already being evaluated?  The finalizer is put to
sleep until the main thread finishes evaluation of the thunk.

And what does GHC do if a call to unsafePerformIO hits a thunk already
under evaluation by the same thread?  This is a blackhole (i.e.,
deadlock).  The thread throws the blackhole exception.

I think Hugs should report a blackhole in the unsafePerformIO case
(I believe it does already).

Hugs (and NHC, I believe) have no way to block finalizers once they
start executing.

I think our only choice is to turn off blackholing, allow the shared
term to be evaluated twice over and hope that the two updates to the
thunk (one by finalizer and then one by the main thread) don't do any
harm.  I don't know if this would work.  Even if it does, it's not too
palatable because of the loss of sharing and the space leaks (that
blackholing normally fixes).

> I suppose you could suspend either the finalizer or the main thread
> using the trick of saving its stack on the heap - is this
> implemented in Hugs?

No - it is an STG-specific trick.  Hugs is based on the G-machine.

In Hugs, the state of a pure evaluation is stored on the C stack and
in the heap.  We could try longjmp-ing out of the finalizer and hope
that the heap has a full and consistent record of the state but I'm
not overly confident that this would work.  (Hmmm, longjmp-ing out is
what exception handlers do and we believe that those work....)

> The only other solution I can think of is to delay finalizers until
> we get back up to the IO monad.  

and not inside an unsafePerformIO call.


More information about the FFI mailing list