module Data.Concurrent.Queue.MichaelScott
(
LinkedQueue(), newQ, nullQ, pushL, tryPopR,
)
where
import Data.IORef (readIORef, newIORef)
import System.IO (stderr)
import Data.ByteString.Char8 (hPutStrLn, pack)
import GHC.IORef(IORef(IORef))
import GHC.STRef(STRef(STRef))
import qualified Data.Concurrent.Deque.Class as C
import Data.Atomics (readForCAS, casIORef, Ticket, peekTicket)
#if MIN_VERSION_base(4,7,0)
import GHC.Base hiding ((==#), sameMutVar#)
import GHC.Prim hiding ((==#), sameMutVar#)
import qualified GHC.PrimopWrappers as GPW
(==#) :: Int# -> Int# -> Bool
(==#) x y = case x GPW.==# y of { 0# -> False; _ -> True }
sameMutVar# :: MutVar# s a -> MutVar# s a -> Bool
sameMutVar# x y = case GPW.sameMutVar# x y of { 0# -> False; _ -> True }
#else
import GHC.Base
import GHC.Prim
#endif
data LinkedQueue a = LQ
{ head :: !(IORef (Pair a))
, tail :: !(IORef (Pair a))
}
data Pair a = Null | Cons a !(IORef (Pair a))
pairEq :: Pair a -> Pair a -> Bool
pairEq Null Null = True
pairEq (Cons _ (IORef (STRef mv1)))
(Cons _ (IORef (STRef mv2))) = sameMutVar# mv1 mv2
pairEq _ _ = False
pushL :: forall a . LinkedQueue a -> a -> IO ()
pushL q@(LQ headPtr tailPtr) val = do
r <- newIORef Null
let newp = Cons val r
loop :: IO ()
loop = do
tailTicket <- readForCAS tailPtr
case peekTicket tailTicket of
Null -> error "push: LinkedQueue invariants broken. Internal error."
Cons _ nextPtr -> do
nextTicket <- readForCAS nextPtr
#ifdef RECHECK_ASSUMPTIONS
(tailTicket', tail') <- readForCAS tailPtr
if not (pairEq tail tail') then loop
else case next of
#else
case peekTicket nextTicket of
#endif
Null -> do (b,newtick) <- casIORef nextPtr nextTicket newp
case b of
True -> do
_ <- casIORef tailPtr tailTicket newp
return ()
False -> loop
nxt@(Cons _ _) -> do
_ <- casIORef tailPtr tailTicket nxt
loop
loop
checkInvariant :: String -> LinkedQueue a -> IO ()
checkInvariant s (LQ headPtr tailPtr) =
do head <- readIORef headPtr
tail <- readIORef tailPtr
if (not (pairEq head tail))
then case head of
Null -> error (s ++ " checkInvariant: LinkedQueue invariants broken. Internal error.")
Cons _ next -> do
next' <- readIORef next
case next' of
Null -> error (s ++ " checkInvariant: next' should not be null")
_ -> return ()
else return ()
tryPopR :: forall a . LinkedQueue a -> IO (Maybe a)
tryPopR q@(LQ headPtr tailPtr) = loop 0
where
loop :: Int -> IO (Maybe a)
#ifdef DEBUG
loop 25 = do hPutStrLn stderr (pack "tryPopR: tried ~25 times!!"); loop 26
loop 50 = do hPutStrLn stderr (pack "tryPopR: tried ~50 times!!"); loop 51
loop 100 = do hPutStrLn stderr (pack "tryPopR: tried ~100 times!!"); loop 101
loop 1000 = do hPutStrLn stderr (pack "tryPopR: tried ~1000 times!!"); loop 1001
#endif
loop !tries = do
headTicket <- readForCAS headPtr
tailTicket <- readForCAS tailPtr
case peekTicket headTicket of
Null -> error "tryPopR: LinkedQueue invariants broken. Internal error."
head@(Cons _ next) -> do
nextTicket' <- readForCAS next
#ifdef RECHECK_ASSUMPTIONS
head' <- readIORef headPtr
if not (pairEq head head') then loop (tries+1) else do
#else
let head' = head
do
#endif
if pairEq head (peekTicket tailTicket) then do
case peekTicket nextTicket' of
Null -> return Nothing
next'@(Cons _ _) -> do
casIORef tailPtr tailTicket next'
loop (tries+1)
else do
case peekTicket nextTicket' of
Null -> error "tryPop: Internal error. Next should not be null if head/=tail."
next'@(Cons value _) -> do
(b,_) <- casIORef headPtr headTicket next'
case b of
True -> return (Just value)
False -> loop (tries+1)
newQ :: IO (LinkedQueue a)
newQ = do
r <- newIORef Null
let newp = Cons (error "LinkedQueue: Used uninitialized magic value.") r
hd <- newIORef newp
tl <- newIORef newp
return (LQ hd tl)
nullQ :: LinkedQueue a -> IO Bool
nullQ (LQ headPtr tailPtr) = do
head <- readIORef headPtr
tail <- readIORef tailPtr
return (pairEq head tail)
instance C.DequeClass LinkedQueue where
newQ = newQ
nullQ = nullQ
pushL = pushL
tryPopR = tryPopR
leftThreadSafe _ = True
rightThreadSafe _ = True