The Revenge of Finalizers
ger at tzi.de
Thu Oct 17 07:49:03 EDT 2002
Alastair Reid wrote:
> >> 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
I do. To modify ioRef1 and ioRef2 "simultaneously", write
atomicModifyIORef ioRef1 (\ contents1 -> unsafePerformIO ioRef2 (\ contents2 -> blah blah))
The actual modification will take place when the result or contents of ioRef1 or ioRef2 get
It's horrible of course, but we've got unsafePerformIO in the FFI standard so why not?
You cannot get deadlocks (because atomicModifyIORef won't deadlock), but instead you can
get non-termination. GHC at least detects this non-termination and produces an
"Exception: <<loop>>" or something of the sort. To avoid the non-termination, you need to
order the ioRefs in some way or use some similar strategy, just as you'd have to for MVars.
In fact for UniForM this strategy looks more reliable than our existing one of using MVars,
since at least atomicModifyIORef is guaranteed to leave something in the ioRef, while with
MVars you have to watch out for someone killing the thread at an inconvenient time when
the MVar is empty.
More information about the FFI