Skip to content

Commit 0729018

Browse files
committed
io-sim-por: added exploreSimTraceST
Unlike `exploreSimTrace` it allows to run an `ST` action in the context of each schedule, and thus allows to share state between schedules. For this reason it evaluates each schedule sequentially, while `exploreSimTrace` evaluates each schedule in parallel. This patch also makes the API more uniform by exposing: * `exploreSimTrace` & `exploreSimTraceST` * `controlSimTrace` & `controlSimTraceST` This patch also removes parallel evaluation of different schedules. Furthermore the tests are updated and they no longer use `unsafePerformIO` as well. The `traceCounter` property was removed as it wasn't used and doesn't make much sense, since `IOSimPOR` is forcing to evaluate each schedule only once by using a cache.
1 parent 96d0f3d commit 0729018

File tree

4 files changed

+99
-147
lines changed

4 files changed

+99
-147
lines changed

io-sim/io-sim.cabal

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ library
8080
containers,
8181
deepseq,
8282
nothunks,
83-
parallel,
8483
psqueues >=0.2 && <0.3,
8584
strict-stm ^>=1.2,
8685
si-timers ^>=1.2,
@@ -108,7 +107,6 @@ test-suite test
108107
containers,
109108
io-classes,
110109
io-sim,
111-
parallel,
112110
QuickCheck,
113111
si-timers,
114112
strict-stm,
@@ -118,7 +116,7 @@ test-suite test
118116
time
119117
ghc-options: -fno-ignore-asserts
120118
-rtsopts
121-
-threaded
119+
-- -threaded
122120

123121
benchmark bench
124122
import: warnings

io-sim/src/Control/Monad/IOSim.hs

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ module Control.Monad.IOSim
2323
-- ** Explore races using /IOSimPOR/
2424
-- $iosimpor
2525
, exploreSimTrace
26+
, exploreSimTraceST
2627
, controlSimTrace
28+
, controlSimTraceST
2729
, ScheduleMod (..)
2830
, ScheduleControl (..)
2931
-- *** Exploration options
@@ -171,6 +173,10 @@ selectTraceRaces = go
171173
-- unsafe, of course, since that function may return different results
172174
-- at different times.
173175

176+
-- | Detach discovered races. This is written in `ST` monad to support
177+
-- simulations which do not terminate, in which case we will only detach races
178+
-- up to the point we evaluated the simulation.
179+
--
174180
detachTraceRacesST :: forall a s. SimTrace a -> ST s (ST s [ScheduleControl], SimTrace a)
175181
detachTraceRacesST trace0 = do
176182
races <- newSTRef []
@@ -191,6 +197,15 @@ detachTraceRacesST trace0 = do
191197
trace <- go trace0
192198
return (readRaces, trace)
193199

200+
201+
-- | Like `detachTraceRacesST`, but it doesn't expose discovered races.
202+
--
203+
detachTraceRaces :: forall a. SimTrace a -> SimTrace a
204+
detachTraceRaces = Trace.filter (\a -> case a of
205+
SimPOREvent { seType = EventRaces {} } -> False
206+
SimRacesFound {} -> False
207+
_ -> True)
208+
194209
-- | Select all the traced values matching the expected type. This relies on
195210
-- the sim's dynamic trace facility.
196211
--
@@ -489,6 +504,8 @@ runSimTrace mainAction = runST (runSimTraceST mainAction)
489504
-- On property failure it will show the failing schedule (`ScheduleControl`)
490505
-- which can be plugged to `controlSimTrace`.
491506
--
507+
-- Note: `exploreSimTrace` evaluates each schedule in parallel (using `par`).
508+
--
492509
exploreSimTrace
493510
:: forall a test. Testable test
494511
=> (ExplorationOptions -> ExplorationOptions)
@@ -499,34 +516,51 @@ exploreSimTrace
499516
-- ^ a callback which receives the previous trace (e.g. before reverting
500517
-- a race condition) and current trace
501518
-> Property
502-
exploreSimTrace optsf mainAction k =
503-
case explorationReplay opts of
504-
Nothing ->
505-
case runST (do cacheRef <- createCacheST
506-
prop <- explore cacheRef (explorationScheduleBound opts) (explorationBranching opts) ControlDefault Nothing
507-
size <- cacheSizeST cacheRef
508-
return (prop, size)
509-
) of
510-
(prop, !size) -> tabulate "Modified schedules explored" [bucket size] prop
511-
512-
Just control ->
513-
replaySimTrace opts mainAction control (k Nothing)
519+
exploreSimTrace optsf main k =
520+
runST (exploreSimTraceST optsf main (\a b -> pure (k a b)))
521+
514522

