Hello community, here is the log from the commit of package ghc-dpor for openSUSE:Factory checked in at 2017-04-11 09:37:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ghc-dpor (Old) and /work/SRC/openSUSE:Factory/.ghc-dpor.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ghc-dpor" Tue Apr 11 09:37:37 2017 rev:2 rq:483922 version:0.2.0.0 Changes: -------- --- /work/SRC/openSUSE:Factory/ghc-dpor/ghc-dpor.changes 2017-03-24 01:54:49.627884853 +0100 +++ /work/SRC/openSUSE:Factory/.ghc-dpor.new/ghc-dpor.changes 2017-04-11 09:37:40.248584807 +0200 @@ -1,0 +2,5 @@ +Thu Sep 15 06:34:19 UTC 2016 - [email protected] + +- Update to version 0.2.0.0 revision 0 with cabal2obs. + +------------------------------------------------------------------- Old: ---- dpor-0.1.0.1.tar.gz New: ---- dpor-0.2.0.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ghc-dpor.spec ++++++ --- /var/tmp/diff_new_pack.8k97xS/_old 2017-04-11 09:37:40.752513620 +0200 +++ /var/tmp/diff_new_pack.8k97xS/_new 2017-04-11 09:37:40.756513055 +0200 @@ -1,7 +1,7 @@ # # spec file for package ghc-dpor # -# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,22 +18,20 @@ %global pkg_name dpor Name: ghc-%{pkg_name} -Version: 0.1.0.1 +Version: 0.2.0.0 Release: 0 Summary: A generic implementation of dynamic partial-order reduction (DPOR) for testing arbitrary models of concurrency License: MIT -Group: System/Libraries +Group: Development/Languages/Other Url: https://hackage.haskell.org/package/%{pkg_name} Source0: https://hackage.haskell.org/package/%{pkg_name}-%{version}/%{pkg_name}-%{version}.tar.gz BuildRequires: ghc-Cabal-devel -# Begin cabal-rpm deps: BuildRequires: ghc-containers-devel BuildRequires: ghc-deepseq-devel BuildRequires: ghc-random-devel BuildRequires: ghc-rpm-macros BuildRequires: ghc-semigroups-devel BuildRoot: %{_tmppath}/%{name}-%{version}-build -# End cabal-rpm deps %description We can characterise the state of a concurrent computation by considering the @@ -78,15 +76,12 @@ %prep %setup -q -n %{pkg_name}-%{version} - %build %ghc_lib_build - %install %ghc_lib_install - %post devel %ghc_pkg_recache ++++++ dpor-0.1.0.1.tar.gz -> dpor-0.2.0.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dpor-0.1.0.1/Test/DPOR/Internal.hs new/dpor-0.2.0.0/Test/DPOR/Internal.hs --- old/dpor-0.1.0.1/Test/DPOR/Internal.hs 2016-05-26 13:22:12.000000000 +0200 +++ new/dpor-0.2.0.0/Test/DPOR/Internal.hs 2016-06-06 21:04:03.000000000 +0200 @@ -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 <[email protected]> +-- 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,38 +117,50 @@ -- 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 - - 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) = (\(ts,c,slp) -> (tid:ts,c,slp)) <$> go tid 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 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 + -> 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 + + 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 + + in if null prefixes + then [] + else case partition (\(t:_,_,_) -> predicate t) sorted of + ([], []) -> err "findSchedulePrefix" "empty prefix list!" + ([], choices) -> choices + (choices, _) -> choices + + go' (tid, dpor) = (\(ts,c,slp) -> (tid:ts,c,slp)) <$> go tid 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 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, @@ -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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dpor-0.1.0.1/Test/DPOR/Random.hs new/dpor-0.2.0.0/Test/DPOR/Random.hs --- old/dpor-0.1.0.1/Test/DPOR/Random.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/dpor-0.2.0.0/Test/DPOR/Random.hs 2016-06-06 21:04:03.000000000 +0200 @@ -0,0 +1,204 @@ +-- | +-- Module : Test.DPOR.Random +-- Copyright : (c) 2016 Michael Walker +-- License : MIT +-- Maintainer : Michael Walker <[email protected]> +-- 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dpor-0.1.0.1/Test/DPOR/Schedule.hs new/dpor-0.2.0.0/Test/DPOR/Schedule.hs --- old/dpor-0.1.0.1/Test/DPOR/Schedule.hs 2016-05-26 13:22:18.000000000 +0200 +++ new/dpor-0.2.0.0/Test/DPOR/Schedule.hs 2016-06-06 21:04:03.000000000 +0200 @@ -1,4 +1,12 @@ --- | Scheduling for concurrent computations. +-- | +-- Module : Test.DPOR.Schedule +-- Copyright : (c) 2016 Michael Walker +-- License : MIT +-- Maintainer : Michael Walker <[email protected]> +-- Stability : experimental +-- Portability : portable +-- +-- Scheduling for concurrent computations. module Test.DPOR.Schedule ( -- * Scheduling Scheduler diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dpor-0.1.0.1/Test/DPOR.hs new/dpor-0.2.0.0/Test/DPOR.hs --- old/dpor-0.1.0.1/Test/DPOR.hs 2016-05-26 13:22:16.000000000 +0200 +++ new/dpor-0.2.0.0/Test/DPOR.hs 2016-06-06 21:04:03.000000000 +0200 @@ -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 <[email protected]> +-- 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 @@ -298,10 +289,6 @@ -> 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dpor-0.1.0.1/dpor.cabal new/dpor-0.2.0.0/dpor.cabal --- old/dpor-0.1.0.1/dpor.cabal 2016-05-26 13:30:01.000000000 +0200 +++ new/dpor-0.2.0.0/dpor.cabal 2016-06-06 21:04:03.000000000 +0200 @@ -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:
