packages feed

dpor 0.1.0.1 → 0.2.0.0

raw patch · 5 files changed

+402/−122 lines, 5 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Test.DPOR.Internal: instance (GHC.Show.Show s, GHC.Show.Show lookahead, GHC.Show.Show action, GHC.Show.Show tid) => GHC.Show.Show (Test.DPOR.Internal.SchedState tid action lookahead s)
- Test.DPOR.Internal: instance (GHC.Show.Show state, GHC.Show.Show lookahead, GHC.Show.Show action, GHC.Show.Show tid) => GHC.Show.Show (Test.DPOR.Internal.BacktrackStep tid action lookahead state)
+ Test.DPOR.Internal: [schedBoundKill] :: SchedState tid action lookahead s -> Bool
+ Test.DPOR.Internal: backtrackAt :: Ord tid => (BacktrackStep tid action lookahead s -> Bool) -> Bool -> BacktrackFunc tid action lookahead s
+ Test.DPOR.Internal: instance (GHC.Show.Show tid, GHC.Show.Show action, GHC.Show.Show lookahead, GHC.Show.Show s) => GHC.Show.Show (Test.DPOR.Internal.SchedState tid action lookahead s)
+ Test.DPOR.Internal: instance (GHC.Show.Show tid, GHC.Show.Show action, GHC.Show.Show lookahead, GHC.Show.Show state) => GHC.Show.Show (Test.DPOR.Internal.BacktrackStep tid action lookahead state)
+ Test.DPOR.Internal: trueBound :: BoundFunc tid action lookahead
+ Test.DPOR.Random: boundedRandom :: (Ord tid, NFData tid, NFData action, NFData lookahead, Monad m, RandomGen g) => (action -> Bool) -> (lookahead -> Bool) -> tid -> Maybe (BoundFunc tid action lookahead) -> (DPORScheduler tid action lookahead () -> SchedState tid action lookahead () -> m (a, SchedState tid action lookahead (), Trace tid action lookahead)) -> g -> Int -> m [(a, Trace tid action lookahead)]
+ Test.DPOR.Random: randomDPOR :: (Ord tid, NFData tid, NFData action, NFData lookahead, NFData s, Monad m, RandomGen g) => (action -> Bool) -> (lookahead -> Bool) -> s -> (s -> (tid, action) -> s) -> (s -> (tid, action) -> (tid, action) -> Bool) -> (s -> (tid, action) -> (tid, lookahead) -> Bool) -> (s -> (tid, lookahead) -> NonEmpty tid -> Bool) -> tid -> (tid -> Bool) -> BoundFunc tid action lookahead -> BacktrackFunc tid action lookahead s -> (DPOR tid action -> DPOR tid action) -> (DPORScheduler tid action lookahead s -> SchedState tid action lookahead s -> m (a, SchedState tid action lookahead s, Trace tid action lookahead)) -> g -> Int -> m [(a, Trace tid action lookahead)]
- Test.DPOR: dpor :: (Ord tid, NFData tid, NFData action, NFData lookahead, NFData s, Monad m) => (action -> Bool) -> (lookahead -> Bool) -> s -> (s -> action -> s) -> (s -> (tid, action) -> (tid, action) -> Bool) -> (s -> (tid, action) -> (tid, lookahead) -> Bool) -> tid -> (tid -> Bool) -> BoundFunc tid action lookahead -> BacktrackFunc tid action lookahead s -> (DPOR tid action -> DPOR tid action) -> (DPORScheduler tid action lookahead s -> SchedState tid action lookahead s -> m (a, SchedState tid action lookahead s, Trace tid action lookahead)) -> m [(a, Trace tid action lookahead)]
+ Test.DPOR: dpor :: (Ord tid, NFData tid, NFData action, NFData lookahead, NFData s, Monad m) => (action -> Bool) -> (lookahead -> Bool) -> s -> (s -> (tid, action) -> s) -> (s -> (tid, action) -> (tid, action) -> Bool) -> (s -> (tid, action) -> (tid, lookahead) -> Bool) -> (s -> (tid, lookahead) -> NonEmpty tid -> Bool) -> tid -> (tid -> Bool) -> BoundFunc tid action lookahead -> BacktrackFunc tid action lookahead s -> (DPOR tid action -> DPOR tid action) -> (DPORScheduler tid action lookahead s -> SchedState tid action lookahead s -> m (a, SchedState tid action lookahead s, Trace tid action lookahead)) -> m [(a, Trace tid action lookahead)]
- Test.DPOR: simpleDPOR :: (Ord tid, NFData tid, NFData action, NFData lookahead, Monad m) => (action -> Bool) -> (lookahead -> Bool) -> ((tid, action) -> (tid, action) -> Bool) -> ((tid, action) -> (tid, lookahead) -> Bool) -> tid -> BoundFunc tid action lookahead -> BacktrackFunc tid action lookahead () -> (DPORScheduler tid action lookahead () -> SchedState tid action lookahead () -> m (a, SchedState tid action lookahead (), Trace tid action lookahead)) -> m [(a, Trace tid action lookahead)]
+ Test.DPOR: simpleDPOR :: (Ord tid, NFData tid, NFData action, NFData lookahead, Monad m) => (action -> Bool) -> (lookahead -> Bool) -> ((tid, action) -> (tid, action) -> Bool) -> ((tid, action) -> (tid, lookahead) -> Bool) -> ((tid, lookahead) -> NonEmpty tid -> Bool) -> tid -> BoundFunc tid action lookahead -> BacktrackFunc tid action lookahead () -> (DPORScheduler tid action lookahead () -> SchedState tid action lookahead () -> m (a, SchedState tid action lookahead (), Trace tid action lookahead)) -> m [(a, Trace tid action lookahead)]
- Test.DPOR.Internal: SchedState :: Map tid action -> [tid] -> Seq (NonEmpty (tid, lookahead), [tid]) -> Bool -> s -> SchedState tid action lookahead s
+ Test.DPOR.Internal: SchedState :: Map tid action -> [tid] -> Seq (NonEmpty (tid, lookahead), [tid]) -> Bool -> Bool -> s -> SchedState tid action lookahead s
- Test.DPOR.Internal: dporSched :: (Ord tid, NFData tid, NFData action, NFData lookahead, NFData s) => (action -> Bool) -> (lookahead -> Bool) -> (s -> (tid, action) -> (tid, action) -> Bool) -> (s -> action -> s) -> BoundFunc tid action lookahead -> DPORScheduler tid action lookahead s
+ Test.DPOR.Internal: dporSched :: (Ord tid, NFData tid, NFData action, NFData lookahead, NFData s) => (action -> Bool) -> (lookahead -> Bool) -> (s -> (tid, action) -> (tid, action) -> Bool) -> (s -> (tid, lookahead) -> NonEmpty tid -> Bool) -> (s -> (tid, action) -> s) -> BoundFunc tid action lookahead -> DPORScheduler tid action lookahead s
- Test.DPOR.Internal: findBacktrackSteps :: Ord tid => s -> (s -> action -> s) -> (s -> (tid, action) -> (tid, lookahead) -> Bool) -> ([BacktrackStep tid action lookahead s] -> Int -> tid -> [BacktrackStep tid action lookahead s]) -> Seq (NonEmpty (tid, lookahead), [tid]) -> Trace tid action lookahead -> [BacktrackStep tid action lookahead s]
+ Test.DPOR.Internal: findBacktrackSteps :: Ord tid => s -> (s -> (tid, action) -> s) -> (s -> (tid, action) -> (tid, lookahead) -> Bool) -> ([BacktrackStep tid action lookahead s] -> Int -> tid -> [BacktrackStep tid action lookahead s]) -> Bool -> Seq (NonEmpty (tid, lookahead), [tid]) -> Trace tid action lookahead -> [BacktrackStep tid action lookahead s]
- Test.DPOR.Internal: findSchedulePrefix :: Ord tid => (tid -> Bool) -> DPOR tid action -> Maybe ([tid], Bool, Map tid action)
+ Test.DPOR.Internal: findSchedulePrefix :: Ord tid => (tid -> Bool) -> (Int -> (Int, g)) -> DPOR tid action -> Maybe ([tid], Bool, Map tid action, g)
- Test.DPOR.Internal: incorporateTrace :: Ord tid => state -> (state -> action -> state) -> (state -> (tid, action) -> (tid, action) -> Bool) -> Bool -> Trace tid action lookahead -> DPOR tid action -> DPOR tid action
+ Test.DPOR.Internal: incorporateTrace :: Ord tid => state -> (state -> (tid, action) -> state) -> (state -> (tid, action) -> (tid, action) -> Bool) -> Bool -> Trace tid action lookahead -> DPOR tid action -> DPOR tid action

