packages feed

chaselev-deque-0.5.0.3: RegressionTests/Issue5B.hs

{-# LANGUAGE FlexibleInstances, NamedFieldPuns, CPP #-}

module RegressionTests.Issue5B (standalone_pushPop) where

import Control.Concurrent
import Data.IORef
import qualified Data.Concurrent.Deque.Class as PC

-- We DO observe the error with SEPLIB on, and NOT with it off.
-- That means for the time being I cannot reproduce this bug in a standalone file...
#define SEPLIB
#ifdef SEPLIB
import Data.Concurrent.Deque.ChaseLev (ChaseLevDeque, dbgInspectCLD)
#endif

import Data.Atomics

-- import Data.CAS (casIORef)
import qualified Data.Vector.Mutable as MV
import qualified Data.Vector as V
-- import Text.Printf (printf)
import Control.Exception(catch, SomeException, throw, evaluate)
import Control.Monad (when, unless, forM_)
-- import Control.Monad.ST

import Data.Atomics (readArrayElem, readForCAS, casIORef, Ticket, peekTicket)
import System.Mem.StableName (makeStableName, hashStableName)
import GHC.IO (unsafePerformIO)
import Text.Printf (printf)

--------------------------------------------------------------------------------

standalone_pushPop :: IO ()
standalone_pushPop =
  triv =<< (PC.newQ :: IO (DebugDeque ChaseLevDeque a))           
 where   
   -- This is what's failing with the debug wrapper, WHY?
   -- triv :: PC.PopL d => d [Char] -> IO ()
   triv :: DebugDeque ChaseLevDeque [Char] -> IO ()
   triv q = do
     -- r <- newIORef
     -- writeIORef r "hi"
     -- (bl,tick) <- readForCAS r 
     -- casIORef r tick "there"
     -- case bl of
     --   True -> putStrLn "CAS succeeded. Test passed.\n"
     let val = "hi"
     PC.pushL q val
     printf "Done pushing single value... queue %x, val %x\n" (unsafeName q) (unsafeName val)
     x <- PC.tryPopL q
     y <- PC.tryPopL q
     z <- PC.tryPopL q

     s <- dbgInspectCLD (unwrapDebug q)
     printf "Queue after 3 pops:\n%s\n" s     
     case x of
       Just "hi" -> putStrLn "Got expected value.  Test passed.\n"
       Just x'   -> error$ "A single push/pop got the WRONG value back: "++show x'
       Nothing   -> error$"Even a single push/pop in isolation did not work!: "++show (x,y,z)

data DebugDeque d elt =
  DebugDeque {
    tidMarks :: (IORef (Maybe ThreadId), IORef (Maybe ThreadId)),
    unwrapDebug :: (d elt)
  }

instance PC.DequeClass d => PC.DequeClass (DebugDeque d) where 
  pushL (DebugDeque (ref,_) q) elt = do
    PC.pushL q elt

  tryPopR (DebugDeque (_,ref) q) = do
    PC.tryPopR q 

  newQ = do l <- newIORef Nothing
            r <- newIORef Nothing
            fmap (DebugDeque (l,r)) PC.newQ

  -- FIXME: What are the threadsafe rules for nullQ?
  nullQ (DebugDeque _ q) = PC.nullQ q
      
  leftThreadSafe  (DebugDeque _ q) = PC.leftThreadSafe q
  rightThreadSafe (DebugDeque _ q) = PC.rightThreadSafe q


instance PC.PopL d => PC.PopL (DebugDeque d) where 
  tryPopL (DebugDeque (ref,_) q) = do
    PC.tryPopL q 

--------------------------------------------------------------------------------

{-# NOINLINE unsafeName #-}
unsafeName :: a -> Int
unsafeName x = unsafePerformIO $ do 
   sn <- makeStableName x
   return (hashStableName sn)

#ifndef SEPLIB
--------------------------------------------------------------------------------
-- Instances

instance PC.DequeClass ChaseLevDeque where 
  newQ  = newQ
  nullQ = nullQ
  pushL = pushL
  tryPopR = tryPopR
  -- | Popping the left end is the "local" side:
  leftThreadSafe  _ = False
  rightThreadSafe _ = True

instance PC.PopL ChaseLevDeque where 
  tryPopL = tryPopL

--------------------------------------------------------------------------------
-- Type definition

data ChaseLevDeque a = CLD {
    top       :: {-# UNPACK #-} !(IORef Int)
  , bottom    :: {-# UNPACK #-} !(IORef Int)
    -- This is a circular array:
  , activeArr :: {-# UNPACK #-} !(IORef (MV.IOVector a))
  }

--------------------------------------------------------------------------------
-- Debugging mode.
#define DEBUG
-- define FAKECAS

{-# INLINE rd #-}
{-# INLINE wr #-}
{-# INLINE nu #-}
{-# INLINE cpy #-}
{-# INLINE slc #-}
#ifndef DEBUG
dbg = False
nu  = MV.unsafeNew
rd  = MV.unsafeRead
wr  = MV.unsafeWrite
slc = MV.unsafeSlice
cpy = MV.unsafeCopy
#else
dbg = True
nu  = MV.new 
rd  = MV.read
slc = MV.slice
cpy = MV.copy
wr  = MV.write
-- Temp, debugging: Our own bounds checking, better error:
-- wr v i x = 
--   if i >= MV.length v
--   then error (printf "ERROR: Out of bounds of top of vector index %d, vec length %d\n" i (MV.length v))
--   else MV.write v i x 
#endif


#ifdef DEBUG
tryit msg action = Control.Exception.catch action 
	                        (\e -> do putStrLn$ "ERROR inside "++msg++" "++ show e 
                                          throw (e::SomeException))
#else
{-# INLINE tryit #-}
tryit msg action = action
#endif



--------------------------------------------------------------------------------
-- Circular array routines:


-- TODO: make a "grow" that uses memcpy.
growCirc strt end oldarr = do  
  -- let len = MV.length oldarr
  --     strtmod = strt`mod` len 
  --     endmod  = end `mod` len
  -- newarr <- nu (len + len)
  -- if endmod < strtmod then do
  --   let elems1 = len - strtmod
  --       elems2 = endmod
  --   BS.putStrLn$ BS.pack$ printf "Copying segmented ... %d and %d" elems1 elems2

  --   -- Copy the upper then lower segments:
  --   copyOffset oldarr newarr   strtmod  0       elems1
  --   copyOffset oldarr newarr   0        elems1  elems2
  --  else do
  --   BS.putStrLn$ BS.pack$ printf "Copying one seg into vec of size %d... size %d, strt %d, end %d, strtmod %d endmod %d" (MV.length newarr) (end - strt) strt end strtmod endmod
  --   -- Copy a single segment:
  --   copyOffset oldarr newarr strtmod 0 (end - strt)
  -- return newarr
  ----------------------------------------
  -- Easier version first:
  let len   = MV.length oldarr
      elems = end - strt

  putStrLn$ "Grow to size "++show (len+len)++", copying over "++show elems

  newarr <- if dbg then
               nu (len + len)
            else  -- Better errors:
                V.thaw $ V.generate (len+len) (\i -> error (" uninitialized element at position " ++ show i
							    ++" had only initialized "++show elems++" elems: "
							    ++show(strt`mod`(len+len),end`mod`(len+len))))
  -- Strictly matches what's in the paper:
  forM_ [strt..end - 1] $ \ind -> do 
    x <- getCirc oldarr ind 
    evaluate x
    putCirc newarr ind x
  return newarr
{-# INLINE growCirc #-}

getCirc arr ind   = rd arr (ind `mod` MV.length arr)
putCirc arr ind x = wr arr (ind `mod` MV.length arr) x
{-# INLINE getCirc #-}
{-# INLINE putCirc #-}

copyOffset :: MV.IOVector t -> MV.IOVector t -> Int -> Int -> Int -> IO ()
copyOffset from to iFrom iTo len =
  cpy (slc iTo len to)
      (slc iFrom len from)
{-# INLINE copyOffset #-}


--------------------------------------------------------------------------------
-- Queue Operations

-- logInitialSize
newQ :: IO (ChaseLevDeque elt)
newQ = do
  -- We start as size 32 and double from there:
  v  <- MV.new 32 
  r1 <- newIORef 0
  r2 <- newIORef 0
  r3 <- newIORef v
  return$ CLD r1 r2 r3

nullQ :: ChaseLevDeque elt -> IO Bool
nullQ CLD{top,bottom} = do
  b   <- readIORef bottom
  t   <- readIORef top  
--  return (b == t)
  let size = b - t  
  return (size <= 0)

-- | For a work-stealing queue `pushL` is the ``local'' push.  Thus
--   only a single thread should perform this operation.
pushL :: ChaseLevDeque a -> a  -> IO ()
pushL CLD{top,bottom,activeArr} obj = tryit "pushL" $ do
  b   <- readIORef bottom
  t   <- readIORef top
  arr <- readIORef activeArr
  let len = MV.length arr 
      size = b - t

--  when (dbg && size < 0) $ error$ "pushL: INVARIANT BREAKAGE - bottom, top: "++ show (b,t)

  arr' <- if (size >= len - 1) then do 
            arr' <- growCirc t b arr -- Double in size, don't change b/t.
            -- Only a single thread will do this!:
	    writeIORef activeArr arr'
            return arr'
          else return arr

  putCirc arr' b obj
  writeIORef bottom (b+1)
  return ()

-- | This is the steal operation.  Multiple threads may concurrently
-- attempt steals from the same thread.
tryPopR :: ChaseLevDeque elt -> IO (Maybe elt)
tryPopR CLD{top,bottom,activeArr} =  tryit "tryPopR" $ do
--  t   <- readIORef top
  tt  <- readForCAS top
  b   <- readIORef bottom
  arr <- readIORef activeArr
 -- when (dbg && b < t) $ error$ "tryPopR: INVARIANT BREAKAGE - bottom < top: "++ show (b,t)

  let t = peekTicket tt
      size = b - t
  if size <= 0 then 
    return Nothing
   else do 
    obj   <- getCirc  arr t
    (b,_) <- doCAS top tt (t+1)
    if b then 
      return (Just obj)
     else 
      return Nothing -- Someone beat us, abort

tryPopL  :: ChaseLevDeque elt -> IO (Maybe elt)
tryPopL CLD{top,bottom,activeArr} = tryit "tryPopL" $ do
  b   <- readIORef bottom
  arr <- readIORef activeArr
  b   <- return (b - 1) -- shadowing
  writeIORef bottom b
  tt   <- readForCAS top    
--  when (dbg && b < t) $ error$ "tryPopL: INVARIANT BREAKAGE - bottom < top: "++ show (b,t)

  let t = peekTicket tt
      size = b - t 
  if size < 0 then do
    writeIORef bottom t 
    return Nothing
   else do
    obj <- getCirc arr b
    if size > 0 then 
      return (Just obj)
     else do
      (b,_) <- doCAS top tt (t+1)
      writeIORef bottom (t+1)
      if b then return$ Just obj
           else return$ Nothing 

------------------------------------------------------------

{-# INLINE doCAS #-}
#ifdef FAKECAS
doCAS = fakeCAS
#else 
doCAS = casIORef
#endif

{-# INLINE fakeCAS #-}
-- This approach for faking it requires proper equality, it doesn't use pointer
-- equality at all.  That makes it not a true substitute but useful for some
-- debugging.
fakeCAS :: Eq a => IORef a -> Ticket a -> a -> IO (Bool,a)
-- casIORef r !old !new =   
fakeCAS r oldT new = do
  let old = peekTicket oldT
  atomicModifyIORef r $ \val -> 
{-
    trace ("    DBG: INSIDE ATOMIC MODIFY, ptr eqs found/expected: " ++ 
	   show [ptrEq val old, ptrEq val old, ptrEq val old] ++ 
	   " ptr eq self: " ++ 
	   show [ptrEq val val, ptrEq old old] ++
	   " names: " ++ show (unsafeName old, unsafeName old, unsafeName val, unsafeName val)
	  ) $
-}
    if   (val == old)
    then (new, (True, val))
    else (val, (False,val))

#endif