Skip to content

Commit 6d62b8b

Browse files
committed
io-sim-por: document, refactor & clean the code
1 parent 9102b32 commit 6d62b8b

File tree

8 files changed

+194
-118
lines changed

8 files changed

+194
-118
lines changed

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

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,22 @@ selectTraceRaces = go
161161
-- unsafe, of course, since that function may return different results
162162
-- at different times.
163163

164-
detachTraceRaces :: SimTrace a -> (() -> [ScheduleControl], SimTrace a)
164+
detachTraceRaces :: forall a. SimTrace a -> (() -> [ScheduleControl], SimTrace a)
165165
detachTraceRaces trace = unsafePerformIO $ do
166166
races <- newIORef []
167-
let readRaces () = concat . reverse . unsafePerformIO $ readIORef races
168-
saveRaces r t = unsafePerformIO $ do
169-
modifyIORef races (r:)
170-
return t
171-
let go (SimTrace a b c d trace) = SimTrace a b c d $ go trace
167+
let readRaces :: () -> [ScheduleControl]
168+
readRaces () = concat . reverse . unsafePerformIO $ readIORef races
169+
170+
saveRaces :: [ScheduleControl] -> x -> x
171+
saveRaces rs t = unsafePerformIO $ modifyIORef races (rs:)
172+
>> return t
173+
174+
go :: SimTrace a -> SimTrace a
175+
go (SimTrace a b c d trace) = SimTrace a b c d $ go trace
172176
go (SimPORTrace a b c d e trace) = SimPORTrace a b c d e $ go trace
173-
go (TraceRacesFound r trace) = saveRaces r $ go trace
177+
go (TraceRacesFound rs trace) = saveRaces rs $ go trace
174178
go t = t
179+
175180
return (readRaces, go trace)
176181

177182
-- | Select all the traced values matching the expected type. This relies on
@@ -475,7 +480,9 @@ exploreSimTrace optsf mainAction k =
475480
where
476481
opts = optsf stdExplorationOptions
477482

478-
explore :: Int -> Int -> ScheduleControl -> Maybe (SimTrace a) -> Property
483+
explore :: Int -- schedule bound
484+
-> Int -- branching factor
485+
-> ScheduleControl -> Maybe (SimTrace a) -> Property
479486
explore n m control passingTrace =
480487