523+
-- | An 'ST' version of 'exploreSimTrace'. The callback also receives
524+
-- 'ScheduleControl'. This is mostly useful for testing /IOSimPOR/ itself.
525+
--
526+
-- Note: `exploreSimTraceST` evaluates each schedule sequentially.
527+
--
528+
exploreSimTraceST
529+
:: forall s a test. Testable test
530+
=> (ExplorationOptions -> ExplorationOptions)
531+
-> (forall s. IOSim s a)
532+
-> (Maybe (SimTrace a) -> SimTrace a -> ST s test)
533+
-> ST s Property
534+
exploreSimTraceST optsf main k =
535+
case explorationReplay opts of
536+
Just control -> do
537+
trace <- controlSimTraceST (explorationStepTimelimit opts) control main
538+
counterexample ("Schedule control: " ++ show control)
539+
<$> k Nothing trace
540+
Nothing -> do
541+
cacheRef <- createCacheST
542+
prop <- go cacheRef (explorationScheduleBound opts)
543+
(explorationBranching opts)
544+
ControlDefault Nothing
545+
!size <- cacheSizeST cacheRef
546+
return $ tabulate "Modified schedules explored" [bucket size] prop
515547
where
516548
opts = optsf stdExplorationOptions
517549

518-
explore :: forall s.
519-
STRef s (Set ScheduleControl)
520-
-> Int -- schedule bound
521-
-> Int -- branching factor
522-
-> ScheduleControl -> Maybe (SimTrace a) -> ST s Property
523-
explore cacheRef n m control passingTrace = do
524-
traceWithRaces <- IOSimPOR.controlSimTraceST (explorationStepTimelimit opts) control mainAction
550+
go :: STRef s (Set ScheduleControl)
551+
-> Int -- schedule bound
552+
-> Int -- branching factor
553+
-> ScheduleControl
554+
-> Maybe (SimTrace a)
555+
-> ST s Property
556+
go cacheRef n m control passingTrace = do
557+
traceWithRaces <- IOSimPOR.controlSimTraceST (explorationStepTimelimit opts) control main
525558
(readRaces, trace0) <- detachTraceRacesST traceWithRaces
526559
(readSleeperST, trace) <- compareTracesST passingTrace trace0
527560
conjoinNoCatchST
528561
[ do sleeper <- readSleeperST
529562
() <- traceDebugLog (explorationDebugLevel opts) traceWithRaces
563+
prop <- k passingTrace trace
530564
return $ counterexample ("Schedule control: " ++ show control)
531565
$ counterexample
532566
(case sleeper of
@@ -538,7 +572,7 @@ exploreSimTrace optsf mainAction k =
538572
"\n until after:\n" ++
539573
unlines (map ((" "++).showThread) $ Set.toList racing)
540574
)
541-
$ k passingTrace trace
575+
prop
542576
, do let limit = (n+m-1) `div` m
543577
-- To ensure the set of schedules explored is deterministic, we
544578
-- filter out cached ones *after* selecting the children of this
@@ -550,8 +584,9 @@ exploreSimTrace optsf mainAction k =
550584
-- tabulate "Races explored" (map show races) $
551585
tabulate "Branching factor" [bucket branching]
552586
. tabulate "Race reversals per schedule" [bucket (raceReversals control)]
553-
<$> conjoinParST
554-
[ explore cacheRef n' ((m-1) `max` 1) r (Just trace0)
587+
. conjoin
588+
<$> sequence
589+
[ go cacheRef n' ((m-1) `max` 1) r (Just trace0)
555590
| (r,n') <- zip races (divide (n-branching) branching) ]
556591
]
557592

@@ -609,28 +644,6 @@ traceDebugLog _ trace = Debug.traceM $ "Simulation trace with discovered schedul
609644
++ Trace.ppTrace show (ppSimEvent 0 0 0) (void `first` trace)
610645

611646

612-
613-
-- | A specialised version of `controlSimTrace'.
614-
--
615-
-- An internal function.
616-
--
617-
replaySimTrace :: forall a test. (Testable test)
618-
=> ExplorationOptions
619-
-- ^ race exploration options
620-
-> (forall s. IOSim s a)
621-
-> ScheduleControl
622-
-- ^ a schedule control to reproduce
623-
-> (SimTrace a -> test)
624-
-- ^ a callback which receives the simulation trace. The trace
625-
-- will not contain any race events
626-
-> Property
627-
replaySimTrace opts mainAction control k =
628-
let trace = runST $ do
629-
(_readRaces, trace) <- IOSimPOR.controlSimTraceST (explorationStepTimelimit opts) control mainAction
630-
>>= detachTraceRacesST
631-
return (ignoreRaces trace)
632-
in property (k trace)
633-
634647
-- | Run a simulation using a given schedule. This is useful to reproduce
635648
-- failing cases without exploring the races.
636649
--
@@ -650,7 +663,7 @@ controlSimTrace limit control main =
650663

651664
controlSimTraceST :: Maybe Int -> ScheduleControl -> IOSim s a -> ST s (SimTrace a)
652665
controlSimTraceST limit control main =
653-
ignoreRaces <$> IOSimPOR.controlSimTraceST limit control main
666+
detachTraceRaces <$> IOSimPOR.controlSimTraceST limit control main
654667

655668

656669
--

io-sim/src/Control/Monad/IOSimPOR/QuickCheckUtils.hs

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,10 @@
44

55
module Control.Monad.IOSimPOR.QuickCheckUtils where
66

7-
import Control.Parallel
87
import Control.Monad.ST.Lazy
9-
import Control.Monad.ST.Lazy.Unsafe (unsafeInterleaveST)
108
import Test.QuickCheck.Gen
119
import Test.QuickCheck.Property
1210

13-
-- note: it evaluates each `ST` action in parallel.
14-
conjoinParST :: TestableNoCatch prop => [ST s prop] -> ST s Property
15-
conjoinParST sts = do
16-
ps <- sequence (unsafeInterleaveST <$> sts)
17-
return $ conjoinPar ps
18-
19-
-- Take the conjunction of several properties, in parallel This is a
20-
-- modification of code from Test.QuickCheck.Property, to run non-IO
21-
-- properties in parallel. It also takes care NOT to label its result
22-
-- as an IO property (using IORose), unless one of its arguments is
23-
-- itself an IO property. This is needed to permit parallel testing.
24-
conjoinPar :: TestableNoCatch prop => [prop] -> Property
25-
conjoinPar = conjoinSpeculate speculate
26-
where
27-
-- speculation tries to evaluate each Rose tree in parallel, to WHNF
28-
-- This will not perform any IO, but should evaluate non-IO properties
29-
-- completely.
30-
speculate [] = []
31-
speculate (rose:roses) = roses' `par` rose' `pseq` (rose':roses')
32-
where rose' = case rose of
33-
MkRose result _ -> (case ok result of { Nothing -> (); Just !_ -> (); })
34-
`pseq` rose
35-
IORose _ -> rose
36-
roses' = speculate roses
37-
3811
-- We also need a version of conjoin that is sequential, but does not
3912
-- label its result as an IO property unless one of its arguments
4013
-- is. Consequently it does not catch exceptions in its arguments.
@@ -82,16 +55,6 @@ conjoinSpeculate spec ps =
8255
classes = classes result ++ classes r,
8356
tables = tables result ++ tables r }
8457

85-
-- |&&| is a replacement for .&&. that evaluates its arguments in
86-
-- parallel. |&&| does NOT label its result as an IO property, unless
87-
-- one of its arguments is--which .&&. does. This means that using
88-
-- .&&. inside an argument to conjoinPar limits parallelism, while
89-
-- |&&| does not.
90-
91-
infixr 1 |&&|
92-
93-
(|&&|) :: TestableNoCatch prop => prop -> prop -> Property
94-
p |&&| q = conjoinPar [p, q]
9558

9659
-- .&&| is a sequential, but parallelism-friendly version of .&&., that
9760
-- tests its arguments in sequence, but does not label its result as

0 commit comments

Comments
 (0)