Skip to content

Commit 6e31608

Browse files
authored
Merge pull request #6241 from commercialhaskell/tinker
2 parents e657751 + cddb8a7 commit 6e31608

File tree

1 file changed

+85
-61
lines changed

1 file changed

+85
-61
lines changed

src/Control/Concurrent/Execute.hs

Lines changed: 85 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module Control.Concurrent.Execute
1212
, runActions
1313
) where
1414

15-
import Control.Concurrent.STM ( retry )
15+
import Control.Concurrent.STM ( check )
1616
import Stack.Prelude
1717
import Data.List ( sortBy )
1818
import qualified Data.Set as Set
@@ -28,45 +28,53 @@ instance Exception ExecuteException where
2828
"Inconsistent dependencies were discovered while executing your build \
2929
\plan."
3030

31+
-- | Type representing types of Stack build actions.
3132
data ActionType
3233
= ATBuild
3334
-- ^ Action for building a package's library and executables. If
34-
-- 'taskAllInOne' is 'True', then this will also build benchmarks
35-
-- and tests. It is 'False' when then library's benchmarks or
36-
-- test-suites have cyclic dependencies.
35+
-- 'taskAllInOne' is 'True', then this will also build benchmarks and tests.
36+
-- It is 'False' when the library's benchmarks or test-suites have cyclic
37+
-- dependencies.
3738
| ATBuildFinal
38-
-- ^ Task for building the package's benchmarks and test-suites.
39-
-- Requires that the library was already built.
39+
-- ^ Task for building the package's benchmarks and test-suites. Requires
40+
-- that the library was already built.
4041
| ATRunTests
4142
-- ^ Task for running the package's test-suites.
4243
| ATRunBenchmarks
4344
-- ^ Task for running the package's benchmarks.
4445
deriving (Show, Eq, Ord)
4546

47+
-- | Types representing the unique ids of Stack build actions.
4648
data ActionId
4749
= ActionId !PackageIdentifier !ActionType
4850
deriving (Eq, Ord, Show)
4951

50-
data Action
51-
= Action
52+
-- | Type representing Stack build actions.
53+
data Action = Action
5254
{ actionId :: !ActionId
55+
-- ^ The action's unique id.
5356
, actionDeps :: !(Set ActionId)
57+
-- ^ Actions on which this action depends.
5458
, actionDo :: !(ActionContext -> IO ())
59+
-- ^ The action's 'IO' action, given a context.
5560
, actionConcurrency :: !Concurrency
61+
-- ^ Whether this action may be run concurrently with others.
5662
}
5763

64+
-- | Type representing permissions for actions to be run concurrently with
65+
-- others.
5866
data Concurrency
5967
= ConcurrencyAllowed
6068
| ConcurrencyDisallowed
6169
deriving Eq
6270

6371
data ActionContext = ActionContext
6472
{ acRemaining :: !(Set ActionId)
65-
-- ^ Does not include the current action
73+
-- ^ Does not include the current action.
6674
, acDownstream :: [Action]
67-
-- ^ Actions which depend on the current action
75+
-- ^ Actions which depend on the current action.
6876
, acConcurrency :: !Concurrency
69-
-- ^ Whether this action may be run concurrently with others
77+
-- ^ Whether this action may be run concurrently with others.
7078
}
7179

7280
data ExecuteState = ExecuteState
@@ -77,11 +85,12 @@ data ExecuteState = ExecuteState
7785
, esKeepGoing :: Bool
7886
}
7987