Files

Test/DPOR.hs view
@@ -1,6 +1,14 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} --- | Systematic testing of concurrent computations through dynamic+-- |+-- Module      : Test.DPOR+-- Copyright   : (c) 2016 Michael Walker+-- License     : MIT+-- Maintainer  : Michael Walker <mike@barrucadu.co.uk>+-- Stability   : experimental+-- Portability : GeneralizedNewtypeDeriving+--+-- Systematic testing of concurrent computations through dynamic -- partial-order reduction and schedule bounding. module Test.DPOR   ( -- * Bounded dynamic partial-order reduction@@ -78,6 +86,9 @@   , lenBound   , lenBacktrack +  -- * Random approaches+  , module Test.DPOR.Random+   -- * Scheduling & execution traces    -- | The partial-order reduction is driven by incorporating@@ -93,10 +104,11 @@  import Control.DeepSeq (NFData) import Data.List (nub)-import Data.Maybe (isNothing)+import Data.List.NonEmpty (NonEmpty) import qualified Data.Map.Strict as M  import Test.DPOR.Internal+import Test.DPOR.Random import Test.DPOR.Schedule  -------------------------------------------------------------------------------@@ -118,6 +130,14 @@ -- the most specific and (2) will be more pessimistic (due to, -- typically, less information being available when merely looking -- ahead).+--+-- The daemon-termination predicate returns @True@ if the action being+-- looked at will cause the entire computation to terminate,+-- regardless of the other runnable threads (which are passed in the+-- 'NonEmpty' list). Such actions will then be put off for as long as+-- possible. This allows supporting concurrency models with daemon+-- threads without doing something as drastic as imposing a dependency+-- between the program-terminating action and /everything/ else. dpor :: ( Ord    tid        , NFData tid        , NFData action@@ -131,12 +151,14 @@   -- ^ Determine if a thread will yield.   -> s   -- ^ The initial state for backtracking.-  -> (s -> action -> s)+  -> (s -> (tid, action) -> s)   -- ^ The backtracking state step function.   -> (s -> (tid, action) -> (tid, action)    -> Bool)   -- ^ The dependency (1) function.   -> (s -> (tid, action) -> (tid, lookahead) -> Bool)   -- ^ The dependency (2) function.+  -> (s -> (tid, lookahead) -> NonEmpty tid -> Bool)+  -- ^ The daemon-termination predicate.   -> tid   -- ^ The initial thread.   -> (tid -> Bool)@@ -162,6 +184,7 @@      ststep      dependency1      dependency2+     killsDaemons      initialTid      predicate      inBound@@ -175,11 +198,11 @@     -- traces into a list until there are no schedules remaining to     -- try.     go dp = case nextPrefix dp of-      Just (prefix, conservative, sleep) -> do+      Just (prefix, conservative, sleep, ()) -> do         (res, s, trace) <- run scheduler                               (initialSchedState stinit sleep prefix) -        let bpoints = findBacktracks s trace+        let bpoints = findBacktracks (schedBoundKill s) (schedBPoints s) trace         let newDPOR = addTrace conservative trace dp          if schedIgnore s@@ -189,14 +212,13 @@       Nothing -> pure []      -- Find the next schedule prefix.-    nextPrefix = findSchedulePrefix predicate+    nextPrefix = findSchedulePrefix predicate (const (0, ()))      -- The DPOR scheduler.-    scheduler = dporSched didYield willYield dependency1 ststep inBound+    scheduler = dporSched didYield willYield dependency1 killsDaemons ststep inBound      -- Find the new backtracking steps.-    findBacktracks = findBacktrackSteps stinit ststep dependency2 backtrack .-                     schedBPoints+    findBacktracks = findBacktrackSteps stinit ststep dependency2 backtrack      -- Incorporate a trace into the DPOR tree.     addTrace = incorporateTrace stinit ststep dependency1@@ -220,6 +242,8 @@   -- ^ The dependency (1) function.   -> ((tid, action) -> (tid, lookahead) -> Bool)   -- ^ The dependency (2) function.+  -> ((tid, lookahead) -> NonEmpty tid -> Bool)+  -- ^ The daemon-termination predicate.   -> tid   -- ^ The initial thread.   -> BoundFunc tid action lookahead@@ -238,6 +262,7 @@            willYield            dependency1            dependency2+           killsDaemons            initialTid            inBound            backtrack@@ -247,47 +272,13 @@          (\_ _ -> ())          (const dependency1)          (const dependency2)+         (const killsDaemons)          initialTid          (const True)          inBound          backtrack          id --- | Add a backtracking point. If the thread isn't runnable, add all--- runnable threads. If the backtracking point is already present,--- don't re-add it UNLESS this would make it conservative.-backtrackAt :: Ord tid-  => (BacktrackStep tid action lookahead s -> Bool)-  -- ^ If this returns @True@, backtrack to all runnable threads,-  -- rather than just the given thread.-  -> Bool-  -- ^ Is this backtracking point conservative? Conservative points-  -- are always explored, whereas non-conservative ones might be-  -- skipped based on future information.-  -> BacktrackFunc tid action lookahead s-backtrackAt toAll conservative bs i tid = go bs i where-  go bx@(b:rest) 0-    -- If the backtracking point is already present, don't re-add it,-    -- UNLESS this would force it to backtrack (it's conservative)-    -- where before it might not.-    | not (toAll b) && tid `M.member` bcktRunnable b =-      let val = M.lookup tid $ bcktBacktracks b-      in if isNothing val || (val == Just False && conservative)-         then b { bcktBacktracks = backtrackTo b } : rest-         else bx--    -- Otherwise just backtrack to everything runnable.-    | otherwise = b { bcktBacktracks = backtrackAll b } : rest--  go (b:rest) n = b : go rest (n-1)-  go [] _ = error "backtrackAt: Ran out of schedule whilst backtracking!"--  -- Backtrack to a single thread-  backtrackTo = M.insert tid conservative . bcktBacktracks--  -- Backtrack to all runnable threads-  backtrackAll = M.map (const conservative) . bcktRunnable- ------------------------------------------------------------------------------- -- Bounds @@ -297,10 +288,6 @@       -> BoundFunc tid action lookahead       -> BoundFunc tid action lookahead (&+&) b1 b2 ts dl = b1 ts dl && b2 ts dl---- | The \"true\" bound, which allows everything.-trueBound :: BoundFunc tid action lookahead-trueBound _ _ = True  ------------------------------------------------------------------------------- -- Preemption bounding
Test/DPOR/Internal.hs view
@@ -1,4 +1,14 @@--- | Internal types and functions for dynamic partial-order reduction.+-- |+-- Module      : Test.DPOR.Internal+-- Copyright   : (c) 2016 Michael Walker+-- License     : MIT+-- Maintainer  : Michael Walker <mike@barrucadu.co.uk>+-- Stability   : experimental+-- Portability : portable+--+-- Internal types and functions for dynamic partial-order+-- reduction. This module is NOT considered to form part of the public+-- interface of this library. module Test.DPOR.Internal where  import Control.DeepSeq (NFData(..), force)@@ -7,7 +17,7 @@ import Data.List.NonEmpty (NonEmpty(..), toList) import Data.Ord (Down(..), comparing) import Data.Map.Strict (Map)-import Data.Maybe (fromJust, mapMaybe)+import Data.Maybe (fromJust, isNothing, mapMaybe) import qualified Data.Map.Strict as M import Data.Set (Set) import qualified Data.Set as S@@ -100,10 +110,6 @@ -- have already been made, terminated by a single decision from the -- to-do set. The intent is to put the system into a new state when -- executed with this initial sequence of scheduling decisions.------ This returns the longest prefix, on the assumption that this will--- lead to lots of backtracking points being identified before--- higher-up decisions are reconsidered, so enlarging the sleep sets. findSchedulePrefix :: Ord tid   => (tid -> Bool)   -- ^ Some partitioning function, applied to the to-do decisions. If@@ -111,39 +117,51 @@   -- rather than any which fail it. This allows a very basic way of   -- domain-specific prioritisation between otherwise equal choices,   -- which may be useful in some cases.+  -> (Int -> (Int, g))+  -- ^ List indexing function, used to select which schedule to+  -- return. Takes the length of the list, and returns an index and+  -- some generator state. The index returned MUST be in range!   -> DPOR tid action-  -> Maybe ([tid], Bool, Map tid action)-findSchedulePrefix predicate dpor0 = go (initialDPORThread dpor0) dpor0 where-  go tid dpor =-        -- All the possible prefix traces from this point, with-        -- updated DPOR subtrees if taken from the done list.-    let prefixes = mapMaybe go' (M.toList $ dporDone dpor) ++ here dpor-        -- Sort by number of preemptions, in descending order.-        cmp = Down . preEmps tid dpor . (\(a,_,_) -> a)-        sorted = sortBy (comparing cmp) prefixes+  -> Maybe ([tid], Bool, Map tid action, g)+findSchedulePrefix predicate idx dpor0+  | null allPrefixes = Nothing+  | otherwise = let (i, g)       = idx (length allPrefixes)+                    (ts, c, slp) = allPrefixes !! i+                in Just (ts, c, slp, g)+  where+    allPrefixes = go (initialDPORThread dpor0) dpor0 -    in if null prefixes-       then Nothing-       else case partition (\(t:_,_,_) -> predicate t) sorted of-              (choice:_, _)  -> Just choice-              ([], choice:_) -> Just choice-              ([], []) -> err "findSchedulePrefix" "empty prefix list!" +    go tid dpor =+          -- All the possible prefix traces from this point, with+          -- updated DPOR subtrees if taken from the done list.+      let prefixes = concatMap go' (M.toList $ dporDone dpor) ++ here dpor+          -- Sort by number of preemptions, in descending order.+          cmp = Down . preEmps tid dpor . (\(a,_,_) -> a)+          sorted = sortBy (comparing cmp) prefixes -  go' (tid, dpor) = (\(ts,c,slp) -> (tid:ts,c,slp)) <$> go tid dpor+      in if null prefixes+         then []+         else case partition (\(t:_,_,_) -> predicate t) sorted of+                ([], []) -> err "findSchedulePrefix" "empty prefix list!" +                ([], choices) -> choices+                (choices, _)  -> choices -  -- Prefix traces terminating with a to-do decision at this point.-  here dpor = [([t], c, sleeps dpor) | (t, c) <- M.toList $ dporTodo dpor]+    go' (tid, dpor) = (\(ts,c,slp) -> (tid:ts,c,slp)) <$> go tid dpor -  -- The new sleep set is the union of the sleep set of the node we're-  -- branching from, plus all the decisions we've already explored.-  sleeps dpor = dporSleep dpor `M.union` dporTaken dpor+    -- Prefix traces terminating with a to-do decision at this point.+    here dpor = [([t], c, sleeps dpor) | (t, c) <- M.toList $ dporTodo dpor] -  -- The number of pre-emptive context switches-  preEmps tid dpor (t:ts) =-    let rest = preEmps t (fromJust . M.lookup t $ dporDone dpor) ts-    in  if tid `S.member` dporRunnable dpor then 1 + rest else rest-  preEmps _ _ [] = 0::Int+    -- The new sleep set is the union of the sleep set of the node+    -- we're branching from, plus all the decisions we've already+    -- explored.+    sleeps dpor = dporSleep dpor `M.union` dporTaken dpor +    -- The number of pre-emptive context switches+    preEmps tid dpor (t:ts) =+      let rest = preEmps t (fromJust . M.lookup t $ dporDone dpor) ts+      in  if tid `S.member` dporRunnable dpor then 1 + rest else rest+    preEmps _ _ [] = 0::Int+ -- | One of the outputs of the runner is a @Trace@, which is a log of -- decisions made, all the runnable threads and what they would do, -- and the action a thread took in its step.@@ -155,7 +173,7 @@ incorporateTrace :: Ord tid   => state   -- ^ Initial state-  -> (state -> action -> state)+  -> (state -> (tid, action) -> state)   -- ^ State step function   -> (state -> (tid, action) -> (tid, action) -> Bool)   -- ^ Dependency function@@ -170,7 +188,7 @@ incorporateTrace stinit ststep dependency conservative trace dpor0 = grow stinit (initialDPORThread dpor0) trace dpor0 where   grow state tid trc@((d, _, a):rest) dpor =     let tid'   = tidOf tid d-        state' = ststep state a+        state' = ststep state (tid', a)     in case M.lookup tid' (dporDone dpor) of          Just dpor' ->            let done = M.insert tid' (grow state' tid' rest dpor') (dporDone dpor)@@ -187,7 +205,7 @@    -- Construct a new subtree corresponding to a trace suffix.   subtree state tid sleep ((_, _, a):rest) =-    let state' = ststep state a+    let state' = ststep state (tid, a)         sleep' = M.filterWithKey (\t a' -> not $ dependency state' (tid, a) (t,a')) sleep     in DPOR         { dporRunnable = S.fromList $ case rest of@@ -221,7 +239,7 @@ findBacktrackSteps :: Ord tid   => s   -- ^ Initial state.-  -> (s -> action -> s)+  -> (s -> (tid, action) -> s)   -- ^ State step function.   -> (s -> (tid, action) -> (tid, lookahead) -> Bool)   -- ^ Dependency function.@@ -230,6 +248,9 @@   -- a thread to backtrack to at a specific point in that list, add   -- the new backtracking points. There will be at least one: this   -- chosen one, but the function may add others.+  -> Bool+  -- ^ Whether the computation was aborted due to no decisions being+  -- in-bounds.   -> Seq (NonEmpty (tid, lookahead), [tid])   -- ^ A sequence of threads at each step: the nonempty list of   -- runnable threads (with lookahead values), and the list of threads@@ -239,9 +260,9 @@   -> Trace tid action lookahead   -- ^ The execution trace.   -> [BacktrackStep tid action lookahead s]-findBacktrackSteps _ _ _ _ bcktrck+findBacktrackSteps _ _ _ _ _ bcktrck   | Sq.null bcktrck = const []-findBacktrackSteps stinit ststep dependency backtrack bcktrck = go stinit S.empty initialThread [] (Sq.viewl bcktrck) where+findBacktrackSteps stinit ststep dependency backtrack boundKill bcktrck = go stinit S.empty initialThread [] (Sq.viewl bcktrck) where   -- Get the initial thread ID   initialThread = case Sq.viewl bcktrck of     (((tid, _):|_, _):<_) -> tid@@ -251,7 +272,7 @@   -- new backtracking points.   go state allThreads tid bs ((e,i):<is) ((d,_,a):ts) =     let tid' = tidOf tid d-        state' = ststep state a+        state' = ststep state (tid', a)         this = BacktrackStep           { bcktThreadid   = tid'           , bcktDecision   = (d, a)@@ -262,7 +283,7 @@         bs' = doBacktrack killsEarly allThreads' (toList e) (bs++[this])         runnable = S.fromList (M.keys $ bcktRunnable this)         allThreads' = allThreads `S.union` runnable-        killsEarly = null ts && any (/=initialThread) runnable+        killsEarly = null ts && boundKill     in go state' allThreads' tid' bs' (Sq.viewl is) ts   go _ _ _ bs _ _ = bs @@ -279,6 +300,9 @@          idxs' u n v = mapMaybe go' where           go' (i, b)+            -- If this is the final action in the trace and the+            -- execution was killed due to nothing being within bounds+            -- (@killsEarly == True@) assume worst-case dependency.             | bcktThreadid b == v && (killsEarly || isDependent b) = Just i             | otherwise = Nothing @@ -325,20 +349,23 @@  -- | The scheduler state data SchedState tid action lookahead s = SchedState-  { schedSleep   :: Map tid action+  { schedSleep     :: Map tid action   -- ^ The sleep set: decisions not to make until something dependent   -- with them happens.-  , schedPrefix  :: [tid]+  , schedPrefix    :: [tid]   -- ^ Decisions still to make-  , schedBPoints :: Seq (NonEmpty (tid, lookahead), [tid])+  , schedBPoints   :: Seq (NonEmpty (tid, lookahead), [tid])   -- ^ Which threads are runnable at each step, and the alternative   -- decisions still to make.-  , schedIgnore  :: Bool+  , schedIgnore    :: Bool   -- ^ Whether to ignore this execution or not: @True@ if the   -- execution is aborted due to all possible decisions being in the   -- sleep set, as then everything in this execution is covered by   -- another.-  , schedDepState :: s+  , schedBoundKill :: Bool+  -- ^ Whether the execution was terminated due to all decisions being+  -- out of bounds.+  , schedDepState  :: s   -- ^ State used by the dependency function to determine when to   -- remove decisions from the sleep set.   } deriving Show@@ -348,11 +375,12 @@          , NFData lookahead          , NFData s          ) => NFData (SchedState tid action lookahead s) where-  rnf s = rnf ( schedSleep    s-              , schedPrefix   s-              , schedBPoints  s-              , schedIgnore   s-              , schedDepState s+  rnf s = rnf ( schedSleep     s+              , schedPrefix    s+              , schedBPoints   s+              , schedIgnore    s+              , schedBoundKill s+              , schedDepState  s               )  -- | Initial scheduler state for a given prefix@@ -364,11 +392,12 @@   -- ^ The schedule prefix.   -> SchedState tid action lookahead s initialSchedState s sleep prefix = SchedState-  { schedSleep    = sleep-  , schedPrefix   = prefix-  , schedBPoints  = Sq.empty-  , schedIgnore   = False-  , schedDepState = s+  { schedSleep     = sleep+  , schedPrefix    = prefix+  , schedBPoints   = Sq.empty+  , schedIgnore    = False+  , schedBoundKill = False+  , schedDepState  = s   }  -- | A bounding function takes the scheduling decisions so far and a@@ -377,6 +406,10 @@ type BoundFunc tid action lookahead   = [(Decision tid, action)] -> (Decision tid, lookahead) -> Bool +-- | The \"true\" bound, which allows everything.+trueBound :: BoundFunc tid action lookahead+trueBound _ _ = True+ -- | A backtracking step is a point in the execution where another -- decision needs to be made, in order to explore interesting new -- schedules. A backtracking /function/ takes the steps identified so@@ -393,6 +426,41 @@   = [BacktrackStep tid action lookahead s] -> Int -> tid   -> [BacktrackStep tid action lookahead s] +-- | Add a backtracking point. If the thread isn't runnable, add all+-- runnable threads. If the backtracking point is already present,+-- don't re-add it UNLESS this would make it conservative.+backtrackAt :: Ord tid+  => (BacktrackStep tid action lookahead s -> Bool)+  -- ^ If this returns @True@, backtrack to all runnable threads,+  -- rather than just the given thread.+  -> Bool+  -- ^ Is this backtracking point conservative? Conservative points+  -- are always explored, whereas non-conservative ones might be+  -- skipped based on future information.+  -> BacktrackFunc tid action lookahead s+backtrackAt toAll conservative bs i tid = go bs i where+  go bx@(b:rest) 0+    -- If the backtracking point is already present, don't re-add it,+    -- UNLESS this would force it to backtrack (it's conservative)+    -- where before it might not.+    | not (toAll b) && tid `M.member` bcktRunnable b =+      let val = M.lookup tid $ bcktBacktracks b+      in if isNothing val || (val == Just False && conservative)+         then b { bcktBacktracks = backtrackTo b } : rest+         else bx++    -- Otherwise just backtrack to everything runnable.+    | otherwise = b { bcktBacktracks = backtrackAll b } : rest++  go (b:rest) n = b : go rest (n-1)+  go [] _ = error "backtrackAt: Ran out of schedule whilst backtracking!"++  -- Backtrack to a single thread+  backtrackTo = M.insert tid conservative . bcktBacktracks++  -- Backtrack to all runnable threads+  backtrackAll = M.map (const conservative) . bcktRunnable+ -- | DPOR scheduler: takes a list of decisions, and maintains a trace -- including the runnable threads, and the alternative choices allowed -- by the bound-specific initialise function.@@ -411,13 +479,15 @@   -- ^ Determine if a thread will yield.   -> (s -> (tid, action) -> (tid, action) -> Bool)   -- ^ Dependency function.-  -> (s -> action -> s)+  -> (s -> (tid, lookahead) -> NonEmpty tid -> Bool)+  -- ^ Daemon-termination predicate.+  -> (s -> (tid, action) -> s)   -- ^ Dependency function's state step function.   -> BoundFunc tid action lookahead   -- ^ Bound function: returns true if that schedule prefix terminated   -- with the lookahead decision fits within the bound.   -> DPORScheduler tid action lookahead s-dporSched didYield willYield dependency ststep inBound trc prior threads s = force schedule where+dporSched didYield willYield dependency killsDaemons ststep inBound trc prior threads s = force schedule where   -- Pick a thread to run.   schedule = case schedPrefix s of     -- If there is a decision available, make it@@ -427,49 +497,59 @@     -- choices, filter out anything in the sleep set, and make one of     -- them arbitrarily (recording the others).     [] ->-      let choices  = initialise+      let choices  = restrictToBound initialise           checkDep t a = case prior of             Just (tid, act) -> dependency (schedDepState s) (tid, act) (t, a)             Nothing -> False           ssleep'  = M.filterWithKey (\t a -> not $ checkDep t a) $ schedSleep s           choices' = filter (`notElem` M.keys ssleep') choices           signore' = not (null choices) && all (`elem` M.keys ssleep') choices+          sbkill'  = not (null initialise) && null choices       in case choices' of             (nextTid:rest) -> (Just nextTid, (nextState rest) { schedSleep = ssleep' })-            [] -> (Nothing, (nextState []) { schedIgnore = signore' })+            [] -> (Nothing, (nextState []) { schedIgnore = signore', schedBoundKill = sbkill' })    -- The next scheduler state   nextState rest = s     { schedBPoints  = schedBPoints s |> (threads, rest)-    , schedDepState = case prior of-        Just (_, act) -> ststep (schedDepState s) act-        Nothing -> schedDepState s+    , schedDepState = nextDepState     }+  nextDepState = let ds = schedDepState s in maybe ds (ststep ds) prior -  -- Pick a new thread to run, which does not exceed the bound. Choose-  -- the current thread if available and it hasn't just yielded,-  -- otherwise add all runnable threads.-  initialise = restrictToBound . yieldsToEnd $ case prior of+  -- Pick a new thread to run, not considering bounds. Choose the+  -- current thread if available and it hasn't just yielded, otherwise+  -- add all runnable threads.+  initialise = tryDaemons . yieldsToEnd $ case prior of     Just (tid, act)-      | didYield act -> map fst (toList threads)-      | any (\(t, _) -> t == tid) threads -> [tid]-    _ -> map fst (toList threads)+      | not (didYield act) && tid `elem` tids -> [tid]+    _ -> tids' +  -- If one of the chosen actions will kill the computation, and there+  -- are daemon threads, try them instead.+  tryDaemons ts+    | any doesKill ts = filter (not . doesKill) tids' ++ filter doesKill ts+    | otherwise       = ts+  doesKill t = killsDaemons nextDepState (t, action t) tids+   -- Restrict the possible decisions to those in the bound.-  restrictToBound = fst . partition (\t -> inBound trc (decision t, action t))+  restrictToBound = filter (\t -> inBound trc (decision t, action t))    -- Move the threads which will immediately yield to the end of the list   yieldsToEnd ts = case partition (willYield . action) ts of     (yields, noyields) -> noyields ++ yields    -- Get the decision that will lead to a thread being scheduled.-  decision = decisionOf (fst <$> prior) (S.fromList $ map fst threads')+  decision = decisionOf (fst <$> prior) (S.fromList tids')    -- Get the action of a thread   action t = fromJust $ lookup t threads' +  -- The runnable thread IDs+  tids = fst <$> threads+   -- The runnable threads as a normal list.   threads' = toList threads+  tids'    = toList tids  ------------------------------------------------------------------------------- -- * Utilities
+ Test/DPOR/Random.hs view
@@ -0,0 +1,204 @@+-- |+-- Module      : Test.DPOR.Random+-- Copyright   : (c) 2016 Michael Walker+-- License     : MIT+-- Maintainer  : Michael Walker <mike@barrucadu.co.uk>+-- Stability   : experimental+-- Portability : portable+--+-- Random and incomplete techniques for when complete testing is+-- infeasible.+module Test.DPOR.Random+  ( -- * Randomness and partial-order reduction+    randomDPOR++  -- * Non-POR techniques++  -- | These algorithms do not make use of partial-order reduction to+  -- systematically prune the search space and search for interesting+  -- interleavings. Instead, the exploration is driven entirely by+  -- random choice, with optional bounds. However, the same schedule+  -- will never be explored twice.+  , boundedRandom+  ) where++import Control.DeepSeq (NFData)+import Data.List.NonEmpty (NonEmpty)+import Data.Maybe (fromMaybe)+import System.Random (RandomGen, randomR)++import Test.DPOR.Internal++-------------------------------------------------------------------------------+-- Randomness and partial-order reduction++-- | Random dynamic partial-order reduction.+--+-- This is the 'dpor' algorithm in "Test.DPOR", however it does not+-- promise to test every distinct schedule: instead, an execution+-- limit is passed in, and a PRNG used to decide which actual+-- schedules to test. Testing terminates when either the execution+-- limit is reached, or when there are no distinct schedules+-- remaining.+--+-- Despite being \"random\", this still uses the normal partial-order+-- reduction and schedule bounding machinery, and so will prune the+-- search space to \"interesting\" cases, and will never try the same+-- schedule twice. Additionally, the thread partitioning function+-- still applies when selecting schedules.+randomDPOR :: ( Ord       tid+             , NFData    tid+             , NFData    action+             , NFData    lookahead+             , NFData    s+             , Monad     m+             , RandomGen g+             )+  => (action    -> Bool)+  -- ^ Determine if a thread yielded.+  -> (lookahead -> Bool)+  -- ^ Determine if a thread will yield.+  -> s+  -- ^ The initial state for backtracking.+  -> (s -> (tid, action) -> s)+  -- ^ The backtracking state step function.+  -> (s -> (tid, action) -> (tid, action)    -> Bool)+  -- ^ The dependency (1) function.+  -> (s -> (tid, action) -> (tid, lookahead) -> Bool)+  -- ^ The dependency (2) function.+  -> (s -> (tid, lookahead) -> NonEmpty tid -> Bool)+  -- ^ The daemon-termination predicate.+  -> tid+  -- ^ The initial thread.+  -> (tid -> Bool)+  -- ^ The thread partitioning function: when choosing what to+  -- execute, prefer threads which return true.+  -> BoundFunc tid action lookahead+  -- ^ The bounding function.+  -> BacktrackFunc tid action lookahead s+  -- ^ The backtracking function. Note that, for some bounding+  -- functions, this will need to add conservative backtracking+  -- points.+  -> (DPOR tid action -> DPOR tid action)+  -- ^ Some post-processing to do after adding the new to-do points.+  -> (DPORScheduler tid action lookahead s+    -> SchedState tid action lookahead s+    -> m (a, SchedState tid action lookahead s, Trace tid action lookahead))+  -- ^ The runner: given the scheduler and state, execute the+  -- computation under that scheduler.+  -> g+  -- ^ Random number generator, used to determine which schedules to+  -- try.+  -> Int+  -- ^ Execution limit, used to abort the execution whilst schedules+  -- still remain.+  -> m [(a, Trace tid action lookahead)]+randomDPOR didYield+           willYield+           stinit+           ststep+           dependency1+           dependency2+           killsDaemons+           initialTid+           predicate+           inBound+           backtrack+           transform+           run+  = go (initialState initialTid)++  where+    -- Repeatedly run the computation gathering all the results and+    -- traces into a list until there are no schedules remaining to+    -- try.+    go _ _ 0 = pure []+    go dp g elim = case nextPrefix g dp of+      Just (prefix, conservative, sleep, g') -> do+        (res, s, trace) <- run scheduler+                              (initialSchedState stinit sleep prefix)++        let bpoints  = findBacktracks (schedBoundKill s) (schedBPoints s) trace+        let newDPOR  = addTrace conservative trace dp+        let newDPOR' = transform (addBacktracks bpoints newDPOR)++        if schedIgnore s+        then go newDPOR g' (elim-1)+        else ((res, trace):) <$> go newDPOR' g' (elim-1)++      Nothing -> pure []++    -- Generate a random value from a range+    gen g hi = randomR (0, hi - 1) g++    -- Find the next schedule prefix.+    nextPrefix = findSchedulePrefix predicate . gen++    -- The DPOR scheduler.+    scheduler = dporSched didYield willYield dependency1 killsDaemons ststep inBound++    -- Find the new backtracking steps.+    findBacktracks = findBacktrackSteps stinit ststep dependency2 backtrack++    -- Incorporate a trace into the DPOR tree.+    addTrace = incorporateTrace stinit ststep dependency1++    -- Incorporate the new backtracking steps into the DPOR tree.+    addBacktracks = incorporateBacktrackSteps inBound++-------------------------------------------------------------------------------+-- Unsystematic techniques++-- | Pure random scheduling. Like 'randomDPOR' but all actions are+-- dependent and the bounds are optional.+boundedRandom :: ( Ord       tid+                , NFData    tid+                , NFData    action+                , NFData    lookahead+                , Monad     m+                , RandomGen g+                )+  => (action    -> Bool)+  -- ^ Determine if a thread yielded.+  -> (lookahead -> Bool)+  -- ^ Determine if a thread will yield.+  -> tid+  -- ^ The initial thread.+  -> Maybe (BoundFunc tid action lookahead)+  -- ^ The bounding function. If no function is provided, 'trueBound'+  -- is used.+  -> (DPORScheduler tid action lookahead ()+    -> SchedState tid action lookahead ()+    -> m (a, SchedState tid action lookahead (), Trace tid action lookahead))+  -- ^ The runner: given the scheduler and state, execute the+  -- computation under that scheduler.+  -> g+  -- ^ Random number generator, used to determine which schedules to+  -- try.+  -> Int+  -- ^ Execution limit, used to abort the execution whilst schedules+  -- still remain.+  -> m [(a, Trace tid action lookahead)]+boundedRandom didYield willYield initialTid inBoundm+  = randomDPOR didYield+               willYield+               stinit+               ststep+               dependency1+               dependency2+               killsDaemons+               initialTid+               predicate+               inBound+               backtrack+               transform+  where+    stinit = ()+    ststep _ _ = ()+    dependency1 _ _ _ = True+    dependency2 _ _ _ = True+    killsDaemons _ _ _ = True+    predicate _ = True+    inBound = fromMaybe trueBound inBoundm+    backtrack = backtrackAt (const False) False+    transform = id
Test/DPOR/Schedule.hs view
@@ -1,4 +1,12 @@--- | Scheduling for concurrent computations.+-- |+-- Module      : Test.DPOR.Schedule+-- Copyright   : (c) 2016 Michael Walker+-- License     : MIT+-- Maintainer  : Michael Walker <mike@barrucadu.co.uk>+-- Stability   : experimental+-- Portability : portable+--+-- Scheduling for concurrent computations. module Test.DPOR.Schedule   ( -- * Scheduling     Scheduler
dpor.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/  name:                dpor-version:             0.1.0.1+version:             0.2.0.0 synopsis:            A generic implementation of dynamic partial-order reduction (DPOR) for testing arbitrary models of concurrency.  description:@@ -55,11 +55,12 @@ source-repository this   type:     git   location: https://github.com/barrucadu/dejafu.git-  tag:      dpor-0.1.0.1+  tag:      dpor-0.2.0.0  library   exposed-modules:     Test.DPOR                      , Test.DPOR.Internal+                     , Test.DPOR.Random                      , Test.DPOR.Schedule   -- other-modules:          -- other-extensions: