The Revenge of Finalizers
alastair at reid-consulting-uk.ltd.uk
Thu Oct 17 07:38:25 EDT 2002
>> So, is this a design that we could agree on?
> 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
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
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