80-
runActions :: Int -- ^ threads
81-
-> Bool -- ^ keep going after one task has failed
82-
-> [Action]
83-
-> (TVar Int -> TVar (Set ActionId) -> IO ()) -- ^ progress updated
84-
-> IO [SomeException]
88+
runActions ::
89+
Int -- ^ threads
90+
-> Bool -- ^ keep going after one task has failed
91+
-> [Action]
92+
-> (TVar Int -> TVar (Set ActionId) -> IO ()) -- ^ progress updated
93+
-> IO [SomeException]
8594
runActions threads keepGoing actions withProgress = do
8695
es <- ExecuteState
8796
<$> newTVarIO (sortActions actions) -- esActions
@@ -110,56 +119,71 @@ sortActions = sortBy (compareConcurrency `on` actionConcurrency)
110119
runActions' :: ExecuteState -> IO ()
111120
runActions' ExecuteState {..} = loop
112121
where
122+
loop :: IO ()
123+
loop = join $ atomically $ breakOnErrs $ withActions processActions
124+
113125
breakOnErrs :: STM (IO ()) -> STM (IO ())
114126
breakOnErrs inner = do
115127
errs <- readTVar esExceptions
116128
if null errs || esKeepGoing
117129
then inner
118-
else pure $ pure ()
130+
else doNothing
131+
119132
withActions :: ([Action] -> STM (IO ())) -> STM (IO ())
120133
withActions inner = do
121-
as <- readTVar esActions
122-
if null as
123-
then pure $ pure ()
124-
else inner as
125-
loop :: IO ()
126-
loop = join $ atomically $ breakOnErrs $ withActions $ \as ->
127-
case break (Set.null . actionDeps) as of
134+
actions <- readTVar esActions
135+
if null actions
136+
then doNothing
137+
else inner actions
138+
139+
processActions :: [Action] -> STM (IO ())
140+
processActions actions = do
141+
inAction <- readTVar esInAction
142+
case break (Set.null . actionDeps) actions of
128143
(_, []) -> do
129-
inAction <- readTVar esInAction
130-
if Set.null inAction
131-
then do
132-
unless esKeepGoing $
133-
modifyTVar esExceptions (toException InconsistentDependenciesBug:)
134-
pure $ pure ()
135-
else retry
136-
(xs, action:ys) -> do
137-
inAction <- readTVar esInAction
138-
case actionConcurrency action of
139-
ConcurrencyAllowed -> pure ()
140-
ConcurrencyDisallowed -> unless (Set.null inAction) retry
141-
let as' = xs ++ ys
142-
remaining = Set.union
143-
(Set.fromList $ map actionId as')
144-
inAction
145-
writeTVar esActions as'
146-
modifyTVar esInAction (Set.insert $ actionId action)
147-
pure $ mask $ \restore -> do
148-
eres <- try $ restore $ actionDo action ActionContext
149-
{ acRemaining = remaining
150-
, acDownstream = downstreamActions (actionId action) as'
151-
, acConcurrency = actionConcurrency action
152-
}
153-
atomically $ do
154-
modifyTVar esInAction (Set.delete $ actionId action)
155-
modifyTVar esCompleted (+1)
156-
case eres of
157-
Left err -> modifyTVar esExceptions (err:)
158-
Right () ->
159-
let dropDep a =
160-
a { actionDeps = Set.delete (actionId action) $ actionDeps a }
161-
in modifyTVar esActions $ map dropDep
162-
restore loop
163-
164-
downstreamActions :: ActionId -> [Action] -> [Action]
165-
downstreamActions aid = filter (\a -> aid `Set.member` actionDeps a)
144+
check (Set.null inAction)
145+
unless esKeepGoing $
146+
modifyTVar esExceptions (toException InconsistentDependenciesBug:)
147+
doNothing
148+
(xs, action:ys) -> processAction inAction (xs ++ ys) action
149+
150+
processAction :: Set ActionId -> [Action] -> Action -> STM (IO ())
151+
processAction inAction otherActions action = do
152+
let concurrency = actionConcurrency action
153+
unless (concurrency == ConcurrencyAllowed) $
154+
check (Set.null inAction)
155+
let action' = actionId action
156+
otherActions' = Set.fromList $ map actionId otherActions
157+
remaining = Set.union otherActions' inAction
158+
actionContext = ActionContext
159+
{ acRemaining = remaining
160+
, acDownstream = downstreamActions action' otherActions
161+
, acConcurrency = concurrency
162+
}
163+
writeTVar esActions otherActions
164+
modifyTVar esInAction (Set.insert action')
165+
pure $ do
166+
mask $ \restore -> do
167+
eres <- try $ restore $ actionDo action actionContext
168+
atomically $ do
169+
modifyTVar esInAction (Set.delete action')
170+
modifyTVar esCompleted (+1)
171+
case eres of
172+
Left err -> modifyTVar esExceptions (err:)
173+
Right () -> modifyTVar esActions $ map (dropDep action')
174+
loop
175+
176+
-- | Filter a list of actions to include only those that depend on the given
177+
-- action.
178+
downstreamActions :: ActionId -> [Action] -> [Action]
179+
downstreamActions aid = filter (\a -> aid `Set.member` actionDeps a)
180+
181+
-- | Given two actions (the first specified by its id) yield an action
182+
-- equivalent to the second but excluding any dependency on the first action.
183+
dropDep :: ActionId -> Action -> Action
184+
dropDep action' action =
185+
action { actionDeps = Set.delete action' $ actionDeps action }
186+
187+
-- | @IO ()@ lifted into 'STM'.
188+
doNothing :: STM (IO ())
189+
doNothing = pure $ pure ()

0 commit comments

Comments
 (0)