481488
-- ALERT!!! Impure code: readRaces must be called *after* we have
@@ -514,16 +521,15 @@ exploreSimTrace optsf mainAction k =
514521
| (r,n') <- zip races (divide (n-branching) branching) ]
515522

516523
bucket :: Int -> String
517-
bucket n | n<10 = show n
518-
| n>=10 = buck n 1
519-
| otherwise = error "Ord Int is not a total order!" -- GHC made me do it!
524+
bucket n | n<10 = show n
525+
| otherwise = buck n 1
520526
buck n t | n<10 = show (n*t) ++ "-" ++ show ((n+1)*t-1)
521-
| n>=10 = buck (n `div` 10) (t*10)
522-
| otherwise = error "Ord Int is not a total order!" -- GHC made me do it!
527+
| otherwise = buck (n `div` 10) (t*10)
523528

529+
-- divide n into k factors which sums up to n
524530
divide :: Int -> Int -> [Int]
525531
divide n k =
526-
[ n `div` k + if i<n `mod` k then 1 else 0
532+
[ n `div` k + if i < n `mod` k then 1 else 0
527533
| i <- [0..k-1] ]
528534

529535
showThread :: (ThreadId,Maybe ThreadLabel) -> String
@@ -562,7 +568,6 @@ exploreSimTrace optsf mainAction k =
562568
cachedIO m = atomicModifyIORef' cache $ \set ->
563569
(Set.insert m set, Set.member m set)
564570

565-
566571
cacheSizeIO :: () -> IO Int
567572
cacheSizeIO () = Set.size <$> readIORef cache
568573

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ module Control.Monad.IOSim.Types
3434
, setCurrentTime
3535
, unshareClock
3636
, ScheduleControl (..)
37+
, isDefaultSchedule
3738
, ScheduleMod (..)
3839
, ExplorationOptions (..)
3940
, ExplorationSpec
@@ -1123,6 +1124,12 @@ data ScheduleControl = ControlDefault
11231124
-- when 'controlTargets' returns true.
11241125
deriving (Eq, Ord, Show)
11251126

1127+
1128+
isDefaultSchedule :: ScheduleControl -> Bool
1129+
isDefaultSchedule ControlDefault = True
1130+
isDefaultSchedule (ControlFollow [] []) = True
1131+
isDefaultSchedule _ = False
1132+
11261133
-- | A schedule modification inserted at given execution step.
11271134
--
11281135
data ScheduleMod = ScheduleMod{

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

Lines changed: 130 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
{-# LANGUAGE MultiParamTypeClasses #-}
88
{-# LANGUAGE NamedFieldPuns #-}
99
{-# LANGUAGE RankNTypes #-}
10+
-- only used to construct records if its clear to do so
11+
{-# LANGUAGE RecordWildCards #-}
1012
{-# LANGUAGE ScopedTypeVariables #-}
1113
{-# LANGUAGE TypeFamilies #-}
1214

@@ -1641,31 +1643,33 @@ data Step = Step {
16411643
}
16421644
deriving Show
16431645

1644-
-- steps race if they can be reordered with a possibly different outcome
1646+
-- | Check if two steps can be reordered with a possibly different outcome.
1647+
--
16451648
racingSteps :: Step -- ^ an earlier step
16461649
-> Step -- ^ a later step
16471650
-> Bool
16481651
racingSteps s s' =
1649-
stepThreadId s /= stepThreadId s'
1652+
-- is s executed by a racy thread
1653+
isRacyThreadId (stepThreadId s)
1654+
-- steps which belong to the same thread cannot race
1655+
&& stepThreadId s /= stepThreadId s'
1656+
-- if s wakes up s' then s and s' cannot race
16501657
&& not (stepThreadId s' `elem` effectWakeup (stepEffect s))
1651-
&& (stepEffect s `racingEffects` stepEffect s'
1652-
|| throwsTo s s'
1653-
|| throwsTo s' s)
1658+
-- s effects races with s' effects or either one throws to the other
1659+
&& ( stepEffect s `racingEffects` stepEffect s'
1660+
|| throwsTo s s'
1661+
|| throwsTo s' s
1662+
)
16541663
where throwsTo s1 s2 =
16551664
stepThreadId s2 `elem` effectThrows (stepEffect s1)
16561665
&& stepEffect s2 /= mempty
16571666

16581667
currentStep :: Thread s a -> Step
1659-
currentStep Thread { threadId = tid,
1660-
threadStep = tstep,
1661-
threadEffect = teffect,
1662-
threadVClock = vClock
1663-
} =
1664-
Step { stepThreadId = tid,
1665-
stepStep = tstep,
1666-
stepEffect = teffect,
1667-
stepVClock = vClock
1668-
}
1668+
currentStep Thread { threadId = stepThreadId,
1669+
threadStep = stepStep,
1670+
threadEffect = stepEffect,
1671+
threadVClock = stepVClock
1672+
} = Step {..}
16691673

16701674
stepThread :: Thread s a -> Thread s a
16711675
stepThread thread@Thread { threadId = tid,
@@ -1678,16 +1682,16 @@ stepThread thread@Thread { threadId = tid,
16781682

16791683
-- As we run a simulation, we collect info about each previous step
16801684
data StepInfo = StepInfo {
1681-
stepInfoStep :: Step,
1685+
stepInfoStep :: !Step,
16821686
-- Control information when we reached this step
1683-
stepInfoControl :: ScheduleControl,
1687+
stepInfoControl :: !ScheduleControl,
16841688
-- threads that are still concurrent with this step
1685-
stepInfoConcurrent :: Set ThreadId,
1689+
stepInfoConcurrent :: !(Set ThreadId),
16861690
-- steps following this one that did not happen after it
16871691
-- (in reverse order)
1688-
stepInfoNonDep :: [Step],
1692+
stepInfoNonDep :: ![Step],
16891693
-- later steps that race with this one
1690-
stepInfoRaces :: [Step]
1694+
stepInfoRaces :: ![Step]
16911695
}
16921696
deriving Show
16931697

@@ -1707,7 +1711,7 @@ noRaces = Races [] []
17071711

17081712
updateRacesInSimState :: Thread s a -> SimState s a -> Races
17091713
updateRacesInSimState thread SimState{ control, threads, races } =
1710-
traceRaces $
1714+
debugTraceRaces $
17111715
updateRaces step
17121716
(isThreadBlocked thread)
17131717
control
@@ -1726,89 +1730,124 @@ updateRacesInSimState thread SimState{ control, threads, races } =
17261730
-- concurrent set. When this becomes empty, a step can be retired into
17271731
-- the "complete" category, but only if there are some steps racing
17281732
-- with it.
1729-
updateRaces :: Step -> Bool -> ScheduleControl -> Set ThreadId -> Races -> Races
1733+
updateRaces :: Step
1734+
-- ^ executed step
1735+
-> Bool
1736+
-- ^ is the thread blocking
1737+
-> ScheduleControl
1738+
-- ^ schedule control
1739+
-> Set ThreadId
1740+
-- ^ set of runnable threads which are not forked by the step, and
1741+
-- thus can race with it.
1742+
-> Races -> Races
17301743
updateRaces newStep@Step{ stepThreadId = tid, stepEffect = newEffect }
17311744
blocking
17321745
control
1733-
newConcurrent0
1746+
concurrent0
17341747
races@Races{ activeRaces } =
17351748

1736-
let justBlocking :: Bool
1737-
justBlocking = blocking && onlyReadEffect newEffect
1738-
1739-
-- a new step cannot race with any threads that it just woke up
1740-
new :: [StepInfo]
1741-
!new | isNotRacyThreadId tid = [] -- non-racy threads do not race
1742-
| Set.null newConcurrent = [] -- cannot race with anything
1743-
| justBlocking = [] -- no need to defer a blocking transaction
1744-
| otherwise =
1745-
[StepInfo { stepInfoStep = newStep,
1746-
stepInfoControl = control,
1747-
stepInfoConcurrent = newConcurrent,
1748-
stepInfoNonDep = [],
1749-
stepInfoRaces = []
1750-
}]
1749+
let -- A new step to add to the `activeRaces` list.
1750+
newStepInfo :: Maybe StepInfo
1751+
!newStepInfo | isNotRacyThreadId tid = Nothing
1752+
-- non-racy threads do not race
1753+
1754+
| Set.null concurrent = Nothing
1755+
-- cannot race with nothing
1756+
1757+
| isBlocking = Nothing
1758+
-- no need to defer a blocking transaction
1759+
1760+
| otherwise =
1761+
Just $! StepInfo { stepInfoStep = newStep,
1762+
stepInfoControl = control,
1763+
stepInfoConcurrent = concurrent,
1764+
stepInfoNonDep = [],
1765+
stepInfoRaces = []
1766+
}
17511767
where
1752-
newConcurrent :: Set ThreadId
1753-
newConcurrent = foldr Set.delete newConcurrent0 (effectWakeup newEffect)
1768+
concurrent :: Set ThreadId
1769+
concurrent = foldr Set.delete concurrent0 (effectWakeup newEffect)
1770+
1771+
isBlocking :: Bool
1772+
isBlocking = blocking && onlyReadEffect newEffect
1773+
1774+
-- Used to update each `StepInfo` in `activeRaces`.
1775+
updateStepInfo :: StepInfo -> StepInfo
1776+
updateStepInfo stepInfo@StepInfo { stepInfoStep = step,
1777+
stepInfoConcurrent = concurrent,
1778+
stepInfoNonDep,
1779+
stepInfoRaces } =
1780+
-- if this step depends on the previous step, or is not concurrent,
1781+
-- then any threads that it wakes up become non-concurrent also.
1782+
let !lessConcurrent = foldr Set.delete concurrent (effectWakeup newEffect) in
1783+
1784+
if tid `notElem` concurrent
1785+
then stepInfo { stepInfoConcurrent = lessConcurrent }
1786+
1787+
-- The core of IOSimPOR. Detect if `newStep` is racing with any
1788+
-- previous steps and update each `StepInfo`.
1789+
else let theseStepsRace = step `racingSteps` newStep
1790+
-- `step` happened before `newStep` (`newStep` happened after
1791+
-- `step`)
1792+
happensBefore = step `happensBeforeStep` newStep
1793+
1794+
-- We will only record the first race with each thread.
1795+
-- Reversing the first race makes the next race detectable.
1796+
-- Thus we remove a thread from the concurrent set after the
1797+
-- first race.
1798+
!concurrent'
1799+
| happensBefore = Set.delete tid lessConcurrent
1800+
| theseStepsRace = Set.delete tid concurrent
1801+
| otherwise = concurrent
1802+
1803+
!stepInfoNonDep'
1804+
-- `newStep` happened after `step`
1805+
| happensBefore = stepInfoNonDep
1806+
-- `newStep` did not happen after `step`
1807+
| otherwise = newStep : stepInfoNonDep
1808+
1809+
-- Here we record discovered races. We only record a new
1810+
-- race if we are following the default schedule, to avoid
1811+
-- finding the same race in different parts of the search
1812+
-- space.
1813+
!stepInfoRaces'
1814+
| theseStepsRace && isDefaultSchedule control
1815+
= newStep : stepInfoRaces
1816+
| otherwise = stepInfoRaces
1817+
1818+
in stepInfo { stepInfoConcurrent = effectForks newEffect
1819+
`Set.union` concurrent',
1820+
stepInfoNonDep = stepInfoNonDep',
1821+
stepInfoRaces = stepInfoRaces'
1822+
}
17541823

17551824
activeRaces' :: [StepInfo]
1756-
!activeRaces' =
1757-
[ -- if this step depends on the previous step, or is not concurrent,
1758-
-- then any threads that it wakes up become non-concurrent also.
1759-
let !lessConcurrent = foldr Set.delete concurrent (effectWakeup newEffect) in
1760-
if tid `elem` concurrent then
1761-
let theseStepsRace = isRacyThreadId tid && racingSteps step newStep
1762-
happensBefore = step `happensBeforeStep` newStep
1763-
!nondep' | happensBefore = nondep
1764-
| otherwise = newStep : nondep
1765-
-- We will only record the first race with each thread---reversing
1766-
-- the first race makes the next race detectable. Thus we remove a
1767-
-- thread from the concurrent set after the first race.
1768-
concurrent' | happensBefore = Set.delete tid lessConcurrent
1769-
| theseStepsRace = Set.delete tid concurrent
1770-
| otherwise = concurrent
1771-
-- Here we record discovered races.
1772-
-- We only record a new race if we are following the default schedule,
1773-
-- to avoid finding the same race in different parts of the search space.
1774-
!stepRaces' | (control == ControlDefault ||
1775-
control == ControlFollow [] []) &&
1776-
theseStepsRace = newStep : stepRaces
1777-
| otherwise = stepRaces
1778-
1779-
in stepInfo { stepInfoConcurrent = effectForks newEffect
1780-
`Set.union` concurrent',
1781-
stepInfoNonDep = nondep',
1782-
stepInfoRaces = stepRaces'
1783-
}
1825+
!activeRaces' =
1826+
case newStepInfo of
1827+
Nothing -> updateStepInfo <$> activeRaces
1828+
Just si -> si : (updateStepInfo <$> activeRaces)
17841829

1785-
else stepInfo { stepInfoConcurrent = lessConcurrent }
1830+
in normalizeRaces races { activeRaces = activeRaces' }
1831+
1832+
normalizeRaces :: Races -> Races
1833+
normalizeRaces Races{ activeRaces, completeRaces } =
1834+
let !activeRaces' = filter (not . null . stepInfoConcurrent) activeRaces
1835+
!completeRaces' = ( filter (not . null . stepInfoRaces)
1836+
. filter (null . stepInfoConcurrent)
1837+
$ activeRaces
1838+
)
1839+
++ completeRaces
1840+
in Races{ activeRaces = activeRaces', completeRaces = completeRaces' }
17861841

1787-
| !stepInfo@StepInfo { stepInfoStep = step,
1788-
stepInfoConcurrent = concurrent,
1789-
stepInfoNonDep = nondep,
1790-
stepInfoRaces = stepRaces
1791-
}
1792-
<- activeRaces ]
1793-
in normalizeRaces $ races { activeRaces = new ++ activeRaces' }
17941842

17951843
-- When a thread terminates, we remove it from the concurrent thread
17961844
-- sets of active races.
1797-
17981845
threadTerminatesRaces :: ThreadId -> Races -> Races
17991846
threadTerminatesRaces tid races@Races{ activeRaces } =
18001847
let activeRaces' = [ s{stepInfoConcurrent = Set.delete tid stepInfoConcurrent}
18011848
| s@StepInfo{ stepInfoConcurrent } <- activeRaces ]
18021849
in normalizeRaces $ races{ activeRaces = activeRaces' }
18031850

1804-
normalizeRaces :: Races -> Races
1805-
normalizeRaces Races{ activeRaces, completeRaces } =
1806-
let !activeRaces' = filter (not . null. stepInfoConcurrent) activeRaces
1807-
!completeRaces' = filter (not . null. stepInfoRaces)
1808-
(filter (null . stepInfoConcurrent) activeRaces)
1809-
++ completeRaces
1810-
in Races{ activeRaces = activeRaces', completeRaces = completeRaces' }
1811-
18121851
-- We assume that steps do not race with later steps after a quiescent
18131852
-- period. Quiescent periods end when simulated time advances, thus we
18141853
-- are assuming here that all work is completed before a timer
@@ -1822,10 +1861,11 @@ quiescentRaces Races{ activeRaces, completeRaces } =
18221861
, not (null (stepInfoRaces s))
18231862
] ++ completeRaces }
18241863

1825-
traceRaces :: Races -> Races
1826-
traceRaces r = r
1827-
-- traceRaces r@Races{activeRaces,completeRaces} =
1828-
-- Debug.trace ("Tracking "++show (length (concatMap stepInfoRaces activeRaces)) ++" races") r
1864+
-- for debugging purposes
1865+
debugTraceRaces :: Races -> Races
1866+
debugTraceRaces r = r
1867+
-- debugTraceRaces r@Races{activeRaces,completeRaces} =
1868+
-- Debug.trace ("Tracking "++show (length (concatMap stepInfoRaces activeRaces)) ++" races") r
18291869

18301870

18311871
--
@@ -1954,4 +1994,4 @@ extendScheduleControl control m =
19541994
"Extending "++show control,
19551995
" with "++show m,
19561996
" yields "++show control']) -}
1957-
control'
1997+
control'

0 commit comments

Comments
 (0)