The Revenge of Finalizers

Alastair Reid alastair at reid-consulting-uk.ltd.uk
Thu Oct 17 07:38:25 EDT 2002


Alastair:
>> So, is this a design that we could agree on?

SimonM:
> I like it.  I'd vote for 'atomicModifyIORef' rather than a new PVar
> type, though.

Ok, onto the second question:

  Can we use atomicModifyIORef to make our code finalizer-safe?


I see potential problems wherever two IORefs need to be modified
atomically.  Obviously, it's easy enough to change code like this:

  foo :: (IORef a, IORef b) -> IO ()
  foo (ref1,ref2) = do
    modifyIORef ref1 f
    modifyIORef ref2 g

to

  foo :: IORef (a,b) -> IO ()
  foo ref = do
    modifyIORef ref (f `cross` g)


More difficult would be something composite objects where multiple
IORefs need to be updated 'at once'.  With MVars, you'd use a single
MVar as the lock for the whole object and then use IORefs for mutable
bits within the tree.  You'd use a similar approach with a construct
like blockFinalizers.  I don't know how to achieve the same goal with
atomicModifyIORef.


--
Alastair

ps While pondering the problems in the semantics of blockFinalizers, I
came up with an alternative semantics which would make sense for GHC.

  @runAtomically m@ runs m atomically with respect to any finalizers
  or any threads also executing @runAtomically at .  That is, any side
  effects from m must not overlap with the side effects of any other
  finalizer or thread (if other threads exist).

  On a system where finalizers behave like interrupts (i.e.,
  finalizers can preempt normal threads and finalizers run to
  completion before any other finalizers or normal threads run),
  runAtomically has the effect of delaying execution of finalizers
  until m completes.  In the presence of cooperative concurrency, 
  we must also block execution of normal threads while m runs.

  On a system where finalizers behave like preemptive threads
  runAtomically must wait until all [other] currently running
  finalizers terminate and any other thread running rnuAtomically to
  terminate, then fresh finalizers must be prevented from starting
  while m is running, when m completes, any pending finalizers can be
  started.  (I think this can be implemented using something like a
  reader-writer lock where all finalizers must take the 'reader' lock
  when they start and runAtomically takes the 'writer' lock when it
  runs.  There's a small wrinkle on the standard reader-writer design
  that a 'reader' become a 'writer' if it calls runAtomically.)

  On multiprocessor systems, it might be possible to optimize things by
  taking the 'reader' lock only when the finalizers/threads start to
  have side effects.

I feel more confident that runAtomically could be used to make
libraries finalizer-safe than I do with atomicModifyIORef.







More information about the FFI mailing list