content: -- |
  -- Module      : Control.Concurrent.Classy.IORef
  -- Copyright   : (c) 2018 Michael Walker
  -- License     : MIT
  -- Maintainer  : Michael Walker <mike@barrucadu.co.uk>
  -- Stability   : stable
  -- Portability : portable
  --
  -- Mutable references in a concurrency monad.
  --
  -- __Deviations:__ There is no @Eq@ instance for @MonadConc@ the
  -- @IORef@ type. Furthermore, the @mkWeakIORef@ function is not
  -- provided.
  module Control.Concurrent.Classy.IORef
    ( -- * IORefs
      newIORef
    , readIORef
    , writeIORef
    , modifyIORef
    , modifyIORef'
    , atomicModifyIORef
    , atomicModifyIORef'
    , atomicWriteIORef
  
    -- * Memory Model
  
    -- | In a concurrent program, @IORef@ operations may appear
    -- out-of-order to another thread, depending on the memory model of
    -- the underlying processor architecture. For example, on x86 (which
    -- uses total store order), loads can move ahead of stores. Consider
    -- this example:
    --
    -- > iorefs :: MonadConc m => m (Bool, Bool)
    -- > iorefs = do
    -- >   r1 <- newIORef False
    -- >   r2 <- newIORef False
    -- >
    -- >   x <- spawn $ writeIORef r1 True >> readIORef r2
    -- >   y <- spawn $ writeIORef r2 True >> readIORef r1
    -- >
    -- >   (,) <$> readMVar x <*> readMVar y
    --
    -- Under a sequentially consistent memory model the possible results
    -- are @(True, True)@, @(True, False)@, and @(False, True)@. Under
    -- total or partial store order, @(False, False)@ is also a possible
    -- result, even though there is no interleaving of the threads which
    -- can lead to this.
    --
    -- We can see this by testing with different memory models:
    --
    -- > > autocheckWay defaultWay SequentialConsistency relaxed
    -- > [pass] Never Deadlocks
    -- > [pass] No Exceptions
    -- > [fail] Consistent Result
    -- >        (False,True) S0---------S1----S0--S2----S0--
    -- >
    -- >        (True,True) S0---------S1-P2----S1---S0---
    -- >
    -- >        (True,False) S0---------S2----S1----S0---
    -- > False
    --
    -- > > autocheckWay defaultWay TotalStoreOrder  relaxed
    -- > [pass] Never Deadlocks
    -- > [pass] No Exceptions
    -- > [fail] Consistent Result
    -- >         (False,True) S0---------S1----S0--S2----S0--
    -- >
    -- >         (False,False) S0---------S1--P2----S1--S0---
    -- >
    -- >         (True,False) S0---------S2----S1----S0---
    -- >
    -- >         (True,True) S0---------S1-C-S2----S1---S0---
    -- > False
    --
    -- Traces for non-sequentially-consistent memory models show where
    -- writes to @IORef@s are /committed/, which makes a write visible to
    -- all threads rather than just the one which performed the
    -- write. Only 'writeIORef' is broken up into separate write and
    -- commit steps, 'atomicModifyIORef' is still atomic and imposes a
    -- memory barrier.
    ) where
  
  import           Control.Monad.Conc.Class
  
  -- | Mutate the contents of a @IORef@.
  --
  -- Be warned that 'modifyIORef' does not apply the function strictly.
  -- This means if the program calls 'modifyIORef' many times, but
  -- seldomly uses the value, thunks will pile up in memory resulting in
  -- a space leak. This is a common mistake made when using a @IORef@ as
  -- a counter. For example, the following will likely produce a stack
  -- overflow:
  --
  -- >ref <- newIORef 0
  -- >replicateM_ 1000000 $ modifyIORef ref (+1)
  -- >readIORef ref >>= print
  --
  -- To avoid this problem, use 'modifyIORef'' instead.
  --
  -- @since 1.6.0.0
  modifyIORef :: MonadConc m => IORef m a -> (a -> a) -> m ()
  modifyIORef ref f = readIORef ref >>= writeIORef ref . f
  
  -- | Strict version of 'modifyIORef'
  --
  -- @since 1.6.0.0
  modifyIORef' :: MonadConc m => IORef m a -> (a -> a) -> m ()
  modifyIORef' ref f = do
    x <- readIORef ref
    writeIORef ref $! f x
  
  -- | Strict version of 'atomicModifyIORef'. This forces both the value
  -- stored in the @IORef@ as well as the value returned.
  --
  -- @since 1.6.0.0
  atomicModifyIORef' :: MonadConc m => IORef m a -> (a -> (a,b)) -> m b
  atomicModifyIORef' ref f = do
    b <- atomicModifyIORef ref $ \a -> case f a of
      v@(a',_) -> a' `seq` v
    pure $! b
  