Finalizers etcetera

Alastair Reid alastair at reid-consulting-uk.ltd.uk
Fri Oct 11 06:45:09 EDT 2002


> | To get any benefit from writing finalizers in Haskell, I have to have 
> | MVars which protect against finalizers.

> Nearly right, but not quite.  You might write a Haskell finalizer
> that did lots of useful things (e.g. consulted a large pure data
> structure) before doing its state-mutation by calling C.

I guess I can imagine doing it - but not often.

> | The scheduler would need to prioritize finalizers over normal
> | threads.

> Desirable but not necessary.  The programmer cannot expect
> finalizers to run promptly (there is always lots of literature on
> the GC mailing list about this point).

This works with preemptive concurrency because whilst we don't know
when it will run, we do know it will run.  In fact, we can estimate
when it will run: if there are N threads in the system and timeslices
are t units long, they won't be delayed more than N*t.  If this delay
is too long, the timeslices can be made shorter (I think GHC has a
switch to do this).

With Hugs' cooperative concurrency, context switches can be delayed
indefinitely.  They can be delayed because the program is executing
pure code.  They can also be delayed because, though in the IO monad,
they do not hit a context-switch triggering event.  Especially in
single-threaded programs, there may be very few context-switch
triggering events.  One could easily see the program run to completion
before the finalizers have a chance to run.  There's no equivalent to
making timeslices shorter that will make the situation better.

> | Most of the cooperative concurrency implementation is written in 
> | Haskell.  That would have to be rewritten in C to make the
> | operations atomic wrt garbage collection.

> No, I disagree with this.  When GC runs, the finalizers can be put
> in the ready queue.  After GC completes, the thread that was
> interrupted by GC continues.  So a thread switch takes place ONLY
> when the thread yields, as now.  (Again, promptness is not a
> reqt. If the thread never yields, the finaliser will never run.
> That is 100% ok. You absolutely should not RELY on finalizers.

There has to be _something_ we can rely on about finalizers.  

If my programs have to work even if finalizers never run, I could make
life much simpler for myself by not bothering with finalizers.  We
know that won't work though.  

I find the idea of switching from a design where finalizers are run
promptly to a design where they may be delayed for a long time or
never run at all highly unsatisfactory.  

> So the question before the house is to choose between:

> A) Haskell finalizers: flexible; continue to be what we want when we
> have concurrency; but if your impl does not support MVars you have
> to call C to do state mutation.

> B) C finalizers: less flexible; GHC will have (A) anyway; but
> arguably one less trap for the unwary.


> I still prefer (A), albeit not unto death, because 

I prefer (B) because:

1) it doesn't require us to implement concurrency in order to call C
2) it satisfies the FFI goals of letting us finalize foreign objects
3) finalizers can be run promptly
4) implementation is straightforward


> (i) I believe that supporting MVars in Hugs is not as hard as you
> think.  

I think it is terrifically hard to implement this unless we are
willing to delay execution of the finalizers until we are back at the
IO level (inside a call to unsafePerformIO wouldn't count).  (In case
there's any doubt, I am strongly against delaying their execution
indefinitely, requiring the programmer to regularily pop out to the IO
level, etc.)

What disturbs me is that when GC strikes, the currently executing
thread doesn't have a properly constructed continuation which can be
saved on the ready list.  Instead, its execution state is on the C
stack and there's no convenient way to save that away.

[Just in case it's not clear from last night's description of Hugs'
concurrency: Hugs carries around a continuation only for stuff in the
IO monad; it uses the C stack when executing pure code.  Also, my
trick for stripping state off the stack and into a chain of stack
frames stored on the stack is only for the STG machine, not Hugs.]

> (I'm agnostic about NHC.)

We'd better get an opinion on that soon because everyone but me seems
to be going for a design which needs a way for the main thread to
protect against finalizers (without disabling GC, of course).  [I use
vague language to describe this protection because I guess it's
possible we could go for a simpler kind of lock than MVars which, on
Hugs and NHC, would protect the main thread from preemption by
finalizers but not vice-versa.  More like disabling interrupts than
locking.  I haven't thought through all the details but it might be
simpler.]

> (ii) Less incompatibility... (i.e. programs that use GHC extensions
> that won't run on Hugs)

The way I see it C finalizers avoid portability problems.

If I ask 100 Haskell programmers to write C finalizers, I'll bet 95%
of them will not even think of calling Haskell from inside the
finalizers.

If I ask 100 Haskell programmers to write Haskell finalizers, I'll bet
50% of them will manipulate shared Haskell state.  They'll call
hPutStr (accesses shared state in GHC's implementation at least),
they'll maintain arrays of pointers to C objects in Haskell, etc.

> (iii) Moves in the direction we will ultimately want to go.  It
> seems bizarre to write finalizers in a different language!

But we're already dealing with that different language!  That's what
the FFI is for.  Writing finalizers for WeakPtrs in C would be
bizarre.  Writing finalizers for Addr/Ptr/ForeignPtr in C is the
normal thing you want to do.  It's all that Hugs has ever supported
and I have never found it constraining.

--
Alastair



More information about the FFI mailing list