mirror of
https://github.com/barrucadu/dejafu.git
synced 2024-12-18 19:11:37 +03:00
Add a pre-emption bounding runner, and use it for tests
This commit is contained in:
parent
edbe04be64
commit
f7ad64fe5b
@ -94,7 +94,9 @@ data ThreadAction =
|
|||||||
-- ^ Try to take from a 'CVar', possibly waking up some threads.
|
-- ^ Try to take from a 'CVar', possibly waking up some threads.
|
||||||
| Lift
|
| Lift
|
||||||
-- ^ Lift an action from the underlying monad.
|
-- ^ Lift an action from the underlying monad.
|
||||||
deriving (Eq, Show)
|
| Stop
|
||||||
|
-- ^ Cease execution and terminate.
|
||||||
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
-- | Run a concurrent computation with a given 'Scheduler' and initial
|
-- | Run a concurrent computation with a given 'Scheduler' and initial
|
||||||
-- state, returning `Just result` if it terminates, and `Nothing` if a
|
-- state, returning `Just result` if it terminates, and `Nothing` if a
|
||||||
@ -144,7 +146,7 @@ runThreads fixed sofar prior sched s threads ref
|
|||||||
| isNonexistant = writeRef fixed ref Nothing >> return (s, sofar)
|
| isNonexistant = writeRef fixed ref Nothing >> return (s, sofar)
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
(threads', act) <- stepThread (fst $ fromJust thread) fixed chosen threads
|
(threads', act) <- stepThread (fst $ fromJust thread) fixed chosen threads
|
||||||
let sofar' = maybe sofar (\a -> (chosen, a) : sofar) act
|
let sofar' = (chosen, act) : sofar
|
||||||
runThreads fixed sofar' chosen sched s' threads' ref
|
runThreads fixed sofar' chosen sched s' threads' ref
|
||||||
|
|
||||||
where
|
where
|
||||||
@ -160,7 +162,7 @@ runThreads fixed sofar prior sched s threads ref
|
|||||||
-- 'Action'.
|
-- 'Action'.
|
||||||
stepThread :: (Monad (c t), Monad n)
|
stepThread :: (Monad (c t), Monad n)
|
||||||
=> Action n r
|
=> Action n r
|
||||||
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, Maybe ThreadAction)
|
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, ThreadAction)
|
||||||
stepThread (AFork a b) = stepFork a b
|
stepThread (AFork a b) = stepFork a b
|
||||||
stepThread (APut ref a c) = stepPut ref a c
|
stepThread (APut ref a c) = stepPut ref a c
|
||||||
stepThread (ATryPut ref a c) = stepTryPut ref a c
|
stepThread (ATryPut ref a c) = stepTryPut ref a c
|
||||||
@ -168,99 +170,99 @@ stepThread (AGet ref c) = stepGet ref c
|
|||||||
stepThread (ATake ref c) = stepTake ref c
|
stepThread (ATake ref c) = stepTake ref c
|
||||||
stepThread (ATryTake ref c) = stepTryTake ref c
|
stepThread (ATryTake ref c) = stepTryTake ref c
|
||||||
stepThread (ALift na) = stepLift na
|
stepThread (ALift na) = stepLift na
|
||||||
stepThread AStop = stepStop
|
stepThread AStop = stepStop
|
||||||
|
|
||||||
-- | Start a new thread, assigning it a unique 'ThreadId'
|
-- | Start a new thread, assigning it a unique 'ThreadId'
|
||||||
stepFork :: (Monad (c t), Monad n)
|
stepFork :: (Monad (c t), Monad n)
|
||||||
=> Action n r -> Action n r
|
=> Action n r -> Action n r
|
||||||
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, Maybe ThreadAction)
|
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, ThreadAction)
|
||||||
stepFork a b _ i threads =
|
stepFork a b _ i threads =
|
||||||
let (threads', newid) = launch a threads
|
let (threads', newid) = launch a threads
|
||||||
in return (goto b i threads', Just $ Fork newid)
|
in return (goto b i threads', Fork newid)
|
||||||
|
|
||||||
-- | Put a value into a @CVar@, blocking the thread until it's empty.
|
-- | Put a value into a @CVar@, blocking the thread until it's empty.
|
||||||
stepPut :: (Monad (c t), Monad n)
|
stepPut :: (Monad (c t), Monad n)
|
||||||
=> R r a -> a -> Action n r
|
=> R r a -> a -> Action n r
|
||||||
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, Maybe ThreadAction)
|
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, ThreadAction)
|
||||||
stepPut ref a c fixed i threads = do
|
stepPut ref a c fixed i threads = do
|
||||||
(val, blocks) <- readRef fixed ref
|
(val, blocks) <- readRef fixed ref
|
||||||
case val of
|
case val of
|
||||||
Just _ -> do
|
Just _ -> do
|
||||||
threads' <- block fixed ref WaitEmpty i threads
|
threads' <- block fixed ref WaitEmpty i threads
|
||||||
return (threads', Just BlockedPut)
|
return (threads', BlockedPut)
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
writeRef fixed ref (Just a, blocks)
|
writeRef fixed ref (Just a, blocks)
|
||||||
(threads', woken) <- wake fixed ref WaitFull threads
|
(threads', woken) <- wake fixed ref WaitFull threads
|
||||||
return (goto c i threads', Just $ Put woken)
|
return (goto c i threads', Put woken)
|
||||||
|
|
||||||
-- | Try to put a value into a @CVar@, without blocking.
|
-- | Try to put a value into a @CVar@, without blocking.
|
||||||
stepTryPut :: (Monad (c t), Monad n)
|
stepTryPut :: (Monad (c t), Monad n)
|
||||||
=> R r a -> a -> (Bool -> Action n r)
|
=> R r a -> a -> (Bool -> Action n r)
|
||||||
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, Maybe ThreadAction)
|
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, ThreadAction)
|
||||||
stepTryPut ref a c fixed i threads = do
|
stepTryPut ref a c fixed i threads = do
|
||||||
(val, blocks) <- readRef fixed ref
|
(val, blocks) <- readRef fixed ref
|
||||||
case val of
|
case val of
|
||||||
Just _ -> return (goto (c False) i threads, Just $ TryPut False [])
|
Just _ -> return (goto (c False) i threads, TryPut False [])
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
writeRef fixed ref (Just a, blocks)
|
writeRef fixed ref (Just a, blocks)
|
||||||
(threads', woken) <- wake fixed ref WaitFull threads
|
(threads', woken) <- wake fixed ref WaitFull threads
|
||||||
return (goto (c True) i threads', Just $ TryPut True woken)
|
return (goto (c True) i threads', TryPut True woken)
|
||||||
|
|
||||||
-- | Get the value from a @CVar@, without emptying, blocking the
|
-- | Get the value from a @CVar@, without emptying, blocking the
|
||||||
-- thread until it's full.
|
-- thread until it's full.
|
||||||
stepGet :: (Monad (c t), Monad n)
|
stepGet :: (Monad (c t), Monad n)
|
||||||
=> R r a -> (a -> Action n r)
|
=> R r a -> (a -> Action n r)
|
||||||
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, Maybe ThreadAction)
|
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, ThreadAction)
|
||||||
stepGet ref c fixed i threads = do
|
stepGet ref c fixed i threads = do
|
||||||
(val, _) <- readRef fixed ref
|
(val, _) <- readRef fixed ref
|
||||||
case val of
|
case val of
|
||||||
Just val' -> return (goto (c val') i threads, Just Read)
|
Just val' -> return (goto (c val') i threads, Read)
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
threads' <- block fixed ref WaitFull i threads
|
threads' <- block fixed ref WaitFull i threads
|
||||||
return (threads', Just BlockedRead)
|
return (threads', BlockedRead)
|
||||||
|
|
||||||
-- | Take the value from a @CVar@, blocking the thread until it's
|
-- | Take the value from a @CVar@, blocking the thread until it's
|
||||||
-- full.
|
-- full.
|
||||||
stepTake :: (Monad (c t), Monad n)
|
stepTake :: (Monad (c t), Monad n)
|
||||||
=> R r a -> (a -> Action n r)
|
=> R r a -> (a -> Action n r)
|
||||||
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, Maybe ThreadAction)
|
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, ThreadAction)
|
||||||
stepTake ref c fixed i threads = do
|
stepTake ref c fixed i threads = do
|
||||||
(val, blocks) <- readRef fixed ref
|
(val, blocks) <- readRef fixed ref
|
||||||
case val of
|
case val of
|
||||||
Just val' -> do
|
Just val' -> do
|
||||||
writeRef fixed ref (Nothing, blocks)
|
writeRef fixed ref (Nothing, blocks)
|
||||||
(threads', woken) <- wake fixed ref WaitEmpty threads
|
(threads', woken) <- wake fixed ref WaitEmpty threads
|
||||||
return (goto (c val') i threads', Just $ Take woken)
|
return (goto (c val') i threads', Take woken)
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
threads' <- block fixed ref WaitFull i threads
|
threads' <- block fixed ref WaitFull i threads
|
||||||
return (threads', Just BlockedTake)
|
return (threads', BlockedTake)
|
||||||
|
|
||||||
-- | Try to take the value from a @CVar@, without blocking.
|
-- | Try to take the value from a @CVar@, without blocking.
|
||||||
stepTryTake :: (Monad (c t), Monad n)
|
stepTryTake :: (Monad (c t), Monad n)
|
||||||
=> R r a -> (Maybe a -> Action n r)
|
=> R r a -> (Maybe a -> Action n r)
|
||||||
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, Maybe ThreadAction)
|
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, ThreadAction)
|
||||||
stepTryTake ref c fixed i threads = do
|
stepTryTake ref c fixed i threads = do
|
||||||
(val, blocks) <- readRef fixed ref
|
(val, blocks) <- readRef fixed ref
|
||||||
case val of
|
case val of
|
||||||
Just _ -> do
|
Just _ -> do
|
||||||
writeRef fixed ref (Nothing, blocks)
|
writeRef fixed ref (Nothing, blocks)
|
||||||
(threads', woken) <- wake fixed ref WaitEmpty threads
|
(threads', woken) <- wake fixed ref WaitEmpty threads
|
||||||
return (goto (c val) i threads', Just $ TryTake True woken)
|
return (goto (c val) i threads', TryTake True woken)
|
||||||
Nothing -> return (goto (c Nothing) i threads, Just $ TryTake False [])
|
Nothing -> return (goto (c Nothing) i threads, TryTake False [])
|
||||||
|
|
||||||
-- | Lift an action from the underlying monad into the @Conc@
|
-- | Lift an action from the underlying monad into the @Conc@
|
||||||
-- computation.
|
-- computation.
|
||||||
stepLift :: (Monad (c t), Monad n)
|
stepLift :: (Monad (c t), Monad n)
|
||||||
=> n (Action n r)
|
=> n (Action n r)
|
||||||
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, Maybe ThreadAction)
|
-> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, ThreadAction)
|
||||||
stepLift na _ i threads = do
|
stepLift na _ i threads = do
|
||||||
a <- na
|
a <- na
|
||||||
return (goto a i threads, Just Lift)
|
return (goto a i threads, Lift)
|
||||||
|
|
||||||
-- | Kill the current thread.
|
-- | Kill the current thread.
|
||||||
stepStop :: (Monad (c t), Monad n)
|
stepStop :: (Monad (c t), Monad n)
|
||||||
=> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, Maybe ThreadAction)
|
=> Fixed c n r t -> ThreadId -> Threads n r -> n (Threads n r, ThreadAction)
|
||||||
stepStop _ i threads = return (kill i threads, Nothing)
|
stepStop _ i threads = return (kill i threads, Stop)
|
||||||
|
|
||||||
-- * Manipulating threads
|
-- * Manipulating threads
|
||||||
|
|
||||||
|
@ -32,29 +32,41 @@
|
|||||||
-- will have `b` and be waiting on `a`.
|
-- will have `b` and be waiting on `a`.
|
||||||
|
|
||||||
module Control.Monad.Conc.SCT
|
module Control.Monad.Conc.SCT
|
||||||
( -- *Systematic Concurrency Testing
|
( -- * Types
|
||||||
SCTScheduler
|
SCTScheduler
|
||||||
, SchedTrace
|
, SchedTrace
|
||||||
, SCTTrace
|
, SCTTrace
|
||||||
, Decision(..)
|
, Decision(..)
|
||||||
|
|
||||||
|
-- * SCT Runners
|
||||||
, runSCT
|
, runSCT
|
||||||
, runSCTIO
|
, runSCTIO
|
||||||
, runSCT'
|
, runSCT'
|
||||||
, runSCTIO'
|
, runSCTIO'
|
||||||
|
|
||||||
-- * Schedulers
|
-- * Random Schedulers
|
||||||
, sctRandom
|
, sctRandom
|
||||||
, sctRandomNP
|
, sctRandomNP
|
||||||
|
|
||||||
|
-- * Pre-emption Bounding
|
||||||
|
, sctPreBound
|
||||||
|
, sctPreBoundIO
|
||||||
|
, preEmpCount
|
||||||
|
|
||||||
-- * Utilities
|
-- * Utilities
|
||||||
, toSCT
|
, toSCT
|
||||||
, showTrace
|
, showTrace
|
||||||
|
, ordNub
|
||||||
|
, (~=)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad.Conc.Fixed
|
import Control.Monad.Conc.Fixed
|
||||||
import System.Random (RandomGen)
|
import System.Random (RandomGen)
|
||||||
|
|
||||||
import qualified Control.Monad.Conc.Fixed.IO as CIO
|
import qualified Control.Monad.Conc.Fixed.IO as CIO
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
|
-- * Types
|
||||||
|
|
||||||
-- | An @SCTScheduler@ is like a regular 'Scheduler', except it builds
|
-- | An @SCTScheduler@ is like a regular 'Scheduler', except it builds
|
||||||
-- a trace of scheduling decisions made.
|
-- a trace of scheduling decisions made.
|
||||||
@ -79,7 +91,9 @@ data Decision =
|
|||||||
-- ^ Continue running the last thread for another step.
|
-- ^ Continue running the last thread for another step.
|
||||||
| SwitchTo ThreadId
|
| SwitchTo ThreadId
|
||||||
-- ^ Pre-empt the running thread, and switch to another.
|
-- ^ Pre-empt the running thread, and switch to another.
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
-- * SCT Runners
|
||||||
|
|
||||||
-- | Run a concurrent program under a given scheduler a number of
|
-- | Run a concurrent program under a given scheduler a number of
|
||||||
-- times, collecting the results and the scheduling that gave rise to
|
-- times, collecting the results and the scheduling that gave rise to
|
||||||
@ -90,8 +104,8 @@ data Decision =
|
|||||||
-- internal state, or all the results will be identical.
|
-- internal state, or all the results will be identical.
|
||||||
runSCT :: SCTScheduler s -> s -> Int -> (forall t. Conc t a) -> [(Maybe a, SCTTrace)]
|
runSCT :: SCTScheduler s -> s -> Int -> (forall t. Conc t a) -> [(Maybe a, SCTTrace)]
|
||||||
runSCT sched s n = runSCT' sched s n term step where
|
runSCT sched s n = runSCT' sched s n term step where
|
||||||
term (_, g) = g == 0
|
term _ g = g == 0
|
||||||
step (s, g) = (s, g - 1)
|
step s' g _ = (s', g - 1)
|
||||||
|
|
||||||
-- | A varant of 'runSCT' for concurrent programs that do 'IO'.
|
-- | A varant of 'runSCT' for concurrent programs that do 'IO'.
|
||||||
--
|
--
|
||||||
@ -101,8 +115,8 @@ runSCT sched s n = runSCT' sched s n term step where
|
|||||||
-- function!
|
-- function!
|
||||||
runSCTIO :: SCTScheduler s -> s -> Int -> (forall t. CIO.Conc t a) -> IO [(Maybe a, SCTTrace)]
|
runSCTIO :: SCTScheduler s -> s -> Int -> (forall t. CIO.Conc t a) -> IO [(Maybe a, SCTTrace)]
|
||||||
runSCTIO sched s n = runSCTIO' sched s n term step where
|
runSCTIO sched s n = runSCTIO' sched s n term step where
|
||||||
term (_, g) = g == 0
|
term _ g = g == 0
|
||||||
step (s, g) = (s, g - 1)
|
step s' g _ = (s', g - 1)
|
||||||
|
|
||||||
-- | Run a concurrent program under a given scheduler, where the SCT
|
-- | Run a concurrent program under a given scheduler, where the SCT
|
||||||
-- runner itself maintains some internal state, and has a function to
|
-- runner itself maintains some internal state, and has a function to
|
||||||
@ -114,17 +128,19 @@ runSCTIO sched s n = runSCTIO' sched s n term step where
|
|||||||
runSCT' :: SCTScheduler s -- ^ The scheduler
|
runSCT' :: SCTScheduler s -- ^ The scheduler
|
||||||
-> s -- ^ The scheduler's initial satte
|
-> s -- ^ The scheduler's initial satte
|
||||||
-> g -- ^ The runner's initial state
|
-> g -- ^ The runner's initial state
|
||||||
-> ((s, g) -> Bool) -- ^ Termination decider
|
-> (s -> g -> Bool) -- ^ Termination decider
|
||||||
-> ((s, g) -> (s, g)) -- ^ State step function
|
-> (s -> g -> SCTTrace -> (s, g)) -- ^ State step function
|
||||||
-> (forall t. Conc t a) -- ^ Conc program
|
-> (forall t. Conc t a) -- ^ Conc program
|
||||||
-> [(Maybe a, SCTTrace)]
|
-> [(Maybe a, SCTTrace)]
|
||||||
runSCT' sched s g term step c
|
runSCT' sched s g term step c
|
||||||
| term (s, g) = []
|
| term s g = []
|
||||||
| otherwise = (res, scttrace strace ttrace) : rest where
|
| otherwise = (res, trace) : rest where
|
||||||
|
|
||||||
(res, (s', strace), ttrace) = runConc' sched (s, [(Start 0, [])]) c
|
(res, (s', strace), ttrace) = runConc' sched (s, [(Start 0, [])]) c
|
||||||
|
|
||||||
(s'', g') = step (s', g)
|
trace = scttrace strace ttrace
|
||||||
|
|
||||||
|
(s'', g') = step s' g trace
|
||||||
|
|
||||||
rest = runSCT' sched s'' g' term step c
|
rest = runSCT' sched s'' g' term step c
|
||||||
|
|
||||||
@ -134,22 +150,20 @@ runSCT' sched s g term step c
|
|||||||
-- interleavings! Be very confident that nothing in a 'liftIO' can
|
-- interleavings! Be very confident that nothing in a 'liftIO' can
|
||||||
-- block on the action of another thread, or you risk deadlocking this
|
-- block on the action of another thread, or you risk deadlocking this
|
||||||
-- function!
|
-- function!
|
||||||
runSCTIO' :: SCTScheduler s -> s -> g -> ((s, g) -> Bool) -> ((s, g) -> (s, g)) -> (forall t. CIO.Conc t a) -> IO [(Maybe a, SCTTrace)]
|
runSCTIO' :: SCTScheduler s -> s -> g -> (s -> g -> Bool) -> (s -> g -> SCTTrace -> (s, g)) -> (forall t. CIO.Conc t a) -> IO [(Maybe a, SCTTrace)]
|
||||||
runSCTIO' sched s g term step c
|
runSCTIO' sched s g term step c
|
||||||
| term (s, g) = return []
|
| term s g = return []
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
(res, (s', strace), ttrace) <- CIO.runConc' sched (s, [(Start 0, [])]) c
|
(res, (s', strace), ttrace) <- CIO.runConc' sched (s, [(Start 0, [])]) c
|
||||||
|
|
||||||
let (s'', g') = step (s', g)
|
let trace = scttrace strace ttrace
|
||||||
|
let (s'', g') = step s' g trace
|
||||||
|
|
||||||
rest <- runSCTIO' sched s'' g' term step c
|
rest <- runSCTIO' sched s'' g' term step c
|
||||||
|
|
||||||
return $ (res, scttrace strace ttrace) : rest
|
return $ (res, trace) : rest
|
||||||
|
|
||||||
-- | Zip a list of 'SchedTrace's and a 'Trace' together into an
|
-- * Random Schedulers
|
||||||
-- 'SCTTrace'.
|
|
||||||
scttrace :: SchedTrace -> Trace -> SCTTrace
|
|
||||||
scttrace = zipWith $ \(d, alts) (_, act) -> (d, alts, act)
|
|
||||||
|
|
||||||
-- | A simple pre-emptive random scheduler.
|
-- | A simple pre-emptive random scheduler.
|
||||||
sctRandom :: RandomGen g => SCTScheduler g
|
sctRandom :: RandomGen g => SCTScheduler g
|
||||||
@ -159,6 +173,119 @@ sctRandom = toSCT randomSched
|
|||||||
sctRandomNP :: RandomGen g => SCTScheduler g
|
sctRandomNP :: RandomGen g => SCTScheduler g
|
||||||
sctRandomNP = toSCT randomSchedNP
|
sctRandomNP = toSCT randomSchedNP
|
||||||
|
|
||||||
|
-- * Pre-emption bounding
|
||||||
|
|
||||||
|
data PreBoundState = P
|
||||||
|
{ _pc :: Int
|
||||||
|
-- ^ Current pre-emption count.
|
||||||
|
, _next :: [[Decision]]
|
||||||
|
-- ^ Schedules to try in this pc.
|
||||||
|
, _done :: [SCTTrace]
|
||||||
|
-- ^ Schedules completed in this pc.
|
||||||
|
, _halt :: Bool
|
||||||
|
-- ^ Indicates more schedules couldn't be found, and to halt
|
||||||
|
-- immediately.
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | An SCT runner using a pre-emption bounding scheduler. Schedules
|
||||||
|
-- will be explored systematically, starting with all
|
||||||
|
-- pre-emption-count zero schedules, and gradually adding more
|
||||||
|
-- pre-emptions.
|
||||||
|
sctPreBound :: Int -- ^ The pre-emption bound. Anything < 0 will be
|
||||||
|
-- interpreted as 0.
|
||||||
|
-> (forall t. Conc t a) -> [(Maybe a, SCTTrace)]
|
||||||
|
sctPreBound pb = runSCT' pbSched s g (pbTerm pb') (pbStep pb') where
|
||||||
|
s = []
|
||||||
|
g = P { _pc = 0, _next = [], _done = [], _halt = False }
|
||||||
|
pb' = if pb < 0 then 0 else pb
|
||||||
|
|
||||||
|
-- | Variant of 'sctPreBound' using 'IO'. See usual caveats about IO.
|
||||||
|
sctPreBoundIO :: Int -> (forall t. CIO.Conc t a) -> IO [(Maybe a, SCTTrace)]
|
||||||
|
sctPreBoundIO pb = runSCTIO' pbSched s g (pbTerm pb') (pbStep pb') where
|
||||||
|
s = []
|
||||||
|
g = P { _pc = 0, _next = [], _done = [], _halt = False }
|
||||||
|
pb' = if pb < 0 then 0 else pb
|
||||||
|
|
||||||
|
-- | Pre-emption bounding scheduler, which uses a queue of scheduling
|
||||||
|
-- decisions to drive the initial trace.
|
||||||
|
pbSched :: SCTScheduler [Decision]
|
||||||
|
pbSched = toSCT sched where
|
||||||
|
-- If we have a decision queued, make it.
|
||||||
|
sched (Start t:ds) _ _ = (t, ds)
|
||||||
|
sched (Continue:ds) t _ = (t, ds)
|
||||||
|
sched (SwitchTo t:ds) _ _ = (t, ds)
|
||||||
|
|
||||||
|
-- Otherwise just use a non-pre-emptive scheduler.
|
||||||
|
sched [] t1 ts@(t2:_)
|
||||||
|
| t1 `elem` ts = (t1, [])
|
||||||
|
| otherwise = (t2, [])
|
||||||
|
|
||||||
|
-- Error, should never happen, so just deadlock it.
|
||||||
|
sched [] _ [] = (-1, [])
|
||||||
|
|
||||||
|
-- | Pre-emption bounding termination function: terminates on attempt
|
||||||
|
-- to start a PB above the limit.
|
||||||
|
pbTerm :: Int -> a -> PreBoundState -> Bool
|
||||||
|
pbTerm pb _ g = (_pc g == pb + 1) || _halt g
|
||||||
|
|
||||||
|
-- | Pre-emption bounding state step function: computes remaining
|
||||||
|
-- schedules to try and chooses one.
|
||||||
|
pbStep :: Int -> a -> PreBoundState -> SCTTrace -> ([Decision], PreBoundState)
|
||||||
|
pbStep pb _ g t = case _next g of
|
||||||
|
-- We have schedules remaining in this PB, so run the next
|
||||||
|
(x:xs) -> (tail x, g { _next = xs, _done = done' })
|
||||||
|
|
||||||
|
-- We have no schedules remaining, try to generate some more.
|
||||||
|
--
|
||||||
|
-- If there are no more schedules in this PB, and this isn't the
|
||||||
|
-- last PB, advance to the next.
|
||||||
|
--
|
||||||
|
-- If there are no schedules in the next PB, halt.
|
||||||
|
[] ->
|
||||||
|
let thisPB = [y | y <- concatMap others done', preEmpCount y == _pc g, not $ any (y ~=) done']
|
||||||
|
nextPB = ordNub [y | y <- concatMap next done', preEmpCount y == pc']
|
||||||
|
in case thisPB of
|
||||||
|
(x:xs) -> (tail x, g { _next = xs, _done = done' })
|
||||||
|
[] -> if _pc g == pb
|
||||||
|
then halt
|
||||||
|
else case nextPB of
|
||||||
|
(x:xs) -> (tail x, g { _pc = pc', _next = xs, _done = [] })
|
||||||
|
[] -> halt
|
||||||
|
|
||||||
|
where
|
||||||
|
halt = ([], g { _halt = True })
|
||||||
|
done' = t : _done g
|
||||||
|
pc' = _pc g + 1
|
||||||
|
|
||||||
|
-- | Return all modifications to this schedule which do not
|
||||||
|
-- introduce extra pre-emptions.
|
||||||
|
others ((Start i, alts, _):ds) = [[a] | a <- alts] ++ [Start i : o | o <- others ds]
|
||||||
|
others ((SwitchTo i, alts, _):ds) = [[a] | a <- alts] ++ [SwitchTo i : o | o <- others ds]
|
||||||
|
others ((d, _, _):ds) = [d : o | o <- others ds]
|
||||||
|
others [] = []
|
||||||
|
|
||||||
|
-- | Return all modifications to this schedule which do introduce
|
||||||
|
-- an extra pre-emption. Only introduce pre-emptions around CVar
|
||||||
|
-- actions.
|
||||||
|
next ((Continue, alts, Put _):ds) = [[n] | n <- alts] ++ [Continue : n | n <- next ds]
|
||||||
|
next ((Continue, alts, BlockedPut):ds) = [[n] | n <- alts] ++ [Continue : n | n <- next ds]
|
||||||
|
next ((Continue, alts, TryPut _ _):ds) = [[n] | n <- alts] ++ [Continue : n | n <- next ds]
|
||||||
|
next ((Continue, alts, Read):ds) = [[n] | n <- alts] ++ [Continue : n | n <- next ds]
|
||||||
|
next ((Continue, alts, BlockedRead):ds) = [[n] | n <- alts] ++ [Continue : n | n <- next ds]
|
||||||
|
next ((Continue, alts, Take _):ds) = [[n] | n <- alts] ++ [Continue : n | n <- next ds]
|
||||||
|
next ((Continue, alts, BlockedTake):ds) = [[n] | n <- alts] ++ [Continue : n | n <- next ds]
|
||||||
|
next ((Continue, alts, TryTake _ _):ds) = [[n] | n <- alts] ++ [Continue : n | n <- next ds]
|
||||||
|
next ((d, _, _):ds) = [d : n | n <- next ds]
|
||||||
|
next [] = []
|
||||||
|
|
||||||
|
-- | Check the pre-emption count of some scheduling decisions.
|
||||||
|
preEmpCount :: [Decision] -> Int
|
||||||
|
preEmpCount (SwitchTo _:ss) = 1 + preEmpCount ss
|
||||||
|
preEmpCount (_:ss) = preEmpCount ss
|
||||||
|
preEmpCount [] = 0
|
||||||
|
|
||||||
|
-- * Utils
|
||||||
|
|
||||||
-- | Convert a 'Scheduler' to an 'SCTScheduler' by recording the
|
-- | Convert a 'Scheduler' to an 'SCTScheduler' by recording the
|
||||||
-- trace.
|
-- trace.
|
||||||
toSCT :: Scheduler s -> SCTScheduler s
|
toSCT :: Scheduler s -> SCTScheduler s
|
||||||
@ -182,3 +309,22 @@ showTrace = trace "" 0 . map fst where
|
|||||||
trace prefix num [] = thread prefix num
|
trace prefix num [] = thread prefix num
|
||||||
|
|
||||||
thread prefix num = prefix ++ replicate num '-'
|
thread prefix num = prefix ++ replicate num '-'
|
||||||
|
|
||||||
|
-- | Zip a list of 'SchedTrace's and a 'Trace' together into an
|
||||||
|
-- 'SCTTrace'.
|
||||||
|
scttrace :: SchedTrace -> Trace -> SCTTrace
|
||||||
|
scttrace = zipWith $ \(d, alts) (_, act) -> (d, alts, act)
|
||||||
|
|
||||||
|
-- | O(nlogn) nub, <https://github.com/nh2/haskell-ordnub>
|
||||||
|
ordNub :: Ord a => [a] -> [a]
|
||||||
|
ordNub = go Set.empty where
|
||||||
|
go _ [] = []
|
||||||
|
go s (x:xs)
|
||||||
|
| x `Set.member` s = go s xs
|
||||||
|
| otherwise = x : go (Set.insert x s) xs
|
||||||
|
|
||||||
|
-- | Check if a list of decisions matches an initial portion of a trace.
|
||||||
|
(~=) :: [Decision] -> SCTTrace -> Bool
|
||||||
|
(d:ds) ~= ((t,_,_):ts) = d == t && ds ~= ts
|
||||||
|
[] ~= _ = True
|
||||||
|
_ ~= [] = False
|
||||||
|
@ -9,18 +9,16 @@ import Tests.Utils
|
|||||||
-- | List of all tests
|
-- | List of all tests
|
||||||
testCases :: [Test]
|
testCases :: [Test]
|
||||||
testCases =
|
testCases =
|
||||||
[ Test "Simple 2-Deadlock" $ testNot "No deadlocks found!" $ testDeadlockFree 100 simple2Deadlock
|
[ Test "Simple 2-Deadlock" $ testNot "No deadlocks found!" $ testDeadlockFree 1 simple2Deadlock
|
||||||
, Test "2 Philosophers" $ testNot "No deadlocks found!" $ testDeadlockFree 100 $ philosophers 2
|
, Test "2 Philosophers" $ testNot "No deadlocks found!" $ testDeadlockFree 1 $ philosophers 2
|
||||||
, Test "3 Philosophers" $ testNot "No deadlocks found!" $ testDeadlockFree 100 $ philosophers 3
|
, Test "3 Philosophers" $ testNot "No deadlocks found!" $ testDeadlockFree 1 $ philosophers 3
|
||||||
--Random scheduling isn't good enough for these, without increasing
|
, Test "4 Philosophers" $ testNot "No deadlocks found!" $ testDeadlockFree 1 $ philosophers 4
|
||||||
--the runs.
|
, Test "5 Philosophers" $ testNot "No deadlocks found!" $ testDeadlockFree 1 $ philosophers 5
|
||||||
--, Test "4 Philosophers" $ testNot "No deadlocks found!" $ testDeadlockFree 100 $ philosophers 4
|
--, Test "100 Philosophers" $ testNot "No deadlocks found!" $ testDeadlockFree 0 $ philosophers 100
|
||||||
--, Test "5 Philosophers" $ testNot "No deadlocks found!" $ testDeadlockFree 100 $ philosophers 5
|
, Test "Threshold Value" $ testNot "All values equal!" $ testAlwaysSame 1 thresholdValue
|
||||||
--, Test "100 Philosophers" $ testNot "No deadlocks found!" $ testDeadlockFree 100 $ philosophers 100
|
, Test "Forgotten Unlock" $ testDeadlocks 1 forgottenUnlock
|
||||||
, Test "Threshold Value" $ testNot "All values equal!" $ testAlwaysSame 100 thresholdValue
|
, Test "Simple 2-Race" $ testNot "All values equal!" $ testAlwaysSame 1 simple2Race
|
||||||
, Test "Forgotten Unlock" $ testDeadlocks 100 forgottenUnlock
|
, Test "Racey Stack" $ testNot "All values equal!" $ testAlwaysSame 1 raceyStack
|
||||||
, Test "Simple 2-Race" $ testNot "All values equal!" $ testAlwaysSame 100 simple2Race
|
|
||||||
, Test "Racey Stack" $ testNot "All values equal!" $ testAlwaysSame 100 raceyStack
|
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | Should deadlock on a minority of schedules.
|
-- | Should deadlock on a minority of schedules.
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
module Tests.Utils where
|
module Tests.Utils where
|
||||||
|
|
||||||
import Control.Monad.Conc.Fixed (Conc)
|
import Control.Monad.Conc.Fixed (Conc)
|
||||||
import Control.Monad.Conc.SCT (runSCT, sctRandom)
|
import Control.Monad.Conc.SCT (sctPreBound)
|
||||||
import Data.List (group, sort)
|
import Data.List (group, sort)
|
||||||
import Data.Maybe (isJust, isNothing)
|
import Data.Maybe (isJust, isNothing)
|
||||||
import System.Random (mkStdGen)
|
import System.Random (mkStdGen)
|
||||||
@ -14,7 +14,7 @@ data Result = Pass | Fail String | Error String
|
|||||||
-- | Test that a predicate holds over the results of a concurrent
|
-- | Test that a predicate holds over the results of a concurrent
|
||||||
-- computation.
|
-- computation.
|
||||||
testPred :: ([Maybe a] -> Result) -> Int -> (forall t. Conc t a) -> Result
|
testPred :: ([Maybe a] -> Result) -> Int -> (forall t. Conc t a) -> Result
|
||||||
testPred predicate num conc = predicate . map fst $ runSCT sctRandom (mkStdGen 0) num conc
|
testPred predicate num conc = predicate . map fst $ sctPreBound num conc
|
||||||
|
|
||||||
-- | Test that a concurrent computation is free of deadlocks.
|
-- | Test that a concurrent computation is free of deadlocks.
|
||||||
testDeadlockFree :: Int -> (forall t. Conc t a) -> Result
|
testDeadlockFree :: Int -> (forall t. Conc t a) -> Result
|
||||||
|
Loading…
Reference in New Issue
Block a user