@@ -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 )
1616import Stack.Prelude
1717import Data.List ( sortBy )
1818import 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.
3132data 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.
4648data 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.
5866data Concurrency
5967 = ConcurrencyAllowed
6068 | ConcurrencyDisallowed
6169 deriving Eq
6270
6371data 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
7280data 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 ]
8594runActions threads keepGoing actions withProgress = do
8695 es <- ExecuteState
8796 <$> newTVarIO (sortActions actions) -- esActions
@@ -110,56 +119,71 @@ sortActions = sortBy (compareConcurrency `on` actionConcurrency)
110119runActions' :: ExecuteState -> IO ()
111120runActions' 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