IORefCAS-0.0.1: Data/CAS.hs
{-# LANGUAGE MagicHash, UnboxedTuples, BangPatterns, MagicHash,
TypeSynonymInstances, FlexibleInstances, MultiParamTypeClasses #-}
-- | Atomic compare and swap for IORefs and CASRefs.
module Data.CAS
( casSTRef, casIORef,
atomicModifyIORefCAS, atomicModifyIORefCAS_,
-- * Generic interface: for interoperation with `Fake` and `Foreign` alternative libraries.
CASRef)
where
import Data.CAS.Internal.Class
import GHC.IO
import GHC.IORef
import GHC.Prim
import GHC.ST
import GHC.STRef
--------------------------------------------------------------------------------
newtype CASRef a = CR { unCR :: IORef a }
instance CASable CASRef a where
newCASable x = newIORef x >>= (return . CR)
readCASable = readIORef . unCR
writeCASable = writeIORef . unCR
cas = casIORef . unCR
--------------------------------------------------------------------------------
-- | Performs a machine-level compare and swap operation on an
-- 'STRef'. Returns a tuple containing a 'Bool' which is 'True' when a
-- swap is performed, along with the 'current' value from the 'STRef'.
casSTRef :: STRef s a -- ^ The 'STRef' containing a value 'current'
-> a -- ^ The 'old' value to compare
-> a -- ^ The 'new' value to replace 'current' if @old == current@
-> ST s (Bool, a)
casSTRef (STRef var#) old new = ST $ \s1# ->
-- The primop treats the boolean as a sort of error code.
-- Zero means the CAS worked, one that it didn't.
-- We flip that here:
case casMutVar# var# old new s1# of
(# s2#, x#, res #) -> (# s2#, (x# ==# 0#, res) #)
-- | Performs a machine-level compare and swap operation on an
-- 'IORef'. Returns a tuple containing a 'Bool' which is 'True' when a
-- swap is performed, along with the 'current' value from the 'IORef'.
casIORef :: IORef a -- ^ The 'IORef' containing a value 'current'
-> a -- ^ The 'old' value to compare
-> a -- ^ The 'new' value to replace 'current' if @old == current@
-> IO (Bool, a)
casIORef (IORef var) old new = stToIO (casSTRef var old new)
-- | A drop-in replacement for `atomicModifyIORefCAS` that
-- optimistically attempts to compute the new value and CAS it into
-- place without introducing new thunks or locking anything. Note
-- that this is STRICTer than its standard counterpart and will only
-- place evaluated (WHNF) values in the IORef.
atomicModifyIORefCAS :: IORef a -> (a -> (a,b)) -> IO b
atomicModifyIORefCAS ref fn = do
-- TODO: Should handle contention in a better way.
init <- readIORef ref
loop init effort
where
effort = 30 :: Int -- TODO: Tune this.
loop old 0 = atomicModifyIORef ref fn
loop old tries = do
(new,result) <- evaluate (fn old)
(b,val) <- casIORef ref old new
if b
then return result
else loop val (tries-1)
-- | A simpler version that modifies the state but does not return anything.
atomicModifyIORefCAS_ :: IORef t -> (t -> t) -> IO ()
-- atomicModifyIORefCAS_ ref fn = atomicModifyIORefCAS ref (\ x -> (fn x, ()))
-- Can't inline a function with a loop so we duplicate this:
-- <duplicated code>
atomicModifyIORefCAS_ ref fn = do
init <- readIORef ref
loop init effort
where
effort = 30 :: Int -- TODO: Tune this.
loop old 0 = atomicModifyIORef_ ref fn
loop old tries = do
new <- evaluate (fn old)
(b,val) <- casIORef ref old new
if b
then return ()
else loop val (tries-1)
atomicModifyIORef_ ref fn = atomicModifyIORef ref (\ x -> (fn x, ()))
-- </duplicated code>