Alastair Reid wrote:
> 
> Alastair:
> > > I don't know how to achieve the same goal with atomicModifyIORef.
> 
> George:
> > 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 evaluated.
> 
> I must be missing something because this seems to be riddled with race
> conditions.
> 
> In particular, if ioRef1 is updated by a lazy function, then the write
> to ioRef1 happens but the write to ioRef2 does not.
Yes but so what?  The write to ioRef2 will happen when you try to evaluate
something from the unsafePerformIO.  You can do that right away (seq'ing whatever
the first atomicModifyIORef returned) if it makes you feel happier.

EG (to take an actual example of something I use to implement composable events in
Haskell) we can implement a function

simpleToggle2 :: IORef Bool -> IORef Bool -> IO (Just (Bool,Bool))

which attempts to flip the two IORefs from True to False (if they are both True);
otherwise returning their actual values.  Then you could code this something like this
(no I'm not going to check if it passes GHC)

simpleToggle2 ioRef1 ioRef2 =
   do
      res <- atomicModifyIORef ioRef1 (\ contents1 ->
         unsafePerformIO (atomicModifyIORef ioRef2 (\ contents2 ->
            if contents1 && contents2 
               then
                 (False,(False,Nothing))
               else
                 (contents2,(contents1,Just (contents1,contents2)))
            )
         )
      seq res (return res)

Then the first atomicModifyIORef replaces the contents of ioRef1 by a thunk, and
returns another thunk.  The seq then proceeds to evaluate this thunk, causing
the unsafePerformIO to be run, which changes ioRef2.

There IS a potential problem, because if you run simpleToggle2 on the same ioRef

simpleToggle2 ioRef ioRef

or if two threads run simpleToggle2 simultaneously on the same ioRefs but in opposite 
order

simpleToggle2 ioRef1 ioRef2 || simpleToggle2 ioRef2 ioRef1

or in general you have n threads simultaneously running simpleToggle on a cycle of 
ioRefs,
you can get non-termination, because you will find your thunks get linked in a 
circular chain,
with each one depending on the next.  This is exactly the same as what happens if you 
attempt

writeIORef x (unsafePerformIO (readIORef x))

and then attempt to evaluate the contents of x.  However you also have exactly the 
same problem
when you try to implement simpleToggle2 on MVar Bool, except there you get deadlock 
instead.
The solution in both cases is to put some constraint on the ordering, for example 
construct some
well-founded linear order on ioRefs such that simpleToggle2 ioRef1 ioRef2 is never 
called unless
ioRef2 < ioRef1 (I think it's that way round).

If you all you have is a data structure containing two IORefs, and the only accesses 
you want
to these IORefs are (a) individual access, with a guaranteed terminating update 
function;
(b) joint access, then the solution is simply to make sure that (say) you always 
access the first
ioRef first.  Then I think you have guaranteed termination.  This is exactly the same 
with MVars.
So although some Haskell purists (mentioning no names) might object to using 
unsafePerformIO
like this, it does not seem to make matters worse than using MVars, since it's purely 
a question
of taste whether you prefer non-termination or a deadlock.
_______________________________________________
FFI mailing list
[EMAIL PROTECTED]
http://www.haskell.org/mailman/listinfo/ffi

Reply via email to