2014-12-20 14:01:51 +03:00
|
|
|
-- | A runner for concurrent monads to systematically detect
|
|
|
|
-- concurrency errors such as data races and deadlocks.
|
|
|
|
--
|
|
|
|
-- As an example, consider this program, which has two locks and a
|
|
|
|
-- shared variable. Two threads are spawned, which claim the locks,
|
|
|
|
-- update the shared variable, and release the locks. The main thread
|
|
|
|
-- waits for them both to terminate, and returns the final result.
|
|
|
|
--
|
2014-12-21 15:59:57 +03:00
|
|
|
-- > bad :: ConcCVar cvar m => m Int
|
2014-12-20 14:01:51 +03:00
|
|
|
-- > bad = do
|
2014-12-21 10:47:45 +03:00
|
|
|
-- > a <- newEmptyCVar
|
|
|
|
-- > b <- newEmptyCVar
|
2014-12-20 14:01:51 +03:00
|
|
|
-- >
|
2014-12-21 12:38:25 +03:00
|
|
|
-- > c <- newCVar 0
|
2014-12-20 14:01:51 +03:00
|
|
|
-- >
|
2014-12-21 12:38:25 +03:00
|
|
|
-- > 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
|
2014-12-20 14:01:51 +03:00
|
|
|
-- >
|
2014-12-21 10:47:45 +03:00
|
|
|
-- > takeCVar j1
|
|
|
|
-- > takeCVar j2
|
2014-12-20 14:01:51 +03:00
|
|
|
-- >
|
2014-12-21 10:47:45 +03:00
|
|
|
-- > takeCVar c
|
2014-12-20 14:01:51 +03:00
|
|
|
--
|
|
|
|
-- The correct result is 0, as it starts out as 0 and is incremented
|
|
|
|
-- and decremented by threads 1 and 2, respectively. However, note the
|
|
|
|
-- order of acquisition of the locks in the two threads. If thread 2
|
|
|
|
-- pre-empts thread 1 between the acquisition of the locks (or if
|
|
|
|
-- thread 1 pre-empts thread 2), a deadlock situation will arise, as
|
2015-01-12 17:24:12 +03:00
|
|
|
-- thread 1 will have lock @a@ and be waiting on @b@, and thread 2
|
|
|
|
-- will have @b@ and be waiting on @a@.
|
2014-12-20 14:01:51 +03:00
|
|
|
|
|
|
|
module Control.Monad.Conc.SCT
|
2015-01-05 00:48:00 +03:00
|
|
|
( -- * Types
|
2014-12-20 14:01:51 +03:00
|
|
|
SCTScheduler
|
2014-12-25 00:41:32 +03:00
|
|
|
, SchedTrace
|
|
|
|
, SCTTrace
|
2014-12-21 15:42:43 +03:00
|
|
|
, Decision(..)
|
2015-01-05 00:48:00 +03:00
|
|
|
|
|
|
|
-- * SCT Runners
|
2014-12-20 14:01:51 +03:00
|
|
|
, runSCT
|
2014-12-28 15:12:57 +03:00
|
|
|
, runSCTIO
|
2015-01-04 18:06:53 +03:00
|
|
|
, runSCT'
|
|
|
|
, runSCTIO'
|
2014-12-21 16:50:52 +03:00
|
|
|
|
2015-01-05 00:48:00 +03:00
|
|
|
-- * Random Schedulers
|
2014-12-20 14:01:51 +03:00
|
|
|
, sctRandom
|
2014-12-21 16:50:52 +03:00
|
|
|
, sctRandomNP
|
|
|
|
|
2015-01-05 00:48:00 +03:00
|
|
|
-- * Pre-emption Bounding
|
|
|
|
, sctPreBound
|
|
|
|
, sctPreBoundIO
|
|
|
|
, preEmpCount
|
|
|
|
|
2014-12-21 16:50:52 +03:00
|
|
|
-- * Utilities
|
2015-01-12 17:24:12 +03:00
|
|
|
, makeSCT
|
2014-12-21 15:42:43 +03:00
|
|
|
, showTrace
|
2014-12-20 14:01:51 +03:00
|
|
|
) where
|
|
|
|
|
|
|
|
import Control.Monad.Conc.Fixed
|
2015-01-09 05:35:28 +03:00
|
|
|
import Control.Monad.Conc.SCT.Internal
|
|
|
|
import Control.Monad.Conc.SCT.PreBound
|
2014-12-21 19:34:55 +03:00
|
|
|
import System.Random (RandomGen)
|
2014-12-20 14:01:51 +03:00
|
|
|
|
2015-01-05 00:48:00 +03:00
|
|
|
-- * Random Schedulers
|
2014-12-20 14:01:51 +03:00
|
|
|
|
|
|
|
-- | A simple pre-emptive random scheduler.
|
|
|
|
sctRandom :: RandomGen g => SCTScheduler g
|
2015-01-12 17:24:12 +03:00
|
|
|
sctRandom = makeSCT randomSched
|
2014-12-21 16:50:52 +03:00
|
|
|
|
|
|
|
-- | A random scheduler with no pre-emption.
|
|
|
|
sctRandomNP :: RandomGen g => SCTScheduler g
|
2015-01-12 17:24:12 +03:00
|
|
|
sctRandomNP = makeSCT randomSchedNP
|
2014-12-21 16:50:52 +03:00
|
|
|
|
2015-01-05 00:48:00 +03:00
|
|
|
-- * Utils
|
|
|
|
|
2014-12-21 16:50:52 +03:00
|
|
|
-- | Convert a 'Scheduler' to an 'SCTScheduler' by recording the
|
|
|
|
-- trace.
|
2015-01-12 17:24:12 +03:00
|
|
|
makeSCT :: Scheduler s -> SCTScheduler s
|
|
|
|
makeSCT sched (s, trace) prior threads = (tid, (s', (decision, alters) : trace)) where
|
2014-12-21 19:34:55 +03:00
|
|
|
(tid, s') = sched s prior threads
|
2014-12-21 16:50:52 +03:00
|
|
|
|
2015-01-12 18:58:53 +03:00
|
|
|
decision | tid == prior = Continue
|
|
|
|
| prior `elem` threads' = SwitchTo tid
|
|
|
|
| otherwise = Start tid
|
2014-12-21 15:42:43 +03:00
|
|
|
|
2015-01-12 18:58:53 +03:00
|
|
|
alters | tid == prior = map SwitchTo $ filter (/=prior) threads'
|
|
|
|
| prior `elem` threads' = Continue : map SwitchTo (filter (\t -> t /= prior && t /= tid) threads')
|
|
|
|
| otherwise = map Start $ filter (/=tid) threads'
|
|
|
|
|
|
|
|
threads' = toList threads
|
2014-12-21 16:25:48 +03:00
|
|
|
|
2014-12-21 15:42:43 +03:00
|
|
|
-- | Pretty-print a scheduler trace.
|
2014-12-25 00:41:32 +03:00
|
|
|
showTrace :: SchedTrace -> String
|
2014-12-21 16:25:48 +03:00
|
|
|
showTrace = trace "" 0 . map fst where
|
2014-12-21 19:34:55 +03:00
|
|
|
trace prefix num (Start tid:ds) = thread prefix num ++ trace ("S" ++ show tid) 1 ds
|
|
|
|
trace prefix num (SwitchTo tid:ds) = thread prefix num ++ trace ("P" ++ show tid) 1 ds
|
|
|
|
trace prefix num (Continue:ds) = trace prefix (num + 1) ds
|
|
|
|
trace prefix num [] = thread prefix num
|
2014-12-21 15:42:43 +03:00
|
|
|
|
2014-12-21 19:34:55 +03:00
|
|
|
thread prefix num = prefix ++ replicate num '-'
|