2016-03-31 20:16:09 +03:00
|
|
|
module Cases.MultiThreaded where
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2017-03-04 05:58:27 +03:00
|
|
|
import Control.Exception (ArithException(..))
|
2017-09-06 18:03:28 +03:00
|
|
|
import Control.Monad (void, unless)
|
2017-05-27 18:43:14 +03:00
|
|
|
import Test.DejaFu (Failure(..), gives, gives')
|
|
|
|
import Test.Framework (Test)
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2017-05-04 01:09:56 +03:00
|
|
|
import Control.Concurrent.Classy hiding (newQSemN, signalQSemN, waitQSemN)
|
2017-05-27 18:43:14 +03:00
|
|
|
import Test.DejaFu.Conc (ConcT, subconcurrency)
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2017-03-04 05:58:27 +03:00
|
|
|
import Utils
|
2017-05-04 01:09:56 +03:00
|
|
|
import QSemN
|
2017-03-04 05:58:27 +03:00
|
|
|
|
2015-12-01 07:11:44 +03:00
|
|
|
tests :: [Test]
|
|
|
|
tests =
|
2017-05-27 18:43:14 +03:00
|
|
|
[ tg "Threading"
|
2017-02-20 07:41:36 +03:00
|
|
|
[ T "child thread ID" threadId1 $ gives' [True]
|
|
|
|
, T "parent thread ID" threadId2 $ gives' [True]
|
|
|
|
, T "no wait" threadNoWait $ gives' [Nothing, Just ()]
|
|
|
|
]
|
2017-05-27 18:43:14 +03:00
|
|
|
, tg "MVar"
|
2017-02-20 07:41:36 +03:00
|
|
|
[ T "deadlock" cvarLock $ gives [Left Deadlock, Right 0]
|
|
|
|
, T "race" cvarRace $ gives' [0,1]
|
|
|
|
]
|
2017-05-27 18:43:14 +03:00
|
|
|
, tg "CRef"
|
2017-09-06 18:03:28 +03:00
|
|
|
[ T "race" crefRace $ gives' [0,1]
|
|
|
|
, T "cas modify" crefCASModify $ gives' [0,1]
|
|
|
|
, T "cas race" crefCASRace $ gives' [(True, 2), (False, 2)]
|
|
|
|
, T "cas race (redo)" crefCASRaceRedo $ gives' [(True, 1), (True, 2)]
|
|
|
|
, T "cas tickets" crefCASTickets $ gives' [(True, False, 1), (False, True, 2)]
|
2017-02-20 07:41:36 +03:00
|
|
|
]
|
2017-05-27 18:43:14 +03:00
|
|
|
, tg "STM"
|
2017-02-20 07:41:36 +03:00
|
|
|
[ T "atomicity" stmAtomic $ gives' [0,2]
|
|
|
|
, T "left retry" stmLeftRetry $ gives' [()]
|
|
|
|
, T "right retry" stmRightRetry $ gives' [()]
|
|
|
|
, T "issue 55" stmIssue55 $ gives' [True]
|
2017-09-01 15:53:16 +03:00
|
|
|
, T "issue 111" stmIssue111 $ gives' [1]
|
2017-02-20 07:41:36 +03:00
|
|
|
]
|
2017-05-27 18:43:14 +03:00
|
|
|
, tg "Killing Threads"
|
2017-02-20 07:41:36 +03:00
|
|
|
[ T "no masking" threadKill $ gives [Left Deadlock, Right ()]
|
|
|
|
, T "masked" threadKillMask $ gives' [()]
|
2017-03-04 08:36:32 +03:00
|
|
|
, T "masked (uninterruptible)" threadKillUninterruptibleMask $ gives [Left Deadlock]
|
2017-02-20 07:41:36 +03:00
|
|
|
, T "unmasked" threadKillUmask $ gives [Left Deadlock, Right ()]
|
2017-03-04 05:58:27 +03:00
|
|
|
, T "throw to main (uncaught)" threadKillToMain1 $ gives [Left UncaughtException]
|
|
|
|
, T "throw to main (caught)" threadKillToMain2 $ gives' [()]
|
2017-02-20 07:41:36 +03:00
|
|
|
]
|
2017-05-27 18:43:14 +03:00
|
|
|
, tg "Daemons"
|
2017-02-20 07:41:36 +03:00
|
|
|
[ T "schedule daemon" schedDaemon $ gives' [0,1]
|
|
|
|
]
|
2017-05-27 18:43:14 +03:00
|
|
|
, tg "Subconcurrency"
|
2017-02-20 07:41:36 +03:00
|
|
|
[ T "deadlock1" scDeadlock1 $ gives' [Left Deadlock, Right ()]
|
|
|
|
, T "deadlock2" scDeadlock2 $ gives' [(Left Deadlock, ()), (Right (), ())]
|
|
|
|
, T "success" scSuccess $ gives' [Right ()]
|
|
|
|
, T "illegal" scIllegal $ gives [Left IllegalSubconcurrency]
|
2017-02-25 09:01:38 +03:00
|
|
|
, T "issue 71" scIssue71 $ gives' [()]
|
2017-05-04 01:09:56 +03:00
|
|
|
, T "issue 81" scIssue81 $ gives' [(Right (),0)]
|
2017-02-20 07:41:36 +03:00
|
|
|
]
|
2015-12-01 07:11:44 +03:00
|
|
|
]
|
2015-11-16 05:48:54 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Threading
|
|
|
|
|
|
|
|
-- | Fork reports the good @ThreadId@.
|
|
|
|
threadId1 :: MonadConc m => m Bool
|
|
|
|
threadId1 = do
|
2016-03-23 06:36:07 +03:00
|
|
|
var <- newEmptyMVar
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
tid <- fork $ myThreadId >>= putMVar var
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
(tid ==) <$> readMVar var
|
2015-11-16 05:48:54 +03:00
|
|
|
|
|
|
|
-- | A child and parent thread have different @ThreadId@s.
|
|
|
|
threadId2 :: MonadConc m => m Bool
|
|
|
|
threadId2 = do
|
|
|
|
tid <- spawn myThreadId
|
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
(/=) <$> myThreadId <*> readMVar tid
|
2015-11-16 05:48:54 +03:00
|
|
|
|
|
|
|
-- | A parent thread doesn't wait for child threads before
|
|
|
|
-- terminating.
|
|
|
|
threadNoWait :: MonadConc m => m (Maybe ())
|
|
|
|
threadNoWait = do
|
|
|
|
x <- newCRef Nothing
|
|
|
|
|
|
|
|
void . fork . writeCRef x $ Just ()
|
|
|
|
|
|
|
|
readCRef x
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
2016-03-23 06:36:07 +03:00
|
|
|
-- @MVar@s
|
2015-11-16 05:48:54 +03:00
|
|
|
|
|
|
|
-- | Deadlocks sometimes due to order of acquision of locks.
|
|
|
|
cvarLock :: MonadConc m => m Int
|
|
|
|
cvarLock = do
|
2016-03-23 06:36:07 +03:00
|
|
|
a <- newEmptyMVar
|
|
|
|
b <- newEmptyMVar
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
c <- newMVar 0
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2016-04-03 08:29:54 +03:00
|
|
|
let lock m = putMVar m ()
|
|
|
|
let unlock = takeMVar
|
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
j1 <- spawn $ lock a >> lock b >> modifyMVar_ c (return . succ) >> unlock b >> unlock a
|
|
|
|
j2 <- spawn $ lock b >> lock a >> modifyMVar_ c (return . pred) >> unlock a >> unlock b
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
takeMVar j1
|
|
|
|
takeMVar j2
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
takeMVar c
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
-- | When racing two @putMVar@s, one of them will win.
|
2015-11-16 05:48:54 +03:00
|
|
|
cvarRace :: MonadConc m => m Int
|
|
|
|
cvarRace = do
|
2016-03-23 06:36:07 +03:00
|
|
|
x <- newEmptyMVar
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
void . fork $ putMVar x 0
|
|
|
|
void . fork $ putMVar x 1
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
readMVar x
|
2015-11-16 05:48:54 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- @CRef@s
|
|
|
|
|
|
|
|
-- | When racing two @writeCRef@s, one of them will win.
|
|
|
|
crefRace :: MonadConc m => m Int
|
|
|
|
crefRace = do
|
|
|
|
x <- newCRef (0::Int)
|
|
|
|
|
|
|
|
j1 <- spawn $ writeCRef x 0
|
|
|
|
j2 <- spawn $ writeCRef x 1
|
|
|
|
|
2016-03-23 06:36:07 +03:00
|
|
|
takeMVar j1
|
|
|
|
takeMVar j2
|
2015-11-16 05:48:54 +03:00
|
|
|
|
|
|
|
readCRef x
|
|
|
|
|
2017-03-04 08:09:30 +03:00
|
|
|
-- | Modify CAS works.
|
|
|
|
crefCASModify :: MonadConc m => m Int
|
|
|
|
crefCASModify = do
|
|
|
|
x <- newCRef (0::Int)
|
|
|
|
fork $ modifyCRefCAS x (\_ -> (1, ()))
|
|
|
|
readCRef x
|
|
|
|
|
|
|
|
-- | CAS with two threads is racey.
|
|
|
|
crefCASRace :: MonadConc m => m (Bool, Int)
|
|
|
|
crefCASRace = do
|
|
|
|
x <- newCRef (0::Int)
|
|
|
|
t <- readForCAS x
|
|
|
|
j <- spawn $ casCRef x t 1
|
|
|
|
writeCRef x 2
|
|
|
|
b <- fst <$> readMVar j
|
|
|
|
v <- readCRef x
|
|
|
|
pure (b, v)
|
|
|
|
|
2017-09-06 18:03:28 +03:00
|
|
|
-- | Failed CAS can use the new ticket to succeed.
|
|
|
|
crefCASRaceRedo :: MonadConc m => m (Bool, Int)
|
|
|
|
crefCASRaceRedo = do
|
|
|
|
x <- newCRef (0::Int)
|
|
|
|
t <- readForCAS x
|
|
|
|
v <- newEmptyMVar
|
|
|
|
j <- spawn $ do
|
|
|
|
o@(f, t') <- casCRef x t 1
|
|
|
|
takeMVar v
|
|
|
|
if f then pure o else casCRef x t' 1
|
|
|
|
writeCRef x 2
|
|
|
|
putMVar v ()
|
|
|
|
b <- fst <$> readMVar j
|
|
|
|
v <- readCRef x
|
|
|
|
pure (b, v)
|
|
|
|
|
2017-03-04 08:09:30 +03:00
|
|
|
-- | A ticket is only good for one CAS.
|
|
|
|
crefCASTickets :: MonadConc m => m (Bool, Bool, Int)
|
|
|
|
crefCASTickets = do
|
|
|
|
x <- newCRef (0::Int)
|
|
|
|
t <- readForCAS x
|
|
|
|
j1 <- spawn $ casCRef x t 1
|
|
|
|
j2 <- spawn $ casCRef x t 2
|
|
|
|
b1 <- fst <$> readMVar j1
|
|
|
|
b2 <- fst <$> readMVar j2
|
|
|
|
v <- readCRef x
|
|
|
|
pure (b1, b2, v)
|
|
|
|
|
2015-11-16 05:48:54 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- STM
|
|
|
|
|
|
|
|
-- | Transactions are atomic.
|
|
|
|
stmAtomic :: MonadConc m => m Int
|
|
|
|
stmAtomic = do
|
2016-03-23 06:27:51 +03:00
|
|
|
x <- atomically $ newTVar (0::Int)
|
|
|
|
void . fork . atomically $ writeTVar x 1 >> writeTVar x 2
|
|
|
|
atomically $ readTVar x
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2016-07-21 20:47:37 +03:00
|
|
|
-- | 'retry' is the left identity of 'orElse'.
|
|
|
|
stmLeftRetry :: MonadConc m => m ()
|
|
|
|
stmLeftRetry = do
|
|
|
|
x <- atomically $ newTVar Nothing
|
|
|
|
let readJust var = maybe retry pure =<< readTVar var
|
|
|
|
fork . atomically . writeTVar x $ Just ()
|
|
|
|
atomically $ retry `orElse` readJust x
|
|
|
|
|
|
|
|
-- | 'retry' is the right identity of 'orElse'.
|
|
|
|
stmRightRetry :: MonadConc m => m ()
|
|
|
|
stmRightRetry = do
|
|
|
|
x <- atomically $ newTVar Nothing
|
|
|
|
let readJust var = maybe retry pure =<< readTVar var
|
|
|
|
fork . atomically . writeTVar x $ Just ()
|
|
|
|
atomically $ readJust x `orElse` retry
|
|
|
|
|
|
|
|
-- | Test case from issue #55.
|
|
|
|
stmIssue55 :: MonadConc m => m Bool
|
|
|
|
stmIssue55 = do
|
|
|
|
a <- atomically $ newTQueue
|
|
|
|
b <- atomically $ newTQueue
|
|
|
|
fork . atomically $ writeTQueue b True
|
|
|
|
let both a b = readTQueue a `orElse` readTQueue b `orElse` retry
|
|
|
|
atomically $ both a b
|
|
|
|
|
2017-09-01 15:53:16 +03:00
|
|
|
-- | Test case from issue #111
|
|
|
|
stmIssue111 :: MonadConc m => m Int
|
|
|
|
stmIssue111 = do
|
|
|
|
v <- atomically $ newTVar 1
|
|
|
|
fork . atomically $ do
|
|
|
|
writeTVar v 2
|
|
|
|
writeTVar v 3
|
|
|
|
retry
|
|
|
|
atomically $ readTVar v
|
|
|
|
|
2015-11-16 05:48:54 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Exceptions
|
|
|
|
|
|
|
|
-- | Cause a deadlock sometimes by killing a thread.
|
|
|
|
threadKill :: MonadConc m => m ()
|
|
|
|
threadKill = do
|
2016-03-23 06:36:07 +03:00
|
|
|
x <- newEmptyMVar
|
|
|
|
tid <- fork $ putMVar x ()
|
2015-11-16 05:48:54 +03:00
|
|
|
killThread tid
|
2016-03-23 06:36:07 +03:00
|
|
|
readMVar x
|
2015-11-16 05:48:54 +03:00
|
|
|
|
|
|
|
-- | Never deadlock by masking a thread.
|
|
|
|
threadKillMask :: MonadConc m => m ()
|
|
|
|
threadKillMask = do
|
2016-03-23 06:36:07 +03:00
|
|
|
x <- newEmptyMVar
|
|
|
|
y <- newEmptyMVar
|
2016-05-26 15:54:13 +03:00
|
|
|
tid <- fork $ mask $ \_ -> putMVar x () >> putMVar y ()
|
2016-03-23 06:36:07 +03:00
|
|
|
readMVar x
|
2015-11-16 05:48:54 +03:00
|
|
|
killThread tid
|
2016-03-23 06:36:07 +03:00
|
|
|
readMVar y
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2017-03-04 08:36:32 +03:00
|
|
|
-- | Deadlock trying to throw an exception to an
|
|
|
|
-- uninterruptibly-masked thread.
|
|
|
|
threadKillUninterruptibleMask :: MonadConc m => m ()
|
|
|
|
threadKillUninterruptibleMask = do
|
|
|
|
x <- newEmptyMVar
|
|
|
|
y <- newEmptyMVar
|
|
|
|
tid <- fork $ uninterruptibleMask $ \_ -> putMVar x () >> takeMVar y
|
|
|
|
readMVar x
|
|
|
|
killThread tid
|
|
|
|
|
2015-11-16 05:48:54 +03:00
|
|
|
-- | Sometimes deadlock by killing a thread.
|
|
|
|
threadKillUmask :: MonadConc m => m ()
|
|
|
|
threadKillUmask = do
|
2016-03-23 06:36:07 +03:00
|
|
|
x <- newEmptyMVar
|
|
|
|
y <- newEmptyMVar
|
2016-05-26 15:54:13 +03:00
|
|
|
tid <- fork $ mask $ \umask -> putMVar x () >> umask (putMVar y ())
|
2016-03-23 06:36:07 +03:00
|
|
|
readMVar x
|
2015-11-16 05:48:54 +03:00
|
|
|
killThread tid
|
2016-03-23 06:36:07 +03:00
|
|
|
readMVar y
|
2016-06-05 18:28:47 +03:00
|
|
|
|
2017-03-04 05:58:27 +03:00
|
|
|
-- | Throw an exception to the main thread with 'throwTo', without a
|
|
|
|
-- handler.
|
|
|
|
threadKillToMain1 :: MonadConc m => m ()
|
|
|
|
threadKillToMain1 = do
|
|
|
|
tid <- myThreadId
|
|
|
|
j <- spawn $ throwTo tid Overflow
|
|
|
|
readMVar j
|
|
|
|
|
|
|
|
-- | Throw an exception to the main thread with 'throwTo', with a
|
|
|
|
-- handler.
|
|
|
|
threadKillToMain2 :: MonadConc m => m ()
|
|
|
|
threadKillToMain2 = do
|
|
|
|
tid <- myThreadId
|
|
|
|
catchArithException (spawn (throwTo tid Overflow) >>= readMVar)
|
|
|
|
(\_ -> pure ())
|
|
|
|
|
2016-06-05 18:28:47 +03:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- Daemon threads
|
|
|
|
|
|
|
|
-- | Fork off a thread where the first action has no dependency with
|
|
|
|
-- anything the initial thread does, but which has a later action
|
|
|
|
-- which does. This exhibits issue #40.
|
|
|
|
schedDaemon :: MonadConc m => m Int
|
|
|
|
schedDaemon = do
|
|
|
|
x <- newCRef 0
|
|
|
|
_ <- fork $ myThreadId >> writeCRef x 1
|
|
|
|
readCRef x
|
2017-02-02 15:26:40 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Subconcurrency
|
|
|
|
|
|
|
|
-- | Subcomputation deadlocks sometimes.
|
2017-02-26 06:02:54 +03:00
|
|
|
scDeadlock1 :: Monad n => ConcT r n (Either Failure ())
|
2017-02-02 15:26:40 +03:00
|
|
|
scDeadlock1 = do
|
|
|
|
var <- newEmptyMVar
|
|
|
|
subconcurrency $ do
|
|
|
|
void . fork $ putMVar var ()
|
|
|
|
putMVar var ()
|
|
|
|
|
|
|
|
-- | Subcomputation deadlocks sometimes, and action after it still
|
|
|
|
-- happens.
|
2017-02-26 06:02:54 +03:00
|
|
|
scDeadlock2 :: Monad n => ConcT r n (Either Failure (), ())
|
2017-02-02 15:26:40 +03:00
|
|
|
scDeadlock2 = do
|
|
|
|
var <- newEmptyMVar
|
|
|
|
res <- subconcurrency $ do
|
|
|
|
void . fork $ putMVar var ()
|
|
|
|
putMVar var ()
|
|
|
|
(,) <$> pure res <*> readMVar var
|
|
|
|
|
|
|
|
-- | Subcomputation successfully completes.
|
2017-02-26 06:02:54 +03:00
|
|
|
scSuccess :: Monad n => ConcT r n (Either Failure ())
|
2017-02-02 15:26:40 +03:00
|
|
|
scSuccess = do
|
|
|
|
var <- newMVar ()
|
|
|
|
subconcurrency $ do
|
|
|
|
out <- newEmptyMVar
|
|
|
|
void . fork $ takeMVar var >>= putMVar out
|
|
|
|
takeMVar out
|
|
|
|
|
|
|
|
-- | Illegal usage
|
2017-02-26 06:02:54 +03:00
|
|
|
scIllegal :: Monad n => ConcT r n ()
|
2017-02-02 15:26:40 +03:00
|
|
|
scIllegal = do
|
|
|
|
var <- newEmptyMVar
|
|
|
|
void . fork $ readMVar var
|
|
|
|
void . subconcurrency $ pure ()
|
2017-02-25 09:01:38 +03:00
|
|
|
|
|
|
|
-- | Test case from issue 71. This won't fail if the bug is
|
|
|
|
-- reintroduced, it will just hang.
|
2017-02-26 06:02:54 +03:00
|
|
|
scIssue71 :: Monad n => ConcT r n ()
|
2017-02-25 09:01:38 +03:00
|
|
|
scIssue71 = do
|
|
|
|
let ma ||| mb = do { j1 <- spawn ma; j2 <- spawn mb; takeMVar j1; takeMVar j2; pure () }
|
|
|
|
s <- newEmptyMVar
|
|
|
|
_ <- subconcurrency (takeMVar s ||| pure ())
|
|
|
|
pure ()
|
2017-05-04 01:09:56 +03:00
|
|
|
|
|
|
|
-- | Test case from issue 81.
|
|
|
|
scIssue81 :: Monad n => ConcT r n (Either Failure (), Int)
|
|
|
|
scIssue81 = do
|
|
|
|
s <- newQSemN 0
|
|
|
|
let interfere = waitQSemN s 0 >> signalQSemN s 0
|
|
|
|
x <- subconcurrency (signalQSemN s 0 ||| waitQSemN s 0 ||| interfere)
|
|
|
|
o <- remainingQSemN s
|
|
|
|
pure (x, o)
|