unagi-chan-0.3.0.0: tests/UnagiUnboxed.hs
{-# LANGUAGE RankNTypes , ScopedTypeVariables , BangPatterns #-}
module UnagiUnboxed (unagiUnboxedMain) where
-- Unagi-chan-specific tests
--
-- Forked from tests/Unagi.hs at 337600f
import Control.Concurrent.Chan.Unagi.Unboxed
import qualified Control.Concurrent.Chan.Unagi.Unboxed.Internal as UI
import Control.Monad
import qualified Data.Primitive as P
import Data.IORef
import Data.Int(Int8,Int16,Int32,Int64)
import Data.Word(Word,Word8,Word16,Word32,Word64)
import Data.Maybe
import Data.Typeable
import Control.Concurrent(forkIO,threadDelay)
import Control.Concurrent.MVar
import Control.Exception
import Data.Atomics.Counter.Fat
unagiUnboxedMain :: IO ()
unagiUnboxedMain = do
putStrLn "==================="
putStrLn "Testing Unagi.Unboxed details:"
-- ------
putStr "Smoke test at different starting offsets, spanning overflow... "
mapM_ smoke $ [ (maxBound - UI.sEGMENT_LENGTH - 1) .. maxBound]
++ [minBound .. (minBound + UI.sEGMENT_LENGTH + 1)]
putStrLn "OK"
-- ------
putStr "segSource sanity... "
applyToAllPrim segSourceMagicSanity
putStrLn "OK"
-- ------
putStr "Atomicity of atomic unicorns... "
applyToAllPrim atomicUnicornAtomicicity
putStrLn "OK"
-- ------
putStr "Correct first write... "
mapM_ correctFirstWrite [ (maxBound - 7), maxBound, minBound, 0]
putStrLn "OK"
-- ------
putStr "Correct initial writes... "
mapM_ correctInitialWrites [ (maxBound - UI.sEGMENT_LENGTH), (maxBound - UI.sEGMENT_LENGTH) - 1, maxBound, minBound, 0]
putStrLn "OK"
-- ------
let tries = 10000
putStrLn $ "Checking for deadlocks from killed Unagi reader in a fancy way, x"++show tries
checkDeadlocksReaderUnagi tries
smoke :: Int -> IO ()
smoke n = do
smoke1 n
smoke2 n
-- test each of UnagiPrim
applyToAllPrim smokeManyElement
-- test for atomicUnicorns
applyToAllPrim smokeManyUnicorn
-- test a function against each Prim type elements not equal to atomicUnicorn
applyToAllPrim :: (forall e. (Num e, Typeable e, Show e, UnagiPrim e)=> e -> IO ()) -> IO ()
applyToAllPrim f = do
f ('c' :: Char)
f (3.14159 :: Float)
f (-1.000000000000001 :: Double)
f (maxBound :: Int)
f (minBound :: Int8)
f (maxBound :: Int16)
f (minBound :: Int32)
f (maxBound :: Int64)
f (maxBound :: Word)
f (maxBound :: Word8)
f (minBound :: Word16)
f (minBound :: Word32)
f (minBound :: Word64)
f (P.nullAddr `P.plusAddr` 1024 :: P.Addr)
-- TODO Maybe refactor & get rid of these
instance Show P.Addr where
show _ = "<addr>"
instance Num P.Addr where
instance Num Char where
-- www.../rrr... spanning overflow
smoke1 :: Int -> IO ()
smoke1 n = do
(i,o) <- UI.newChanStarting n
let inp = [0 .. (UI.sEGMENT_LENGTH * 3)]
mapM_ (writeChan i) inp
outp <- getChanContents o
unless (and (zipWith (==) inp outp)) $
error $ "Smoke test failed with starting offset of: "++(show n)
-- w/r/w/r... spanning overflow
smoke2 :: Int -> IO ()
smoke2 n = do
(i,o) <- UI.newChanStarting n
let inp = [0 .. (UI.sEGMENT_LENGTH * 3)]
mapM_ (check i o) inp
where check i o x = do
writeChan i x
x' <- readChan o
unless (x == x') $
error $ "Smoke test failed with starting offset of: "++(show n)++"at write: "++(show x)
-- for smoke checking size, and alignment of different Prim types, and allowing
-- testing of writing atomicUnicorn
smokeManyElement :: (Num e, Typeable e, Show e, UnagiPrim e)=> e -> IO ()
smokeManyElement e = do
(i,o) <- newChan
let n = UI.sEGMENT_LENGTH*2 + 1
replicateM_ n (writeChan i e)
outp <- getChanContents o
unless (all (== e) $ take n outp) $
error $ "smokeManyElement failed with type "++(show $ typeOf e)++": "++(show e)++" /= "++(show outp)
-- smokeManyElement for atomicUnicorn values
smokeManyUnicorn :: forall e. (Num e, Typeable e, Show e, UnagiPrim e)=> e -> IO ()
smokeManyUnicorn _ =
maybe (return ()) smokeManyElement (atomicUnicorn :: Maybe e)
-- check our segSource is doing what we expect with magic values:
segSourceMagicSanity :: forall e. (Num e, Typeable e, Show e, UnagiPrim e)=> e -> IO ()
segSourceMagicSanity _ =
case atomicUnicorn :: Maybe e of
Nothing -> return ()
Just e -> do
(_,eArr) <- UI.segSource :: IO (UI.SignalIntArray, UI.ElementArray e)
forM_ [0.. UI.sEGMENT_LENGTH-1] $ \i-> do
e' <- UI.readElementArray eArr i
unless (e == e') $
error $ "in segSource, with type "++(show $ typeOf e)++", "++(show e)++" /= "++(show e')
-- -------------
-- Make sure we get no tearing of adjacent word-size or smaller (as determined
-- by atomicUnicorn instantiation) values, making sure we cross a cache-line.
atomicUnicornAtomicicity :: forall e. (Num e, Typeable e, Show e, UnagiPrim e)=> e -> IO ()
atomicUnicornAtomicicity _e =
when (isJust (atomicUnicorn :: Maybe e)) $ do
(_,eArr) <- UI.segSource :: IO (UI.SignalIntArray, UI.ElementArray e)
let iters = (64 `quot` P.sizeOf _e) + 1
when (iters >= UI.sEGMENT_LENGTH) $
error "Our sEGMENT_LENGTH is smaller than expected; please fix test"
-- just skip Addr for now TODO:
unless ( isJust (cast _e :: Maybe P.Addr)
|| isJust (cast _e :: Maybe Char)) $
forM_ [0.. iters] $ \i0 -> do
let i1 = i0+1
rd = UI.readElementArray eArr
first0 <- rd i0
first1 <- rd i1
v0 <- newEmptyMVar
v1 <- newEmptyMVar
let incr f v i = go
where go _ 0 = putMVar v ()
go expected n = do
val <- rd i
unless (val == expected) $
error $ "atomicUnicornAtomicicity with type "++(show $ typeOf val)++" "++(show val)++" /= "++(show expected)
let !next = f val
UI.writeElementArray eArr i next
go next (n-1)
let counts = 1000000 :: Int
_ <- forkIO $ incr (+1) v0 i0 first0 counts
_ <- forkIO $ incr (subtract 1) v1 i1 first1 counts
-- BlockedIndefinitelyOnMVar means a problem TODO make better
takeMVar v0 >> takeMVar v1
correctFirstWrite :: Int -> IO ()
correctFirstWrite n = do
(i,UI.OutChan (UI.ChanEnd _ arrRef)) <- UI.newChanStarting n
writeChan i (7::Int)
(UI.StreamHead _ (UI.Stream sigArr eArr _ _)) <- readIORef arrRef
cell <- UI.readElementArray eArr 0 :: IO Int
case cell of
7 -> return ()
_ -> error "Expected a write at index 0 of 7"
sigCell <- P.readByteArray sigArr 0 :: IO Int
unless (sigCell == 1) $ -- i.e. cellWritten
error "Expected cellWritten at sigArr!0"
-- check writes by doing a segment+1-worth of reads by hand
-- also check that segments pre-allocated as expected
correctInitialWrites :: Int -> IO ()
correctInitialWrites startN = do
(i,(UI.OutChan (UI.ChanEnd _ arrRef))) <- UI.newChanStarting startN
let writes = [0..UI.sEGMENT_LENGTH]
mapM_ (writeChan i) writes
(UI.StreamHead _ (UI.Stream sigArr eArr _ next)) <- readIORef arrRef
-- check all of first segment:
forM_ (init writes) $ \ix-> do
cell <- UI.readElementArray eArr ix :: IO Int
sigCell <- P.readByteArray sigArr ix :: IO Int
unless (cell == ix && sigCell == 1) $
error $ "Expected a write at index "++(show ix)++" of same value but got "++(show cell)++" with signal "++(show sigCell)
-- check last write:
lastSeg <- readIORef next
case lastSeg of
(UI.Next (UI.Stream sigArr2 eArr2 _ next2)) -> do
cell <- UI.readElementArray eArr2 0 :: IO Int
sigCell <- P.readByteArray sigArr2 0 :: IO Int
unless (cell == last writes && sigCell == 1) $
error $ "Expected last write at index "++(show $ last writes)++" of same value but got "++(show cell)
-- check pre-allocation:
n2 <- readIORef next2
case n2 of
(UI.Next (UI.Stream _ _ _ next3)) -> do
n3 <- readIORef next3
case n3 of
UI.NoSegment -> return ()
_ -> error "Too many segments pre-allocated!"
_ -> error "Next segment was not pre-allocated!"
_ -> error "No last segment present!"
{- NOTE: we would like this to pass (see trac #9030) but are happy to note that
- it fails which somewhat validates the test below
testBlockedRecovery = do
(i,o) <- newChan
v <- newEmptyMVar
rid <- forkIO (putMVar v () >> readChan o)
takeMVar v
threadDelay 1000
throwTo rid ThreadKilled
-- we race the exception-handler in `readChan` here...
writeChan i ()
-- In a buggy implementation, this would consistently win failing by losing
-- the message and raising BlockedIndefinitely here:
readChan o
-}
-- test for deadlocks caused by async exceptions in reader.
checkDeadlocksReaderUnagi :: Int -> IO ()
checkDeadlocksReaderUnagi times = do
let run 0 normalRetries numRace = putStrLn $ "Lates: "++(show normalRetries)++", Races: "++(show numRace)
run n normalRetries numRace
| (normalRetries + numRace) > (times `div` 3) = error "This test is taking too long. Please retry, and if still failing send the log to me"
| otherwise = do
-- we'll kill the reader with our special exception half the time,
-- expecting that we never get our race condition on those runs:
let usingReadChanOnException = even n
(i,o) <- UI.newChanStarting 0
-- preload a chan with 0s
let numPreloaded = 10000
replicateM_ numPreloaded $ writeChan i (0::Int)
rStart <- newEmptyMVar
saved <- newEmptyMVar -- for holding potential saved (IO a) from blocked readChanOnException
rid <- forkIO $ (\rd-> putMVar rStart () >> (forever $ void $ rd o)) $
if usingReadChanOnException
then flip readChanOnException ( putMVar saved )
else readChan
takeMVar rStart >> threadDelay 1
throwTo rid ThreadKilled
-- did killing reader damage queue for reads or writes?
writeChan i 1 `onException` ( putStrLn "Exception from first writeChan!")
writeChan i 2 `onException` ( putStrLn "Exception from second writeChan!")
finalRead <- readChan o `onException` ( putStrLn "Exception from final readChan!")
oCnt <- readCounter $ (\(UI.OutChan(UI.ChanEnd cntr _))-> cntr) o
iCnt <- readCounter $ (\(UI.InChan (UI.ChanEnd cntr _))-> cntr) i
unless (iCnt == numPreloaded + 1) $
error "The InChan counter doesn't match what we'd expect from numPreloaded!"
case finalRead of
0 -> if oCnt <= 0 -- (technically, -1 means hasn't started yet)
-- reader didn't have a chance to get started
then putStr "0" >> run n (normalRetries+1) numRace
-- normal run; we tested that killing a reader didn't
-- break chan for other readers/writers:
else putStr "." >> run (n-1) normalRetries numRace
--
-- Rare. Reader was killed after reading all pre-loaded messages
-- but before starting what would be the blocking read:
1 | oCnt == numPreloaded -> putStr "X" >> run n normalRetries (numRace + 1)
| otherwise -> error $ "Having read final 1, "++
"Expecting a counter value of "++(show numPreloaded)++
" but got: "++(show oCnt)
2 -> do when usingReadChanOnException $ do
shouldBe1 <- join $ takeMVar saved
unless (shouldBe1 == 1) $
error "The handler for our readChanOnException should only have returned 1"
unless (oCnt == numPreloaded + 1) $
error $ "Having read final 2, "++
"Expecting a counter value of "++(show $ numPreloaded+1)++
" but got: "++(show oCnt)
putStr "+" >> run n (normalRetries + 1) numRace
_ -> error "Fix your #$%@ test!"
run times 0 0
putStrLn ""