The Revenge of Finalizers

George Russell ger at
Wed Oct 16 09:25:15 EDT 2002

(message referred to follows)

Alastair suggested implementing blockFinalizers rather than PVars.  However I
dislike this for two reason:
(1) I'm rather attached to PVars.  Not just because I suggested them (actually
I think I stole them from Einar Karlsen) but because it looks to me as if they
could be implemented very efficiently and would be quite useful.  If we take the
more general interface:

newPVar :: a -> IO (PVar a)
updatePVar :: PVar a -> (a -> (a,b)) -> IO b

then PVar's are absolutely guaranteed not to block, and updatePVar can be implemented
as an atomic operation in any of GHC, NHC and Hugs, and I suspect it should even be 
pretty easy to implement it directly in C (since all you are doing is moving pointers to thunks around).  
In particular I would like to petition Simon Marlow to include updatePVar in this way in GHC, 
because (unlike the corresponding solution with MVars) it could very cheaply be guaranteed to 
work atomically (with MVars you need complicated patterns of expensive block-exception 
primitives to stop asynchronous exceptions mucking things up).  Furthermore although you cannot provide
updatePVarIO :: PVar a -> (a -> IO (a,b)) -> IO b
you can implement something fairly similar to it if you are naughty by giving updatePVar a 
function which returns an unsafePerformIO'd action.  The action will then of course be executed
at some outspecified date when the user tries to read the contents of the PVar.  For UniForM at
least there is an application for this which would plug an existing embarassing hole in the
events code, namely that you mustn't throw asynchronous exceptions at threads which use events.

(2) blockFinalizers looks fine for Hugs and NHC which only have a single-thread model, but it
looks tricky in general where we do not have a conception of "during".  Effectively that means
an implementation on a parallel architecture which accesses state has to come up with some
arbitrary order of state accesses, just so that it can rely on it to
specify blockFinalizers.  Of course PVars assume an ordering of state accesses, but only between
accesses to the single PVar.  (There is a similar problem lurking with "unsafe" external calls,
which is probably why they are called "unsafe".)

(3) The implementation of PVars Alastair gives using blockFinalizers also will not work in general
unless you also specify that Haskell finalizers are properly nested.  This is OK for NHC, maybe not
for Hugs, certainly not for GHC.  This is not of course a problem for PVars since the reason is that 
Hugs and GHC have concurrency, and on a concurrent machine you would naturally implement PVars using 
MVars (if not as primitives in their own right).  

Alastair Reid wrote:
> > However even if Haskell finalizers + MVars are impossible in NHC, I
> > don't think Haskell finalizers + mutable state have to be.  For
> > example another mutable variable we could have would be a PVar which
> > is always full and has functions [snip]
> > updatePVar (PVar ioRef) updateFn =
> >    do
> >       [stop any new finalizers running]
> >       a <- readIORef ioRef
> >       writeIORef (updateFn a)
> >       [reenable finalizers]
> >       return a
> Just for the record, I think that if we were to pursue this approach,
> then the right primitive to add is:
>   -- |
>   -- Execute argument atomically with respect to finalizers.
>   -- Nested calls to blockFinalizers are allowed.
>   --
>   -- That is, while executing the argument, no finalizers will start
>   -- to execute.
>   -- (Finalizers that are already executing may continue to execute.)
>   blockFinalizers :: IO a -> IO a

More information about the FFI mailing list