dejafu/tests/Tests/Cases.hs
2015-02-14 23:45:14 +00:00

146 lines
3.7 KiB
Haskell

-- | Tests sourced from <https://github.com/sctbenchmarks>.
module Tests.Cases where
import Control.Concurrent.CVar
import Control.Exception (ArithException(..), ArrayException)
import Control.Monad (replicateM)
import Control.Monad.Conc.Class
-- | Should deadlock on a minority of schedules.
simple2Deadlock :: MonadConc m => m Int
simple2Deadlock = do
a <- newEmptyCVar
b <- newEmptyCVar
c <- newCVar 0
j1 <- spawn $ lock a >> lock b >> modifyCVar_ c (return . succ) >> unlock b >> unlock a
j2 <- spawn $ lock b >> lock a >> modifyCVar_ c (return . pred) >> unlock a >> unlock b
takeCVar j1
takeCVar j2
takeCVar c
-- | Dining philosophers problem, result is irrelevent, we just want
-- deadlocks.
philosophers :: MonadConc m => Int -> m ()
philosophers n = do
forks <- replicateM n newEmptyCVar
let phils = map (\(i,p) -> p i forks) $ zip [0..] $ replicate n philosopher
cvars <- mapM spawn phils
mapM_ takeCVar cvars
where
philosopher ident forks = do
let leftId = ident
let rightId = (ident + 1) `mod` length forks
lock $ forks !! leftId
lock $ forks !! rightId
-- In the traditional approach, we'd wait for a random time
-- here, but we want the only source of (important)
-- nondeterminism to come from the scheduler, which it does, as
-- pre-emption is effectively a delay.
unlock $ forks !! leftId
unlock $ forks !! rightId
-- | Checks if a value has been increased above a threshold, data
-- racey.
thresholdValue :: MonadConc m => m Bool
thresholdValue = do
l <- newEmptyCVar
x <- newCVar 0
fork $ lock l >> modifyCVar_ x (return . (+1)) >> unlock l
fork $ lock l >> modifyCVar_ x (return . (+2)) >> unlock l
res <- spawn $ lock l >> readCVar x >>= \x' -> unlock l >> return (x' >= 3)
takeCVar res
-- | A lock taken but never released.
forgottenUnlock :: MonadConc m => m ()
forgottenUnlock = do
l <- newEmptyCVar
m <- newEmptyCVar
let lockl = lock l >> unlock l >> lock l >> lock m >> unlock m >> lock m >> unlock m
j1 <- spawn lockl
j2 <- spawn lockl
takeCVar j1
takeCVar j2
-- | Very simple data race between two threads.
simple2Race :: MonadConc m => m Int
simple2Race = do
x <- newEmptyCVar
fork $ putCVar x 0
fork $ putCVar x 1
readCVar x
-- | Race on popping from a stack.
raceyStack :: MonadConc m => m (Maybe Int)
raceyStack = do
s <- newCVar []
fork $ t1 s [1..10]
j <- spawn $ t2 s 10 0
takeCVar j
where
push s a = modifyCVar_ s $ return . (a:)
pop s = do
val <- takeCVar s
case val of
[] -> putCVar s [] >> return Nothing
(x:xs) -> putCVar s xs >> return (Just x)
t1 s (x:xs) = push s x >> t1 s xs
t1 _ [] = return ()
t2 _ 0 total = return $ Just total
t2 s n total = do
val <- pop s
case val of
Just x -> t2 s (n-1) (total+x)
Nothing -> return Nothing
-- | Cause a deadlock sometimes by killing a thread.
threadKill :: MonadConc m => m ()
threadKill = do
x <- newEmptyCVar
tid <- fork $ putCVar x ()
killThread tid
readCVar x
-- | Never deadlock by masking a thread.
threadKillMask :: MonadConc m => m ()
threadKillMask = do
x <- newEmptyCVar
y <- newEmptyCVar
tid <- fork . mask . const $ putCVar x () >> putCVar y ()
readCVar x
killThread tid
readCVar y
-- | Test nested exception handlers.
excNest :: MonadConc m => m Int
excNest =
catch (catch (throw Overflow)
(\e -> return . const 1 $ (e :: ArrayException)))
(\e -> return . const 2 $ (e :: ArithException))
-- | Test unmasking exceptions
threadKillUmask :: MonadConc m => m ()
threadKillUmask = do
x <- newEmptyCVar
y <- newEmptyCVar
tid <- fork . mask $ \umask -> putCVar x () >> umask (putCVar y ())
readCVar x
killThread tid
readCVar y