sheaf pushed to branch wip/romes/27514 at Glasgow Haskell Compiler / GHC
Commits:
-
566d789d
by sheaf at 2026-08-14T23:26:51+02:00
9 changed files:
- + compiler/GHC/Driver/Concurrency.hs
- + compiler/GHC/Driver/Config/Concurrency.hs
- compiler/GHC/Driver/Downsweep.hs
- compiler/GHC/Driver/Make.hs
- compiler/GHC/Driver/MakeAction.hs
- compiler/GHC/Driver/MakeSem.hs
- compiler/GHC/Utils/TmpFs.hs
- compiler/ghc.cabal.in
- utils/haddock/haddock-api/src/Haddock/Interface.hs
Changes:
| 1 | +{-# LANGUAGE CPP #-}
|
|
| 2 | + |
|
| 3 | +{-# LANGUAGE BlockArguments #-}
|
|
| 4 | + |
|
| 5 | +module GHC.Driver.Concurrency
|
|
| 6 | + ( -- * Worker limit and concurrency
|
|
| 7 | + WorkerLimit(..)
|
|
| 8 | + , isWorkerLimitSequential
|
|
| 9 | + , withWorkerLimit
|
|
| 10 | + , Concurrency
|
|
| 11 | + , withConcurrency
|
|
| 12 | + -- * Concurrent worker scheduling
|
|
| 13 | + , ConcurrentWorkerEnv(..)
|
|
| 14 | + , mapConcurrentWorkers
|
|
| 15 | + , concurrentTraversal_DF
|
|
| 16 | + )
|
|
| 17 | + where
|
|
| 18 | + |
|
| 19 | +import GHC.Prelude
|
|
| 20 | + |
|
| 21 | +import GHC.Driver.MakeSem
|
|
| 22 | +import GHC.Driver.Pipeline.LogQueue
|
|
| 23 | + ( LogQueueQueue, finishLogQueue, initLogQueue, logThread
|
|
| 24 | + , newLogQueue, newLogQueueQueue, parLogAction )
|
|
| 25 | +import GHC.Utils.Logger
|
|
| 26 | + ( Logger, makeThreadSafe, pushLogHook )
|
|
| 27 | +import GHC.Utils.Panic
|
|
| 28 | + ( panic )
|
|
| 29 | +import GHC.Utils.TmpFs
|
|
| 30 | + ( TmpFs, forkTmpFsFrom, mergeTmpFsInto, withLocalTmpFS )
|
|
| 31 | + |
|
| 32 | +import System.Semaphore
|
|
| 33 | + ( SemaphoreError, SemaphoreIdentifier )
|
|
| 34 | + |
|
| 35 | +#if defined(wasm32_HOST_ARCH) || defined(javascript_HOST_ARCH)
|
|
| 36 | +import Control.Concurrent
|
|
| 37 | + ( ThreadId, forkIOWithUnmask, killThread, myThreadId )
|
|
| 38 | +import Control.Concurrent.MVar
|
|
| 39 | + ( MVar, newEmptyMVar, newMVar, putMVar, takeMVar )
|
|
| 40 | +import GHC.Conc
|
|
| 41 | + ( labelThread )
|
|
| 42 | +#else
|
|
| 43 | +import Control.Concurrent
|
|
| 44 | + ( ThreadId, forkIOWithUnmask, killThread, myThreadId
|
|
| 45 | + , newQSem, signalQSem, waitQSem, MVar, takeMVar, putMVar, newEmptyMVar )
|
|
| 46 | +import Control.Monad
|
|
| 47 | + ( unless )
|
|
| 48 | +import qualified Control.Monad.Catch as MC
|
|
| 49 | +import GHC.Conc
|
|
| 50 | + ( getNumCapabilities, getNumProcessors, labelThread, setNumCapabilities )
|
|
| 51 | +#endif
|
|
| 52 | +import Control.Concurrent.STM
|
|
| 53 | + ( TVar, atomically, check, modifyTVar', newTVarIO, readTVar, writeTVar )
|
|
| 54 | +import Control.Exception
|
|
| 55 | + ( AsyncException(ThreadKilled), SomeAsyncException, SomeException
|
|
| 56 | + , finally, fromException, mask, mask_, onException
|
|
| 57 | + , throwIO, try, uninterruptibleMask_ )
|
|
| 58 | +import Control.Monad
|
|
| 59 | + ( replicateM )
|
|
| 60 | +import Data.Foldable
|
|
| 61 | + ( for_ )
|
|
| 62 | +import Data.IORef
|
|
| 63 | + ( IORef, atomicModifyIORef', modifyIORef', newIORef, readIORef, writeIORef )
|
|
| 64 | +import qualified Data.Map as Map
|
|
| 65 | +import qualified Data.Sequence as Seq
|
|
| 66 | +import qualified Data.Set as Set
|
|
| 67 | + |
|
| 68 | +--------------------------------------------------------------------------------
|
|
| 69 | +-- * Worker Limit
|
|
| 70 | +--------------------------------------------------------------------------------
|
|
| 71 | + |
|
| 72 | +-- | A limit on the number of concurrent worker threads.
|
|
| 73 | +data WorkerLimit
|
|
| 74 | + -- | Fixed concurrent worker count limit @-jN@
|
|
| 75 | + = NumProcessorsLimit Int
|
|
| 76 | + -- | The concurrent worker count is limited by a @-jsem@ semaphore
|
|
| 77 | + | JSemLimit
|
|
| 78 | + SemaphoreIdentifier
|
|
| 79 | + -- ^ Semaphore identifier (from the @semaphore-compat@ library)
|
|
| 80 | + deriving Eq
|
|
| 81 | + |
|
| 82 | +isWorkerLimitSequential :: WorkerLimit -> Bool
|
|
| 83 | +isWorkerLimitSequential (NumProcessorsLimit x) = x <= 1
|
|
| 84 | +isWorkerLimitSequential (JSemLimit {}) = False
|
|
| 85 | + |
|
| 86 | +runWorkerLimit
|
|
| 87 | + :: (SemaphoreError -> IO ())
|
|
| 88 | + -- ^ report failure when opening the @-jsem@ semaphore
|
|
| 89 | + -- (after which we fall back to running with a single job)
|
|
| 90 | + -> WorkerLimit -> (AbstractSem -> IO a) -> IO a
|
|
| 91 | +#if defined(wasm32_HOST_ARCH) || defined(javascript_HOST_ARCH)
|
|
| 92 | +runWorkerLimit _report_semaphore_failure _ action = do
|
|
| 93 | + lock <- newMVar ()
|
|
| 94 | + action $ AbstractSem (takeMVar lock) (putMVar lock ())
|
|
| 95 | +#else
|
|
| 96 | +runWorkerLimit report_semaphore_failure worker_limit action = case worker_limit of
|
|
| 97 | + NumProcessorsLimit n_jobs ->
|
|
| 98 | + runNjobsAbstractSem n_jobs action
|
|
| 99 | + JSemLimit sem_ident ->
|
|
| 100 | + runJSemAbstractSem sem_ident action >>= \case
|
|
| 101 | + Right a -> return a
|
|
| 102 | + Left err -> do
|
|
| 103 | + report_semaphore_failure err
|
|
| 104 | + runNjobsAbstractSem 1 action
|
|
| 105 | +#endif
|
|
| 106 | + |
|
| 107 | +#if !(defined(wasm32_HOST_ARCH) || defined(javascript_HOST_ARCH))
|
|
| 108 | +runNjobsAbstractSem :: Int -> (AbstractSem -> IO a) -> IO a
|
|
| 109 | +runNjobsAbstractSem n_jobs action = do
|
|
| 110 | + compile_sem <- newQSem n_jobs
|
|
| 111 | + n_capabilities <- getNumCapabilities
|
|
| 112 | + n_cpus <- getNumProcessors
|
|
| 113 | + let
|
|
| 114 | + asem = AbstractSem (waitQSem compile_sem) (signalQSem compile_sem)
|
|
| 115 | + set_num_caps n = unless (n_capabilities /= 1) $ setNumCapabilities n
|
|
| 116 | + updNumCapabilities = do
|
|
| 117 | + -- Setting number of capabilities more than
|
|
| 118 | + -- CPU count usually leads to high userspace
|
|
| 119 | + -- lock contention. #9221
|
|
| 120 | + set_num_caps $ min n_jobs n_cpus
|
|
| 121 | + resetNumCapabilities = set_num_caps n_capabilities
|
|
| 122 | + MC.bracket_ updNumCapabilities resetNumCapabilities $ action asem
|
|
| 123 | + |
|
| 124 | +#endif
|
|
| 125 | + |
|
| 126 | +--------------------------------------------------------------------------------
|
|
| 127 | +-- * Workers
|
|
| 128 | +--------------------------------------------------------------------------------
|
|
| 129 | + |
|
| 130 | +data Concurrency
|
|
| 131 | + = Serial
|
|
| 132 | + | Concurrent !ConcurrencyEnv
|
|
| 133 | + |
|
| 134 | +-- | Run an action with the given concurrency control (serial or concurrent).
|
|
| 135 | +withConcurrency :: Concurrency -> IO a -> IO a
|
|
| 136 | +withConcurrency conc act =
|
|
| 137 | + case conc of
|
|
| 138 | + Serial -> act
|
|
| 139 | + Concurrent ( ConcurrencyEnv { ce_semaphore } ) ->
|
|
| 140 | + withAbstractSem ce_semaphore act
|
|
| 141 | + |
|
| 142 | +data ConcurrencyEnv =
|
|
| 143 | + ConcurrencyEnv
|
|
| 144 | + { ce_semaphore :: !AbstractSem
|
|
| 145 | + , ce_log_queue_queue :: !( TVar LogQueueQueue )
|
|
| 146 | + , ce_next_log_queue_id :: !( IORef Int )
|
|
| 147 | + }
|
|
| 148 | + |
|
| 149 | +-- | The local environment of a worker thread that may be scheduled concurrently.
|
|
| 150 | +data ConcurrentWorkerEnv = ConcurrentWorkerEnv
|
|
| 151 | + { cwe_logger :: !Logger
|
|
| 152 | + , cwe_tmpfs :: !TmpFs
|
|
| 153 | + }
|
|
| 154 | + |
|
| 155 | +-- | Run an action with a local 'TmpFs', merging in the resulting temporary file
|
|
| 156 | +-- accumulator into the parent afterwards.
|
|
| 157 | +workerEnv_withLocalTmpFS :: ConcurrentWorkerEnv -> (ConcurrentWorkerEnv -> IO a) -> IO a
|
|
| 158 | +workerEnv_withLocalTmpFS env use =
|
|
| 159 | + withLocalTmpFS (cwe_tmpfs env) \ lcl_tmpfs ->
|
|
| 160 | + use env { cwe_tmpfs = lcl_tmpfs }
|
|
| 161 | + |
|
| 162 | +-- | Run an action either serially or concurrently based on the provided
|
|
| 163 | +-- 'WorkerLimit'.
|
|
| 164 | +withWorkerLimit
|
|
| 165 | + :: Logger
|
|
| 166 | + -> TmpFs
|
|
| 167 | + -> (SemaphoreError -> IO ())
|
|
| 168 | + -- ^ report a failure to open the @-jsem@ semaphore
|
|
| 169 | + -- (after which we fall back to running with a single job)
|
|
| 170 | + -> WorkerLimit
|
|
| 171 | + -> (Concurrency -> ConcurrentWorkerEnv -> IO a) -- ^ action to run
|
|
| 172 | + -> IO a
|
|
| 173 | +withWorkerLimit logger tmpfs report_semaphore_failure limit action
|
|
| 174 | + | isWorkerLimitSequential limit
|
|
| 175 | + = action Serial $
|
|
| 176 | + ConcurrentWorkerEnv
|
|
| 177 | + { cwe_logger = logger
|
|
| 178 | + , cwe_tmpfs = tmpfs
|
|
| 179 | + }
|
|
| 180 | + | otherwise
|
|
| 181 | + = do
|
|
| 182 | + safe_logger <- makeThreadSafe logger
|
|
| 183 | + lqq_var <- newTVarIO newLogQueueQueue
|
|
| 184 | + stopped_var <- newTVarIO False
|
|
| 185 | + wait_log_thread <- logThread safe_logger stopped_var lqq_var
|
|
| 186 | + next_logq_var <- newIORef 1
|
|
| 187 | + |
|
| 188 | + let
|
|
| 189 | + stop_logging :: IO ()
|
|
| 190 | + stop_logging = do
|
|
| 191 | + atomically $ writeTVar stopped_var True
|
|
| 192 | + wait_log_thread
|
|
| 193 | + |
|
| 194 | + parent_work_env :: ConcurrentWorkerEnv
|
|
| 195 | + parent_work_env =
|
|
| 196 | + ConcurrentWorkerEnv
|
|
| 197 | + { cwe_logger = safe_logger
|
|
| 198 | + , cwe_tmpfs = tmpfs
|
|
| 199 | + }
|
|
| 200 | + |
|
| 201 | + ( `finally` stop_logging ) $
|
|
| 202 | + runWorkerLimit report_semaphore_failure limit \ sem -> do
|
|
| 203 | + let
|
|
| 204 | + conc =
|
|
| 205 | + Concurrent $
|
|
| 206 | + ConcurrencyEnv
|
|
| 207 | + { ce_semaphore = sem
|
|
| 208 | + , ce_log_queue_queue = lqq_var
|
|
| 209 | + , ce_next_log_queue_id = next_logq_var
|
|
| 210 | + }
|
|
| 211 | + action conc parent_work_env
|
|
| 212 | + |
|
| 213 | +--------------------------------------------------------------------------------
|
|
| 214 | +-- * Scheduling concurrent workers
|
|
| 215 | +--------------------------------------------------------------------------------
|
|
| 216 | + |
|
| 217 | +-- | Internal scheduler abstraction with two capabilities:
|
|
| 218 | +--
|
|
| 219 | +-- - spawn a new worker thread
|
|
| 220 | +-- - wait for a worker thread to complete
|
|
| 221 | +data Scheduler r = Scheduler
|
|
| 222 | + { spawnWorker :: (ConcurrentWorkerEnv -> IO r) -> IO ()
|
|
| 223 | + -- ^ Spawn one concurrent worker.
|
|
| 224 | + --
|
|
| 225 | + -- The worker does not hold a token of the concurrency semaphore: the
|
|
| 226 | + -- worker action should use 'withConcurrency' around the work whose
|
|
| 227 | + -- concurrency should be limited.
|
|
| 228 | + , awaitWorker :: IO (Either SomeException r)
|
|
| 229 | + -- ^ Wait for one worker to complete.
|
|
| 230 | + --
|
|
| 231 | + -- Will crash if there are no outstanding workers.
|
|
| 232 | + }
|
|
| 233 | + |
|
| 234 | +-- | Internal implementation of a concurrent worker scheduler.
|
|
| 235 | +--
|
|
| 236 | +-- Usage of this function requires the following:
|
|
| 237 | +--
|
|
| 238 | +-- - all spawn/await actions are performed by a single thread,
|
|
| 239 | +-- - we never wait for more workers than were spawned,
|
|
| 240 | +-- - no worker outlives 'run_schedule'.
|
|
| 241 | +run_schedule
|
|
| 242 | + :: forall r a
|
|
| 243 | + . String
|
|
| 244 | + -- ^ thread label for workers
|
|
| 245 | + -> Concurrency
|
|
| 246 | + -> ConcurrentWorkerEnv
|
|
| 247 | + -> (Scheduler r -> IO a)
|
|
| 248 | + -- ^ worker action
|
|
| 249 | + -> IO a
|
|
| 250 | +run_schedule worker_label conc parent_work_env withScheduler =
|
|
| 251 | + case conc of
|
|
| 252 | + |
|
| 253 | + Serial -> do
|
|
| 254 | + results_var <- newIORef Seq.empty
|
|
| 255 | + let
|
|
| 256 | + spawnWorker :: (ConcurrentWorkerEnv -> IO r) -> IO ()
|
|
| 257 | + spawnWorker action = do
|
|
| 258 | + res <- try @SomeException $
|
|
| 259 | + workerEnv_withLocalTmpFS parent_work_env action
|
|
| 260 | + case res of
|
|
| 261 | + Left e
|
|
| 262 | + | Just _ <- fromException @SomeAsyncException e
|
|
| 263 | + -> throwIO e
|
|
| 264 | + _ -> modifyIORef' results_var (Seq.|> res)
|
|
| 265 | + |
|
| 266 | + awaitWorker :: IO (Either SomeException r)
|
|
| 267 | + awaitWorker =
|
|
| 268 | + readIORef results_var >>= \case
|
|
| 269 | + res Seq.:<| rest -> do
|
|
| 270 | + writeIORef results_var rest
|
|
| 271 | + pure res
|
|
| 272 | + Seq.Empty ->
|
|
| 273 | + panic "run_schedule: no outstanding job"
|
|
| 274 | + |
|
| 275 | + withScheduler $ Scheduler { spawnWorker, awaitWorker }
|
|
| 276 | + |
|
| 277 | + Concurrent ( ConcurrencyEnv { ce_next_log_queue_id, ce_log_queue_queue } ) -> do
|
|
| 278 | + worker_tids_var <- newTVarIO $ Set.empty @ThreadId
|
|
| 279 | + all_results_vars_var <- newIORef $ Seq.empty @(MVar (Either SomeException r))
|
|
| 280 | + |
|
| 281 | + let
|
|
| 282 | + wait_for_workers :: IO ()
|
|
| 283 | + wait_for_workers =
|
|
| 284 | + atomically $
|
|
| 285 | + check . Set.null =<< readTVar worker_tids_var
|
|
| 286 | + |
|
| 287 | + cancel_workers :: IO ()
|
|
| 288 | + cancel_workers = do
|
|
| 289 | + uninterruptibleMask_ do
|
|
| 290 | + tids <- atomically $ readTVar worker_tids_var
|
|
| 291 | + for_ tids killThread
|
|
| 292 | + wait_for_workers
|
|
| 293 | + |
|
| 294 | + awaitWorker :: IO (Either SomeException r)
|
|
| 295 | + awaitWorker =
|
|
| 296 | + readIORef all_results_vars_var >>= \case
|
|
| 297 | + first_worker_res_var Seq.:<| rest -> do
|
|
| 298 | + writeIORef all_results_vars_var rest
|
|
| 299 | + -- block on the earliest-spawned outstanding worker
|
|
| 300 | + takeMVar first_worker_res_var
|
|
| 301 | + Seq.Empty ->
|
|
| 302 | + panic "run_schedule: no outstanding job"
|
|
| 303 | + |
|
| 304 | + spawnWorker :: (ConcurrentWorkerEnv -> IO r) -> IO ()
|
|
| 305 | + spawnWorker action = mask_ do
|
|
| 306 | + |
|
| 307 | + worker_res_var <- newEmptyMVar
|
|
| 308 | + |
|
| 309 | + -- TmpFs
|
|
| 310 | + lcl_tmpfs <- forkTmpFsFrom (cwe_tmpfs parent_work_env)
|
|
| 311 | + |
|
| 312 | + -- LogQueue
|
|
| 313 | + lq <- do
|
|
| 314 | + job_id <- atomicModifyIORef' ce_next_log_queue_id \n -> (n + 1, n)
|
|
| 315 | + lq <- newLogQueue job_id
|
|
| 316 | + atomically $ initLogQueue ce_log_queue_queue lq
|
|
| 317 | + pure lq
|
|
| 318 | + |
|
| 319 | + let
|
|
| 320 | + |
|
| 321 | + worker_work_env :: ConcurrentWorkerEnv
|
|
| 322 | + worker_work_env =
|
|
| 323 | + parent_work_env
|
|
| 324 | + { cwe_tmpfs = lcl_tmpfs
|
|
| 325 | + , cwe_logger = pushLogHook (const (parLogAction lq))
|
|
| 326 | + (cwe_logger parent_work_env)
|
|
| 327 | + }
|
|
| 328 | + |
|
| 329 | + -- Run a worker action and record its result.
|
|
| 330 | + run_worker_and_record :: IO r -> IO ()
|
|
| 331 | + run_worker_and_record worker_action = do
|
|
| 332 | + res <- try @SomeException worker_action
|
|
| 333 | + case res of
|
|
| 334 | + Left e
|
|
| 335 | + -- Worker is being cancelled: don't record anything.
|
|
| 336 | + | Just ThreadKilled <- fromException e
|
|
| 337 | + -> pure ()
|
|
| 338 | + _ -> putMVar worker_res_var res
|
|
| 339 | + |
|
| 340 | + -- Record that a worker thread is done.
|
|
| 341 | + mark_worker_done :: ThreadId -> IO ()
|
|
| 342 | + mark_worker_done tid =
|
|
| 343 | + uninterruptibleMask_ do
|
|
| 344 | + -- Uninterruptible: the deletion below /must/ occur.
|
|
| 345 | + -- An uninterruptible mask is OK as we only ever block for (GAP) below.
|
|
| 346 | + mergeTmpFsInto lcl_tmpfs $ cwe_tmpfs parent_work_env
|
|
| 347 | + finishLogQueue lq
|
|
| 348 | + atomically do
|
|
| 349 | + tids <- readTVar worker_tids_var
|
|
| 350 | + check $ tid `Set.member` tids
|
|
| 351 | + -- Ensure we never end up with a dead ThreadId in 'worker_tids_var'
|
|
| 352 | + -- (if the worker thread finishes before the parent thread has
|
|
| 353 | + -- the time to add its ThreadId to 'worker_tids_var').
|
|
| 354 | + |
|
| 355 | + writeTVar worker_tids_var $ Set.delete tid tids
|
|
| 356 | + |
|
| 357 | + run_worker :: (forall b. IO b -> IO b) -> IO ()
|
|
| 358 | + run_worker unmask = do
|
|
| 359 | + tid <- myThreadId
|
|
| 360 | + labelThread tid worker_label
|
|
| 361 | + let
|
|
| 362 | + worker_action :: IO r
|
|
| 363 | + worker_action = unmask $ action worker_work_env
|
|
| 364 | + |
|
| 365 | + run_worker_and_record worker_action `finally`
|
|
| 366 | + mark_worker_done tid
|
|
| 367 | + |
|
| 368 | + worker_tid <-
|
|
| 369 | + forkIOWithUnmask run_worker
|
|
| 370 | + `onException` finishLogQueue lq
|
|
| 371 | + -- Very short (GAP) between forking the thread and recording its ThreadId.
|
|
| 372 | + atomically $ modifyTVar' worker_tids_var $ Set.insert worker_tid
|
|
| 373 | + modifyIORef' all_results_vars_var (Seq.|> worker_res_var)
|
|
| 374 | + |
|
| 375 | + mask \ restore -> do
|
|
| 376 | + result <- restore (withScheduler $ Scheduler { spawnWorker, awaitWorker })
|
|
| 377 | + `onException` cancel_workers
|
|
| 378 | + restore wait_for_workers `onException` cancel_workers
|
|
| 379 | + pure result
|
|
| 380 | + |
|
| 381 | +--------------------------------------------------------------------------------
|
|
| 382 | +-- * Derived scheduling functionality
|
|
| 383 | +--------------------------------------------------------------------------------
|
|
| 384 | + |
|
| 385 | +-- | Map a worker action over the input list with the given concurrency control.
|
|
| 386 | +--
|
|
| 387 | +-- Workers run to completion (no early abort); the first exception
|
|
| 388 | +-- (in input order) is rethrown at the end.
|
|
| 389 | +mapConcurrentWorkers
|
|
| 390 | + :: String -- ^ thread label for workers
|
|
| 391 | + -> Concurrency
|
|
| 392 | + -> ConcurrentWorkerEnv
|
|
| 393 | + -> (ConcurrentWorkerEnv -> a -> IO b)
|
|
| 394 | + -- ^ individual worker action
|
|
| 395 | + --
|
|
| 396 | + -- NB: workers do not hold semaphore tokens by default; use
|
|
| 397 | + -- 'withConcurrency' to acquire one
|
|
| 398 | + -> [a]
|
|
| 399 | + -> IO [b]
|
|
| 400 | +mapConcurrentWorkers worker_label conc work_env f xs =
|
|
| 401 | + run_schedule worker_label conc work_env \ scheduler -> do
|
|
| 402 | + for_ xs \ x -> spawnWorker scheduler \ worker_env -> f worker_env x
|
|
| 403 | + results <- replicateM (length xs) (awaitWorker scheduler)
|
|
| 404 | + either throwIO pure (sequence results)
|
|
| 405 | + |
|
| 406 | +-- | Depth-first traversal with on-the-fly expansion of nodes.
|
|
| 407 | +--
|
|
| 408 | +-- Each expansion step is handled by a worker thread under the given
|
|
| 409 | +-- concurrency control.
|
|
| 410 | +--
|
|
| 411 | +-- Deterministic: expansions are consumed in the order the nodes were
|
|
| 412 | +-- discovered, so the traversal is a function of the node graph alone.
|
|
| 413 | +--
|
|
| 414 | +-- Fails fast: the first worker exception cancels the outstanding workers and
|
|
| 415 | +-- is rethrown.
|
|
| 416 | +concurrentTraversal_DF
|
|
| 417 | + :: forall k n r
|
|
| 418 | + . Ord k
|
|
| 419 | + => String -- ^ thread label for workers
|
|
| 420 | + -> Concurrency
|
|
| 421 | + -> ConcurrentWorkerEnv
|
|
| 422 | + -> Map.Map k r
|
|
| 423 | + -- ^ results known ahead of time (no expansion needed)
|
|
| 424 | + -> [n]
|
|
| 425 | + -- ^ root nodes
|
|
| 426 | + -> (n -> k)
|
|
| 427 | + -- ^ node key from node
|
|
| 428 | + -> (ConcurrentWorkerEnv -> n -> IO (r, [n]))
|
|
| 429 | + -- ^ worker action: expand a node into its result and the children to visit next
|
|
| 430 | + --
|
|
| 431 | + -- NB: workers do not hold semaphore tokens by default; use
|
|
| 432 | + -- 'withConcurrency' to acquire one
|
|
| 433 | + -> IO (Map.Map k r)
|
|
| 434 | +concurrentTraversal_DF worker_label conc work_env base_map roots key expand =
|
|
| 435 | + run_schedule worker_label conc work_env \ scheduler -> do
|
|
| 436 | + let
|
|
| 437 | + expand_node :: n -> ConcurrentWorkerEnv -> IO (k, (r, [n]))
|
|
| 438 | + expand_node node worker_env = do
|
|
| 439 | + res <- expand worker_env node
|
|
| 440 | + pure (key node, res)
|
|
| 441 | + |
|
| 442 | + go
|
|
| 443 | + :: Map.Map k r -- expanded nodes and their results
|
|
| 444 | + -> Set.Set k -- nodes currently being expanded
|
|
| 445 | + -> [n] -- discovered nodes, to expand next
|
|
| 446 | + -> IO (Map.Map k r)
|
|
| 447 | + go !visited !pending (node : worklist)
|
|
| 448 | + | k `Set.member` pending || k `Map.member` visited
|
|
| 449 | + = go visited pending worklist
|
|
| 450 | + | otherwise
|
|
| 451 | + = do spawnWorker scheduler (expand_node node)
|
|
| 452 | + go visited (Set.insert k pending) worklist
|
|
| 453 | + where
|
|
| 454 | + k = key node
|
|
| 455 | + go visited pending []
|
|
| 456 | + | Set.null pending
|
|
| 457 | + = pure visited
|
|
| 458 | + | otherwise
|
|
| 459 | + = awaitWorker scheduler >>= \case
|
|
| 460 | + Left e -> throwIO e
|
|
| 461 | + Right (k, (result, children)) ->
|
|
| 462 | + go (Map.insert k result visited) (Set.delete k pending) children
|
|
| 463 | + |
|
| 464 | + go base_map Set.empty roots |
| 1 | +-- | Subsystem configuration for 'GHC.Driver.Concurrency'.
|
|
| 2 | +module GHC.Driver.Config.Concurrency
|
|
| 3 | + ( mkWorkerLimit
|
|
| 4 | + , semaphoreOpenFailureHandler
|
|
| 5 | + ) where
|
|
| 6 | + |
|
| 7 | +import GHC.Prelude
|
|
| 8 | + |
|
| 9 | +import GHC.Driver.Concurrency
|
|
| 10 | +import GHC.Driver.Config.Diagnostic ( initDiagOpts, initPrintConfig )
|
|
| 11 | +import GHC.Driver.DynFlags
|
|
| 12 | +import GHC.Driver.Errors ( printOrThrowDiagnostics )
|
|
| 13 | +import GHC.Driver.Errors.Types
|
|
| 14 | + |
|
| 15 | +import GHC.Types.Error ( singleMessage )
|
|
| 16 | +import GHC.Types.SrcLoc ( noSrcSpan )
|
|
| 17 | +import GHC.Utils.Error ( mkPlainMsgEnvelope )
|
|
| 18 | +import GHC.Utils.Logger ( Logger )
|
|
| 19 | + |
|
| 20 | +import GHC.Conc ( getNumProcessors )
|
|
| 21 | +import System.Semaphore ( SemaphoreError )
|
|
| 22 | + |
|
| 23 | +--------------------------------------------------------------------------------
|
|
| 24 | + |
|
| 25 | +-- | Compute the 'WorkerLimit' from the @-j@\/@-jsem@ flags.
|
|
| 26 | +mkWorkerLimit :: DynFlags -> IO WorkerLimit
|
|
| 27 | +mkWorkerLimit dflags =
|
|
| 28 | + case parMakeCount dflags of
|
|
| 29 | + Nothing -> pure $ num_procs 1
|
|
| 30 | + Just (ParMakeSemaphore h) -> pure (JSemLimit h)
|
|
| 31 | + Just ParMakeNumProcessors -> num_procs <$> getNumProcessors
|
|
| 32 | + Just (ParMakeThisMany n) -> pure $ num_procs n
|
|
| 33 | + where
|
|
| 34 | + num_procs x = NumProcessorsLimit (max 1 x)
|
|
| 35 | + |
|
| 36 | +-- | Report that the semaphore specified using the @-jsem@ flag could not be opened.
|
|
| 37 | +semaphoreOpenFailureHandler :: Logger -> DynFlags -> SemaphoreError -> IO ()
|
|
| 38 | +semaphoreOpenFailureHandler logger dflags err = do
|
|
| 39 | + let diag = DriverSemaphoreOpenFailure (checkBuildingCabalPackage dflags) err
|
|
| 40 | + msg = singleMessage $ mkPlainMsgEnvelope (initDiagOpts dflags) noSrcSpan diag
|
|
| 41 | + printOrThrowDiagnostics logger (initPrintConfig dflags) (initDiagOpts dflags) (GhcDriverMessage <$> msg) |
| ... | ... | @@ -40,9 +40,9 @@ import GHC.Driver.Monad |
| 40 | 40 | import GHC.Driver.Env
|
| 41 | 41 | import GHC.Driver.Errors
|
| 42 | 42 | import GHC.Driver.Errors.Types
|
| 43 | -import GHC.Driver.Messager
|
|
| 44 | -import GHC.Driver.MakeSem
|
|
| 43 | +import GHC.Driver.Concurrency
|
|
| 45 | 44 | import GHC.Driver.MakeAction
|
| 45 | +import GHC.Driver.Config.Concurrency
|
|
| 46 | 46 | import GHC.Driver.Config.Diagnostic
|
| 47 | 47 | import GHC.Driver.Ppr
|
| 48 | 48 | |
| ... | ... | @@ -64,7 +64,6 @@ import GHC.Data.OsPath ( OsPath, unsafeEncodeUtf ) |
| 64 | 64 | import GHC.Data.StringBuffer
|
| 65 | 65 | import GHC.Data.Graph.Directed.Reachability
|
| 66 | 66 | |
| 67 | -import GHC.Utils.Exception ( throwIO, SomeAsyncException, AsyncException (..) )
|
|
| 68 | 67 | import GHC.Utils.Outputable
|
| 69 | 68 | import GHC.Utils.Panic
|
| 70 | 69 | import GHC.Utils.Misc
|
| ... | ... | @@ -101,7 +100,6 @@ import qualified Data.Set as Set |
| 101 | 100 | import Control.Concurrent.MVar
|
| 102 | 101 | import Control.Monad
|
| 103 | 102 | import Control.Monad.Trans.Except ( ExceptT(..), runExceptT, throwE )
|
| 104 | -import qualified Control.Monad.Catch as MC
|
|
| 105 | 103 | import Data.Maybe
|
| 106 | 104 | import Data.List (partition)
|
| 107 | 105 | import Data.Time
|
| ... | ... | @@ -112,13 +110,8 @@ import System.FilePath |
| 112 | 110 | |
| 113 | 111 | import Control.Monad.Trans.Reader
|
| 114 | 112 | import qualified Data.Map.Strict as M
|
| 115 | -import Control.Monad.Trans.Class
|
|
| 116 | 113 | import System.IO.Unsafe (unsafeInterleaveIO)
|
| 117 | 114 | import qualified Data.List.NonEmpty as NE
|
| 118 | -import Control.Concurrent
|
|
| 119 | -import Control.Concurrent.STM.TQueue
|
|
| 120 | -import Control.Concurrent.STM
|
|
| 121 | -import Control.Applicative
|
|
| 122 | 115 | |
| 123 | 116 | {-
|
| 124 | 117 | Note [The ModuleGraph]
|
| ... | ... | @@ -245,8 +238,6 @@ See Note [The ModuleGraph] for an overview when we do downsweep. |
| 245 | 238 | --
|
| 246 | 239 | -- See also Note [The ModuleGraph]
|
| 247 | 240 | downsweep :: HscEnv
|
| 248 | - -> (GhcMessage -> AnyGhcDiagnostic)
|
|
| 249 | - -> Maybe Messager
|
|
| 250 | 241 | -> [ModSummary]
|
| 251 | 242 | -- ^ Old summaries
|
| 252 | 243 | -> Maybe ModuleGraph
|
| ... | ... | @@ -260,13 +251,14 @@ downsweep :: HscEnv |
| 260 | 251 | -- The non-error elements of the returned list all have distinct
|
| 261 | 252 | -- (Modules, IsBoot) identifiers, unless the Bool is true in
|
| 262 | 253 | -- which case there can be repeats
|
| 263 | -downsweep hsc_env diag_wrapper msg old_summaries maybe_base_graph excl_mods allow_dup_roots = do
|
|
| 254 | +downsweep hsc_env old_summaries maybe_base_graph excl_mods allow_dup_roots = do
|
|
| 264 | 255 | n_jobs <- mkWorkerLimit (hsc_dflags hsc_env)
|
| 265 | 256 | summ_cache <- newMVar (mkModSummaryCache (zip old_summaries (repeat SummOld)))
|
| 266 | 257 | imps_cache <- newMVar Map.empty
|
| 267 | - withMakeEnv n_jobs hsc_env diag_wrapper msg $ \make_env -> do
|
|
| 268 | - (root_errs, root_summaries) <- rootSummariesParallel n_jobs make_env (hsc_targets hsc_env)
|
|
| 269 | - (getRootSummary excl_mods summ_cache imps_cache)
|
|
| 258 | + withWorkerLimitHsc hsc_env n_jobs $ \conc hsc_env' -> do
|
|
| 259 | + (root_errs, root_summaries) <-
|
|
| 260 | + rootSummariesParallel conc hsc_env' (hsc_targets hsc_env)
|
|
| 261 | + (getRootSummary excl_mods summ_cache imps_cache)
|
|
| 270 | 262 | let closure_errs = checkHomeUnitsClosed unit_env
|
| 271 | 263 | unit_env = hsc_unit_env hsc_env
|
| 272 | 264 | |
| ... | ... | @@ -275,13 +267,12 @@ downsweep hsc_env diag_wrapper msg old_summaries maybe_base_graph excl_mods allo |
| 275 | 267 | case all_errs of
|
| 276 | 268 | [] -> do
|
| 277 | 269 | let env = DownsweepEnv
|
| 278 | - { ds_hsc_env = hsc_env
|
|
| 270 | + { ds_hsc_env = hsc_env'
|
|
| 279 | 271 | , ds_summaries_cache = summ_cache
|
| 280 | 272 | , ds_imports_cache = imps_cache
|
| 281 | 273 | , ds_mode = DownsweepUseCompile
|
| 282 | 274 | , ds_excl_mods = excl_mods
|
| 283 | - , ds_n_jobs = n_jobs
|
|
| 284 | - , ds_make_env = make_env
|
|
| 275 | + , ds_concurrency = conc
|
|
| 285 | 276 | }
|
| 286 | 277 | (downsweep_errs, downsweep_nodes) <- runDownsweepM env $
|
| 287 | 278 | downsweepFromRootNodes maybe_base_graph allow_dup_roots
|
| ... | ... | @@ -349,15 +340,14 @@ downsweepThunk hsc_env mod_summary = unsafeInterleaveIO $ do |
| 349 | 340 | njobs <- mkWorkerLimit (hsc_dflags hsc_env)
|
| 350 | 341 | summs <- newMVar (mkModSummaryCache [(mod_summary,SummOld)])
|
| 351 | 342 | imps <- newMVar mempty
|
| 352 | - withMakeEnv njobs hsc_env mkUnknownDiagnostic Nothing $ \make_env -> do
|
|
| 343 | + withWorkerLimitHsc hsc_env njobs $ \conc hsc_env' -> do
|
|
| 353 | 344 | let env = DownsweepEnv
|
| 354 | - { ds_hsc_env = hsc_env
|
|
| 345 | + { ds_hsc_env = hsc_env'
|
|
| 355 | 346 | , ds_summaries_cache = summs
|
| 356 | 347 | , ds_imports_cache = imps
|
| 357 | 348 | , ds_mode = DownsweepUseFixed
|
| 358 | 349 | , ds_excl_mods = []
|
| 359 | - , ds_n_jobs = njobs
|
|
| 360 | - , ds_make_env = make_env
|
|
| 350 | + , ds_concurrency = conc
|
|
| 361 | 351 | }
|
| 362 | 352 | ~(errs, mg) <- runDownsweepM env $
|
| 363 | 353 | downsweepFromRootNodes Nothing True
|
| ... | ... | @@ -394,15 +384,14 @@ downsweepInteractiveImports hsc_env ic = unsafeInterleaveIO $ do |
| 394 | 384 | n_jobs <- mkWorkerLimit (hsc_dflags hsc_env)
|
| 395 | 385 | summ_cache <- newMVar mempty
|
| 396 | 386 | imps_cache <- newMVar mempty
|
| 397 | - withMakeEnv n_jobs hsc_env mkUnknownDiagnostic Nothing $ \make_env -> do
|
|
| 387 | + withWorkerLimitHsc hsc_env n_jobs $ \conc hsc_env' -> do
|
|
| 398 | 388 | let env = DownsweepEnv
|
| 399 | - { ds_hsc_env = hsc_env
|
|
| 389 | + { ds_hsc_env = hsc_env'
|
|
| 400 | 390 | , ds_mode = DownsweepUseFixed{-or DownsweepUseCompile?-}
|
| 401 | 391 | , ds_summaries_cache = summ_cache
|
| 402 | 392 | , ds_imports_cache = imps_cache
|
| 403 | 393 | , ds_excl_mods = []
|
| 404 | - , ds_n_jobs = n_jobs
|
|
| 405 | - , ds_make_env = make_env
|
|
| 394 | + , ds_concurrency = conc
|
|
| 406 | 395 | }
|
| 407 | 396 | graph <- runDownsweepM env do
|
| 408 | 397 | loopFromInteractive cached_nodes interactive_mn imps
|
| ... | ... | @@ -439,15 +428,14 @@ downsweepInstalledModules hsc_env mods = do |
| 439 | 428 | nodes <- mapM process installed_mods
|
| 440 | 429 | summs <- newMVar mempty
|
| 441 | 430 | imps <- newMVar mempty
|
| 442 | - withMakeEnv njobs hsc_env mkUnknownDiagnostic Nothing $ \make_env -> do
|
|
| 431 | + withWorkerLimitHsc hsc_env njobs $ \conc hsc_env' -> do
|
|
| 443 | 432 | let env = DownsweepEnv
|
| 444 | - { ds_hsc_env = hsc_env
|
|
| 433 | + { ds_hsc_env = hsc_env'
|
|
| 445 | 434 | , ds_summaries_cache = summs
|
| 446 | 435 | , ds_imports_cache = imps
|
| 447 | 436 | , ds_mode = DownsweepUseFixed
|
| 448 | 437 | , ds_excl_mods = []
|
| 449 | - , ds_n_jobs = njobs
|
|
| 450 | - , ds_make_env = make_env
|
|
| 438 | + , ds_concurrency = conc
|
|
| 451 | 439 | }
|
| 452 | 440 | (errs, mg) <- runDownsweepM env $
|
| 453 | 441 | downsweepFromRootNodes Nothing True nodes external_uids
|
| ... | ... | @@ -562,8 +550,8 @@ data DownsweepEnv = DownsweepEnv { |
| 562 | 550 | , ds_summaries_cache :: ModSummaryCache
|
| 563 | 551 | , ds_imports_cache :: ImportsCache
|
| 564 | 552 | , ds_excl_mods :: [ModuleName]
|
| 565 | - , ds_n_jobs :: WorkerLimit
|
|
| 566 | - , ds_make_env :: MakeEnv
|
|
| 553 | + , ds_concurrency :: Concurrency
|
|
| 554 | + -- ^ The concurrency to use for downsweep
|
|
| 567 | 555 | }
|
| 568 | 556 | |
| 569 | 557 | mkModSummaryCache :: [(ModSummary, SummProvenance)] -> ModSummaryCacheMap
|
| ... | ... | @@ -928,15 +916,28 @@ getRootSummary excl_mods summ_cache imports_cache hsc_env target |
| 928 | 916 | rootLoc = mkGeneralSrcSpan (fsLit "<command line>")
|
| 929 | 917 | dflags = homeUnitEnv_dflags (ue_findHomeUnitEnv uid (hsc_unit_env hsc_env))
|
| 930 | 918 | |
| 931 | --- | Execute 'getRootSummary' for the 'Target's using the parallelism pipeline system.
|
|
| 919 | +-- | Execute 'getRootSummary' for the 'Target's in bundles, spawning one
|
|
| 920 | +-- worker per bundle. The number of bundles processed at once is limited by
|
|
| 921 | +-- the given 'Concurrency'.
|
|
| 932 | 922 | rootSummariesParallel
|
| 933 | - :: WorkerLimit -> MakeEnv -> [Target]
|
|
| 923 | + :: Concurrency -> HscEnv -> [Target]
|
|
| 934 | 924 | -> (HscEnv -> Target -> IO (Either DriverMessages ModSummary))
|
| 935 | 925 | -> IO ([DriverMessages], [ModSummary])
|
| 936 | -rootSummariesParallel n_jobs make_env targets get_summary = do
|
|
| 937 | - partitionEithers <$> mapConcDS n_jobs bundle_size make_env get_summary targets
|
|
| 938 | - where
|
|
| 939 | - bundle_size = 20
|
|
| 926 | +rootSummariesParallel conc hsc_env targets get_summary = do
|
|
| 927 | + results <-
|
|
| 928 | + mapConcurrentWorkers "root_summary_worker" conc (viewHscWorkerEnv hsc_env)
|
|
| 929 | + ( \ work_env bundle ->
|
|
| 930 | + withConcurrency conc $
|
|
| 931 | + mapM (get_summary (setHscWorkerEnv work_env hsc_env)) bundle )
|
|
| 932 | + bundles
|
|
| 933 | + pure $ partitionEithers (concat results)
|
|
| 934 | + where
|
|
| 935 | + bundle_size = 20
|
|
| 936 | + |
|
| 937 | + bundles = mk_bundles targets
|
|
| 938 | + mk_bundles = unfoldr \case
|
|
| 939 | + [] -> Nothing
|
|
| 940 | + ts -> Just (splitAt bundle_size ts)
|
|
| 940 | 941 | |
| 941 | 942 | --------------------------------------------------------------------------------
|
| 942 | 943 | -- * Check/validate properties and error out
|
| ... | ... | @@ -1762,7 +1763,7 @@ data NodeRes v |
| 1762 | 1763 | -- node. The result includes the previously visited nodes given in @base_map@,
|
| 1763 | 1764 | -- s.t. @parDfsBuild base_map [] _ _ == base_map@.
|
| 1764 | 1765 | --
|
| 1765 | --- The @expand@ function returns an 'NResult'. See the 'NResult' documentation
|
|
| 1766 | +-- The @expand@ function returns a 'NodeRes'. See the 'NodeRes' documentation
|
|
| 1766 | 1767 | -- for more information about each result type.
|
| 1767 | 1768 | --
|
| 1768 | 1769 | -- Example usage: @n@ is instanced to @DownsweepNode@, @k@ is @NodeKey@, and @v@ is @ModuleNodeEdge@.
|
| ... | ... | @@ -1785,90 +1786,25 @@ parDfsBuild :: forall k v n. Ord k |
| 1785 | 1786 | -- ^ The result accumulates the payload of expanding the root nodes
|
| 1786 | 1787 | -- and all nodes transitively reachable from those roots.
|
| 1787 | 1788 | parDfsBuild base_map roots key expand = ReaderT $ \ds_env -> do
|
| 1788 | - exc_var <- newTVarIO $ Nothing @MC.SomeException
|
|
| 1789 | - visited_var <- newTVarIO $ fromMaybe Map.empty base_map
|
|
| 1790 | - pending <- newTVarIO $ Set.empty @k
|
|
| 1791 | - worklist <- newTQueueIO @n
|
|
| 1792 | - threads <- newTVarIO []
|
|
| 1793 | - |
|
| 1794 | - coord_tid <- forkIO $
|
|
| 1795 | - coordinator ds_env exc_var visited_var worklist pending threads
|
|
| 1796 | - `MC.catch` \case
|
|
| 1797 | - (e::MC.SomeException)
|
|
| 1798 | - -- exit cleanly when killed
|
|
| 1799 | - | Just ThreadKilled <- fromException e -> return ()
|
|
| 1800 | - -- if the coordinator somehow else crashes,
|
|
| 1801 | - -- signal the exc_var for the main thread to throw it
|
|
| 1802 | - | otherwise -> atomically (modifyTVar' exc_var (<|> Just e))
|
|
| 1803 | - |
|
| 1804 | - atomically $ mapM_ (writeTQueue worklist) roots
|
|
| 1805 | - |
|
| 1806 | - mb_exc <- wait_done exc_var worklist pending
|
|
| 1807 | - `MC.finally` do
|
|
| 1808 | - killThread coord_tid
|
|
| 1809 | - mapM_ killThread =<< readTVarIO threads
|
|
| 1810 | - |
|
| 1811 | - case mb_exc of
|
|
| 1812 | - Just e -> throwIO e
|
|
| 1813 | - Nothing -> readTVarIO visited_var
|
|
| 1814 | 1789 | |
| 1815 | - where
|
|
| 1816 | - wait_done exc_var worklist pending =
|
|
| 1817 | - -- this txn retries until all work is done or an exception is signaled
|
|
| 1818 | - atomically $ do
|
|
| 1819 | - readTVar exc_var >>= \case
|
|
| 1820 | - Just e -> return (Just e)
|
|
| 1821 | - Nothing -> do
|
|
| 1822 | - empty_worklist <- isEmptyTQueue worklist
|
|
| 1823 | - empty_pending <- Set.null <$> readTVar pending
|
|
| 1824 | - check (empty_worklist && empty_pending)
|
|
| 1825 | - return Nothing
|
|
| 1826 | - |
|
| 1827 | - coordinator ds_env exc_var visvar worklist pendvar threads = forever $ do
|
|
| 1828 | - mb_node_to_expand <- atomically $ do
|
|
| 1829 | - node <- readTQueue worklist
|
|
| 1830 | - let k = key node
|
|
| 1831 | - |
|
| 1832 | - visited <- readTVar visvar
|
|
| 1833 | - pending <- readTVar pendvar
|
|
| 1834 | - |
|
| 1835 | - if (k `Set.member` pending || k `Map.member` visited)
|
|
| 1836 | - then return Nothing
|
|
| 1837 | - else do
|
|
| 1838 | - -- must add to pending in the same transaction as worklist dequeue,
|
|
| 1839 | - -- otherwise the main thread may find both the worklist and pending
|
|
| 1840 | - -- lists empty and exit prematurely.
|
|
| 1841 | - modifyTVar' pendvar (Set.insert k)
|
|
| 1842 | - return (Just (k, node))
|
|
| 1843 | - |
|
| 1844 | - case mb_node_to_expand of
|
|
| 1845 | - Nothing -> return ()
|
|
| 1846 | - Just (k, node) -> do
|
|
| 1847 | - tid <- MC.mask_ $ forkIOWithUnmask $ \unmask ->
|
|
| 1848 | - unmask (withLocalTmpFSMake (ds_make_env ds_env) $ \make_env ->
|
|
| 1849 | - worker ds_env{ds_make_env = make_env} visvar worklist pendvar k node)
|
|
| 1850 | - `MC.catch` \case
|
|
| 1851 | - e | Just (_ :: SomeAsyncException) <- fromException e
|
|
| 1852 | - -> throwIO e -- async exceptions like KillThread get thrown
|
|
| 1853 | - | otherwise -- exceptions in workers are written for main thread
|
|
| 1854 | - -> atomically (modifyTVar' exc_var (<|> Just e))
|
|
| 1855 | - |
|
| 1856 | - atomically $ modifyTVar' threads (tid:)
|
|
| 1857 | - |
|
| 1858 | - worker ds_env@DownsweepEnv{..} visvar worklist pendvar k node =
|
|
| 1859 | - withAbstractSem (compile_sem ds_make_env) $ do
|
|
| 1860 | - r <- runDownsweepM ds_env $
|
|
| 1861 | - expand node -- do the main work!
|
|
| 1862 | - |
|
| 1863 | - atomically $ do
|
|
| 1864 | - case r of
|
|
| 1865 | - NSkip ->
|
|
| 1866 | - modifyTVar' visvar (Map.insert k NSkip)
|
|
| 1867 | - NSuccess (v,ns) -> do
|
|
| 1868 | - modifyTVar' visvar (Map.insert k (NSuccess v))
|
|
| 1869 | - mapM_ (writeTQueue worklist) ns
|
|
| 1870 | - |
|
| 1871 | - modifyTVar' pendvar (Set.delete k)
|
|
| 1790 | + let
|
|
| 1791 | + conc :: Concurrency
|
|
| 1792 | + conc = ds_concurrency ds_env
|
|
| 1793 | + |
|
| 1794 | + expand_node :: ConcurrentWorkerEnv -> n -> IO (NodeRes v, [n])
|
|
| 1795 | + expand_node worker_env node = do
|
|
| 1796 | + result <- withConcurrency conc $
|
|
| 1797 | + runDownsweepM (setDownsweepWorkerEnv worker_env ds_env) (expand node)
|
|
| 1798 | + pure $ case result of
|
|
| 1799 | + NSkip -> (NSkip, [])
|
|
| 1800 | + NSuccess (val, new_work) -> (NSuccess val, new_work)
|
|
| 1801 | + |
|
| 1802 | + concurrentTraversal_DF "downsweep_worker" conc (viewHscWorkerEnv $ ds_hsc_env ds_env)
|
|
| 1803 | + (fromMaybe mempty base_map) roots key expand_node
|
|
| 1804 | + |
|
| 1805 | +setDownsweepWorkerEnv :: ConcurrentWorkerEnv -> DownsweepEnv -> DownsweepEnv
|
|
| 1806 | +setDownsweepWorkerEnv work_env env =
|
|
| 1807 | + env { ds_hsc_env = setHscWorkerEnv work_env (ds_hsc_env env) }
|
|
| 1872 | 1808 | |
| 1873 | 1809 | {-
|
| 1874 | 1810 | Note [Downsweep Control Flow and Caching]
|
| ... | ... | @@ -1963,70 +1899,10 @@ things, and that processing can often be costly (e.g. see `expandModuleSummary`) |
| 1963 | 1899 | |
| 1964 | 1900 | We leverage multiple threads in this traversal to expand more than one module
|
| 1965 | 1901 | at once, respecting -j<N> to mean we never expand more than N modules at once.
|
| 1966 | -The parallel downsweep is all handled by `parDfsBuild` as follows:
|
|
| 1967 | - |
|
| 1968 | -- We launch a thread for every module we discover that needs to be
|
|
| 1969 | - expanded in the `coordinator` thread, popping it from the worklist
|
|
| 1970 | -- Every launched `worker` thread blocks waiting for a semaphore token
|
|
| 1971 | - (`withAbstractSem`) to respect -j<N>
|
|
| 1972 | -- The main thread waits until both the worklist and pending list is
|
|
| 1973 | - cleared, atomically.
|
|
| 1974 | - |
|
| 1975 | -STM is used crucially to guarantee e.g. we don't have race conditions
|
|
| 1976 | -between taking from the worklist and writing to the pending list while
|
|
| 1977 | -checking whether they are clear.
|
|
| 1978 | - |
|
| 1979 | -Exceptions are bubbled up to the main thread. The "main" thread, which is
|
|
| 1980 | -typically waiting for the worklist+pending lists to be clear, instead gets
|
|
| 1981 | -unblocked by this exception (signaled in `exc_var`) and re-throws it.
|
|
| 1982 | --}
|
|
| 1983 | 1902 | |
| 1984 | ---------------------------------------------------------------------------------
|
|
| 1985 | --- * Concurrent utilities
|
|
| 1986 | ---------------------------------------------------------------------------------
|
|
| 1987 | - |
|
| 1988 | --- | Map an action over a list using the parallelism pipeline system.
|
|
| 1989 | --- Create bundles of the list elems wrapped in a 'MakeAction' that uses
|
|
| 1990 | --- 'withAbstractSem' to wait for a free slot, limiting the number of
|
|
| 1991 | --- concurrently computed summaries to the value of the @-j@ option or the slots
|
|
| 1992 | --- allocated by the job server, if that is used.
|
|
| 1993 | ---
|
|
| 1994 | --- The 'MakeAction' returns 'Maybe', which is not handled as an error, because
|
|
| 1995 | --- 'runLoop' only sets it to 'Nothing' when an exception was thrown, so the
|
|
| 1996 | --- result won't be read anyway here.
|
|
| 1997 | ---
|
|
| 1998 | --- To emulate the current behavior, we funnel exceptions past the concurrency
|
|
| 1999 | --- barrier and rethrow the first one afterwards.
|
|
| 2000 | -mapConcDS ::
|
|
| 2001 | - WorkerLimit ->
|
|
| 2002 | - Int {-^ Batch size -} ->
|
|
| 2003 | - MakeEnv ->
|
|
| 2004 | - (HscEnv -> a -> IO b) ->
|
|
| 2005 | - [a] ->
|
|
| 2006 | - IO ([b])
|
|
| 2007 | -mapConcDS n_jobs bundle_size make_env run_action xs = do
|
|
| 2008 | - (actions, get_results) <- unzip <$> mapM action_and_result (zip [1..] bundles)
|
|
| 2009 | - runAllPipelines n_jobs make_env actions
|
|
| 2010 | - (sequence . catMaybes <$> sequence get_results) >>= \case
|
|
| 2011 | - Right results -> pure (concat results)
|
|
| 2012 | - Left exc -> throwIO exc
|
|
| 2013 | - where
|
|
| 2014 | - bundles = mk_bundles xs
|
|
| 2015 | - |
|
| 2016 | - mk_bundles = unfoldr \case
|
|
| 2017 | - [] -> Nothing
|
|
| 2018 | - ts -> Just (splitAt bundle_size ts)
|
|
| 2019 | - |
|
| 2020 | - action_and_result (log_queue_id, ts) = do
|
|
| 2021 | - res_var <- liftIO newEmptyMVar
|
|
| 2022 | - pure $! (MakeAction (action log_queue_id ts) res_var, readMVar res_var)
|
|
| 2023 | - |
|
| 2024 | - action log_queue_id target_bundle = do
|
|
| 2025 | - env@MakeEnv {compile_sem} <- ask
|
|
| 2026 | - lift $ lift $
|
|
| 2027 | - withAbstractSem compile_sem $
|
|
| 2028 | - withLoggerHsc log_queue_id env \ lcl_hsc_env ->
|
|
| 2029 | - MC.try (mapM (run_action lcl_hsc_env) target_bundle) >>= \case
|
|
| 2030 | - Left e | Just (_ :: SomeAsyncException) <- fromException e ->
|
|
| 2031 | - throwIO e
|
|
| 2032 | - a -> pure a |
|
| 1903 | +We use the concurrent scheduling abstraction from GHC.Driver.Concurrency
|
|
| 1904 | +('concurrentTraversal_DF'). Each time we discover a new node, a worker is
|
|
| 1905 | +spawned to expand it. After each expansion completes, the resulting children
|
|
| 1906 | +nodes are pushed onto the worklist. With -j1 no threads are involved: each
|
|
| 1907 | +expansion runs in sequence.
|
|
| 1908 | +-} |
| ... | ... | @@ -63,8 +63,9 @@ import GHC.Driver.Env |
| 63 | 63 | import GHC.Driver.Errors
|
| 64 | 64 | import GHC.Driver.Errors.Types
|
| 65 | 65 | import GHC.Driver.Main
|
| 66 | -import GHC.Driver.MakeSem
|
|
| 67 | 66 | import GHC.Driver.Downsweep
|
| 67 | +import GHC.Driver.Concurrency
|
|
| 68 | +import GHC.Driver.Config.Concurrency
|
|
| 68 | 69 | import GHC.Driver.MakeAction
|
| 69 | 70 | |
| 70 | 71 | import GHC.Types.UnresolvedImport
|
| ... | ... | @@ -156,22 +157,20 @@ depanal :: GhcMonad m => |
| 156 | 157 | depanal excluded_mods allow_dup_roots = do
|
| 157 | 158 | hsc_env <- getSession
|
| 158 | 159 | let sec = initSourceErrorContext (hsc_dflags hsc_env)
|
| 159 | - (errs, mod_graph) <- depanalE mkUnknownDiagnostic Nothing excluded_mods allow_dup_roots
|
|
| 160 | + (errs, mod_graph) <- depanalE excluded_mods allow_dup_roots
|
|
| 160 | 161 | if isEmptyMessages errs
|
| 161 | 162 | then pure mod_graph
|
| 162 | 163 | else throwErrors sec (fmap GhcDriverMessage errs)
|
| 163 | 164 | |
| 164 | 165 | -- | Perform dependency analysis like in 'depanal'.
|
| 165 | 166 | -- In case of errors, the errors and an empty module graph are returned.
|
| 166 | -depanalE :: GhcMonad m => -- New for #17459
|
|
| 167 | - (GhcMessage -> AnyGhcDiagnostic)
|
|
| 168 | - -> Maybe Messager
|
|
| 169 | - -> [ModuleName] -- ^ excluded modules
|
|
| 167 | +depanalE :: GhcMonad m =>
|
|
| 168 | + [ModuleName] -- ^ excluded modules
|
|
| 170 | 169 | -> Bool -- ^ allow duplicate roots
|
| 171 | 170 | -> m (DriverMessages, ModuleGraph)
|
| 172 | -depanalE diag_wrapper msg excluded_mods allow_dup_roots = do
|
|
| 171 | +depanalE excluded_mods allow_dup_roots = do
|
|
| 173 | 172 | hsc_env <- getSession
|
| 174 | - (errs, mod_graph) <- depanalPartial diag_wrapper msg excluded_mods allow_dup_roots
|
|
| 173 | + (errs, mod_graph) <- depanalPartial excluded_mods allow_dup_roots
|
|
| 175 | 174 | if isEmptyMessages errs
|
| 176 | 175 | then do
|
| 177 | 176 | hsc_env <- getSession
|
| ... | ... | @@ -209,13 +208,11 @@ depanalE diag_wrapper msg excluded_mods allow_dup_roots = do |
| 209 | 208 | -- new module graph.
|
| 210 | 209 | depanalPartial
|
| 211 | 210 | :: GhcMonad m
|
| 212 | - => (GhcMessage -> AnyGhcDiagnostic)
|
|
| 213 | - -> Maybe Messager
|
|
| 214 | - -> [ModuleName] -- ^ excluded modules
|
|
| 211 | + => [ModuleName] -- ^ excluded modules
|
|
| 215 | 212 | -> Bool -- ^ allow duplicate roots
|
| 216 | 213 | -> m (DriverMessages, ModuleGraph)
|
| 217 | 214 | -- ^ possibly empty 'Bag' of errors and a module graph.
|
| 218 | -depanalPartial diag_wrapper msg excluded_mods allow_dup_roots = do
|
|
| 215 | +depanalPartial excluded_mods allow_dup_roots = do
|
|
| 219 | 216 | hsc_env <- getSession
|
| 220 | 217 | let
|
| 221 | 218 | targets = hsc_targets hsc_env
|
| ... | ... | @@ -234,7 +231,7 @@ depanalPartial diag_wrapper msg excluded_mods allow_dup_roots = do |
| 234 | 231 | liftIO $ flushFinderCaches (hsc_FC hsc_env) (hsc_unit_env hsc_env)
|
| 235 | 232 | |
| 236 | 233 | (errs, mod_graph) <- liftIO $ downsweep
|
| 237 | - hsc_env diag_wrapper msg (mgModSummaries old_graph) Nothing
|
|
| 234 | + hsc_env (mgModSummaries old_graph) Nothing
|
|
| 238 | 235 | excluded_mods allow_dup_roots
|
| 239 | 236 | return (unionManyMessages errs, mod_graph)
|
| 240 | 237 | |
| ... | ... | @@ -438,7 +435,7 @@ loadWithCache :: GhcMonad m => Maybe ModIfaceCache -- ^ Instructions about how t |
| 438 | 435 | -> m SuccessFlag
|
| 439 | 436 | loadWithCache cache diag_wrapper how_much = do
|
| 440 | 437 | msg <- mkBatchMsg <$> getSession
|
| 441 | - (errs, mod_graph) <- depanalE diag_wrapper (Just msg) [] False -- #17459
|
|
| 438 | + (errs, mod_graph) <- depanalE [] False -- #17459
|
|
| 442 | 439 | success <- load' cache how_much diag_wrapper (Just msg) mod_graph
|
| 443 | 440 | hsc_env <- getSession
|
| 444 | 441 | let sec = initSourceErrorContext (hsc_dflags hsc_env)
|
| ... | ... | @@ -840,14 +837,15 @@ The Algorithm |
| 840 | 837 | a pair of an `IO a` action and a `MVar a`, where to place the result.
|
| 841 | 838 | The list is sorted topologically, so can be executed in order without fear of
|
| 842 | 839 | blocking.
|
| 843 | -* runPipelines takes this list and eventually passes it to runLoop which executes
|
|
| 844 | - each action and places the result into the right MVar.
|
|
| 845 | -* The amount of parallelism is controlled by a semaphore. This is just used around the
|
|
| 846 | - module compilation step, so that only the right number of modules are compiled at
|
|
| 847 | - the same time which reduces overall memory usage and allocations.
|
|
| 848 | -* Each proper node has a LogQueue, which dictates where to send it's output.
|
|
| 849 | -* The LogQueue is placed into the LogQueueQueue when the action starts and a worker
|
|
| 850 | - thread processes the LogQueueQueue printing logs for each module in a stable order.
|
|
| 840 | +* runPipelines spawns one worker per action ('GHC.Driver.Concurrency.mapConcurrentWorkers'),
|
|
| 841 | + which executes the action and places the result into the right MVar.
|
|
| 842 | +* The amount of parallelism is controlled by a semaphore ('withMakeEnvConcurrency'). This is
|
|
| 843 | + just used around the module compilation step, so that only the right number of
|
|
| 844 | + modules are compiled at the same time which reduces overall memory usage and
|
|
| 845 | + allocations.
|
|
| 846 | +* Each worker has a LogQueue, which dictates where to send its output. A log
|
|
| 847 | + thread processes the LogQueues, printing logs for each module in a stable
|
|
| 848 | + order (the order in which the actions were spawned).
|
|
| 851 | 849 | * The result variable for an action producing `a` is of type `Maybe a`, therefore
|
| 852 | 850 | it is still filled on a failure. If a module fails to compile, the
|
| 853 | 851 | failure is propagated through the whole module graph and any modules which didn't
|
| ... | ... | @@ -1137,7 +1135,7 @@ interpretBuildPlan hug mhmi_cache old_hpt plan = do |
| 1137 | 1135 | !build_deps = getDependencies (map gwib_mod deps) build_map
|
| 1138 | 1136 | let loop_action = withCurrentUnit loop_unit $ do
|
| 1139 | 1137 | !_ <- wait_deps build_deps
|
| 1140 | - hsc_env <- asks hsc_env
|
|
| 1138 | + hsc_env <- asks me_hsc_env
|
|
| 1141 | 1139 | let mns :: [ModuleName]
|
| 1142 | 1140 | mns = mapMaybe (nodeKeyModName . gwib_mod) deps
|
| 1143 | 1141 | |
| ... | ... | @@ -1180,7 +1178,7 @@ interpretBuildPlan hug mhmi_cache old_hpt plan = do |
| 1180 | 1178 | |
| 1181 | 1179 | withCurrentUnit :: UnitId -> RunMakeM a -> RunMakeM a
|
| 1182 | 1180 | withCurrentUnit uid = do
|
| 1183 | - local (\env -> env { hsc_env = hscSetActiveUnitId uid (hsc_env env)})
|
|
| 1181 | + local (\env -> env { me_hsc_env = hscSetActiveUnitId uid (me_hsc_env env)})
|
|
| 1184 | 1182 | |
| 1185 | 1183 | upsweep
|
| 1186 | 1184 | :: WorkerLimit -- ^ The number of workers we wish to run in parallel
|
| ... | ... | @@ -1556,10 +1554,11 @@ executeInstantiationNode k n deps uid iu = do |
| 1556 | 1554 | env <- ask
|
| 1557 | 1555 | -- Output of the logger is mediated by a central worker to
|
| 1558 | 1556 | -- avoid output interleaving
|
| 1559 | - msg <- asks env_messager
|
|
| 1560 | - wrapper <- asks diag_wrapper
|
|
| 1561 | - lift $ MaybeT $ withLoggerHsc k env $ \hsc_env ->
|
|
| 1562 | - let lcl_hsc_env = setHUG deps hsc_env
|
|
| 1557 | + msg <- asks me_messager
|
|
| 1558 | + wrapper <- asks me_diag_wrapper
|
|
| 1559 | + lift $ MaybeT $
|
|
| 1560 | + let hsc_env = me_hsc_env env
|
|
| 1561 | + lcl_hsc_env = setHUG deps hsc_env
|
|
| 1563 | 1562 | in wrapAction wrapper lcl_hsc_env $ do
|
| 1564 | 1563 | res <- upsweep_inst lcl_hsc_env msg k n uid iu
|
| 1565 | 1564 | cleanCurrentModuleTempFilesMaybe (hsc_logger hsc_env) (hsc_tmpfs hsc_env) (hsc_dflags hsc_env)
|
| ... | ... | @@ -1582,13 +1581,13 @@ executeCompileNode :: Int |
| 1582 | 1581 | -> ModuleNodeInfo
|
| 1583 | 1582 | -> RunMakeM HomeModInfo
|
| 1584 | 1583 | executeCompileNode k n !old_hmi hug mrehydrate_mods mni = do
|
| 1585 | - me@MakeEnv{..} <- ask
|
|
| 1584 | + make_env <- ask
|
|
| 1586 | 1585 | -- Rehydrate any dependencies if this module had a boot file or is a signature file.
|
| 1587 | - lift $ MaybeT (withAbstractSem compile_sem $ withLoggerHsc k me $ \hsc_env -> do
|
|
| 1586 | + lift $ MaybeT (withMakeEnvConcurrency make_env $ \hsc_env -> do
|
|
| 1588 | 1587 | hsc_env' <- liftIO $ maybeRehydrateBefore (setHUG hug hsc_env) mni fixed_mrehydrate_mods
|
| 1589 | 1588 | case mni of
|
| 1590 | - ModuleNodeCompile mod -> executeCompileNodeWithSource hsc_env' me mod
|
|
| 1591 | - ModuleNodeFixed key loc -> executeCompileNodeFixed hsc_env' me key loc
|
|
| 1589 | + ModuleNodeCompile mod -> executeCompileNodeWithSource hsc_env' make_env mod
|
|
| 1590 | + ModuleNodeFixed key loc -> executeCompileNodeFixed hsc_env' make_env key loc
|
|
| 1592 | 1591 | )
|
| 1593 | 1592 | |
| 1594 | 1593 | where
|
| ... | ... | @@ -1601,9 +1600,9 @@ executeCompileNode k n !old_hmi hug mrehydrate_mods mni = do |
| 1601 | 1600 | _ -> mrehydrate_mods
|
| 1602 | 1601 | |
| 1603 | 1602 | executeCompileNodeFixed :: HscEnv -> MakeEnv -> ModNodeKeyWithUid -> ModLocation -> IO (Maybe HomeModInfo)
|
| 1604 | - executeCompileNodeFixed hsc_env MakeEnv{diag_wrapper, env_messager} mod loc =
|
|
| 1605 | - wrapAction diag_wrapper hsc_env $ do
|
|
| 1606 | - forM_ env_messager $ \hscMessage -> hscMessage hsc_env (k, n) UpToDate (ModuleNode [] (ModuleNodeFixed mod loc))
|
|
| 1603 | + executeCompileNodeFixed hsc_env MakeEnv{me_diag_wrapper, me_messager} mod loc =
|
|
| 1604 | + wrapAction me_diag_wrapper hsc_env $ do
|
|
| 1605 | + forM_ me_messager $ \hscMessage -> hscMessage hsc_env (k, n) UpToDate (ModuleNode [] (ModuleNodeFixed mod loc))
|
|
| 1607 | 1606 | read_result <- readIface (hsc_hooks hsc_env) (hsc_logger hsc_env) (hsc_dflags hsc_env) (hsc_NC hsc_env) (mnkToModule mod) (ml_hi_file loc)
|
| 1608 | 1607 | let sec = initSourceErrorContext (hsc_dflags hsc_env)
|
| 1609 | 1608 | case read_result of
|
| ... | ... | @@ -1619,7 +1618,7 @@ executeCompileNode k n !old_hmi hug mrehydrate_mods mni = do |
| 1619 | 1618 | return (HomeModInfo iface details hm_linkable)
|
| 1620 | 1619 | |
| 1621 | 1620 | executeCompileNodeWithSource :: HscEnv -> MakeEnv -> ModSummary -> IO (Maybe HomeModInfo)
|
| 1622 | - executeCompileNodeWithSource hsc_env MakeEnv{diag_wrapper, env_messager} mod = do
|
|
| 1621 | + executeCompileNodeWithSource hsc_env MakeEnv{me_diag_wrapper, me_messager} mod = do
|
|
| 1623 | 1622 | let -- Use the cached DynFlags which includes OPTIONS_GHC pragmas
|
| 1624 | 1623 | lcl_dynflags = ms_hspp_opts mod
|
| 1625 | 1624 | let lcl_hsc_env =
|
| ... | ... | @@ -1628,8 +1627,8 @@ executeCompileNode k n !old_hmi hug mrehydrate_mods mni = do |
| 1628 | 1627 | hsc_env
|
| 1629 | 1628 | -- Compile the module, locking with a semaphore to avoid too many modules
|
| 1630 | 1629 | -- being compiled at the same time leading to high memory usage.
|
| 1631 | - wrapAction diag_wrapper lcl_hsc_env $ do
|
|
| 1632 | - res <- upsweep_mod lcl_hsc_env env_messager old_hmi mod k n
|
|
| 1630 | + wrapAction me_diag_wrapper lcl_hsc_env $ do
|
|
| 1631 | + res <- upsweep_mod lcl_hsc_env me_messager old_hmi mod k n
|
|
| 1633 | 1632 | cleanCurrentModuleTempFilesMaybe (hsc_logger hsc_env) (hsc_tmpfs hsc_env) lcl_dynflags
|
| 1634 | 1633 | return res
|
| 1635 | 1634 | |
| ... | ... | @@ -1853,15 +1852,15 @@ Also closely related are |
| 1853 | 1852 | -}
|
| 1854 | 1853 | |
| 1855 | 1854 | executeLinkNode :: HomeUnitGraph -> (Int, Int) -> UnitId -> [NodeKey] -> RunMakeM ()
|
| 1856 | -executeLinkNode hug kn@(k, _) uid deps = do
|
|
| 1855 | +executeLinkNode hug kn uid deps = do
|
|
| 1857 | 1856 | withCurrentUnit uid $ do
|
| 1858 | 1857 | make_env@MakeEnv{..} <- ask
|
| 1859 | - let dflags = hsc_dflags hsc_env
|
|
| 1860 | - msg' = (\messager -> \recomp -> messager hsc_env kn recomp (LinkNode deps uid)) <$> env_messager
|
|
| 1858 | + let dflags = hsc_dflags me_hsc_env
|
|
| 1859 | + msg' = (\messager -> \recomp -> messager me_hsc_env kn recomp (LinkNode deps uid)) <$> me_messager
|
|
| 1861 | 1860 | |
| 1862 | - linkresult <- lift $ MaybeT $ withAbstractSem compile_sem $ withLoggerHsc k make_env $ \lcl_hsc_env -> do
|
|
| 1861 | + linkresult <- lift $ MaybeT $ withMakeEnvConcurrency make_env $ \lcl_hsc_env -> do
|
|
| 1863 | 1862 | let hsc_env' = setHUG hug lcl_hsc_env
|
| 1864 | - wrapAction diag_wrapper hsc_env' $ do
|
|
| 1863 | + wrapAction me_diag_wrapper hsc_env' $ do
|
|
| 1865 | 1864 | link (ghcLink dflags)
|
| 1866 | 1865 | hsc_env'
|
| 1867 | 1866 | True -- We already decided to link
|
| 1 | -{-# LANGUAGE CPP #-}
|
|
| 2 | 1 | module GHC.Driver.MakeAction
|
| 3 | 2 | ( MakeAction(..)
|
| 4 | 3 | , RunMakeM
|
| ... | ... | @@ -7,79 +6,41 @@ module GHC.Driver.MakeAction |
| 7 | 6 | -- * Running the pipelines
|
| 8 | 7 | , runAllPipelines
|
| 9 | 8 | , runPipelines
|
| 10 | - -- * Worker limit
|
|
| 11 | - , WorkerLimit(..)
|
|
| 12 | - , mkWorkerLimit
|
|
| 13 | - , runWorkerLimit
|
|
| 14 | 9 | -- * Utility
|
| 15 | - , withLoggerHsc
|
|
| 16 | - , withParLog
|
|
| 17 | - , withLocalTmpFS
|
|
| 18 | - , withLocalTmpFSMake
|
|
| 10 | + , withMakeEnvConcurrency
|
|
| 11 | + , withWorkerLimitHsc
|
|
| 12 | + , viewHscWorkerEnv
|
|
| 13 | + , setHscWorkerEnv
|
|
| 19 | 14 | ) where
|
| 20 | 15 | |
| 21 | 16 | import GHC.Prelude
|
| 22 | -import GHC.Driver.DynFlags
|
|
| 23 | 17 | |
| 24 | -import GHC.Driver.Monad
|
|
| 18 | +import GHC.Driver.Concurrency
|
|
| 19 | +import GHC.Driver.Config.Concurrency
|
|
| 25 | 20 | import GHC.Driver.Env
|
| 26 | 21 | import GHC.Driver.Errors.Types
|
| 27 | 22 | import GHC.Driver.Messager
|
| 28 | -import GHC.Driver.MakeSem
|
|
| 29 | - |
|
| 30 | -#if defined(wasm32_HOST_ARCH) || defined(javascript_HOST_ARCH)
|
|
| 31 | -import System.Semaphore
|
|
| 32 | - ( SemaphoreIdentifier )
|
|
| 33 | -#else
|
|
| 34 | -import System.Semaphore
|
|
| 35 | - ( SemaphoreError, SemaphoreIdentifier )
|
|
| 36 | -#endif
|
|
| 37 | - |
|
| 38 | -#if !(defined(wasm32_HOST_ARCH) || defined(javascript_HOST_ARCH))
|
|
| 39 | -import GHC.Driver.Config.Diagnostic ( initDiagOpts, initPrintConfig )
|
|
| 40 | -import GHC.Driver.Errors ( printOrThrowDiagnostics )
|
|
| 41 | -import GHC.Types.Error ( singleMessage )
|
|
| 42 | -import GHC.Types.SrcLoc ( noSrcSpan )
|
|
| 43 | -import GHC.Utils.Error ( mkPlainMsgEnvelope )
|
|
| 44 | -#endif
|
|
| 45 | -import GHC.Utils.Logger
|
|
| 46 | -import GHC.Utils.TmpFs
|
|
| 23 | +import GHC.Driver.Monad
|
|
| 47 | 24 | |
| 48 | -#if defined(wasm32_HOST_ARCH) || defined(javascript_HOST_ARCH)
|
|
| 49 | -import Control.Concurrent ( ThreadId, killThread, forkIOWithUnmask )
|
|
| 50 | -#else
|
|
| 51 | -import Control.Concurrent ( newQSem, waitQSem, signalQSem, ThreadId, killThread, forkIOWithUnmask )
|
|
| 52 | -#endif
|
|
| 53 | 25 | import qualified GHC.Conc as CC
|
| 54 | 26 | import Control.Concurrent.MVar
|
| 55 | 27 | import Control.Monad
|
| 56 | 28 | import qualified Control.Monad.Catch as MC
|
| 57 | - |
|
| 58 | -#if defined(wasm32_HOST_ARCH) || defined(javascript_HOST_ARCH)
|
|
| 59 | -import GHC.Conc ( getNumProcessors )
|
|
| 60 | -#else
|
|
| 61 | -import GHC.Conc ( getNumProcessors, getNumCapabilities, setNumCapabilities )
|
|
| 62 | -#endif
|
|
| 63 | -import Control.Monad.Trans.Reader
|
|
| 64 | -import GHC.Driver.Pipeline.LogQueue
|
|
| 65 | -import Control.Concurrent.STM
|
|
| 66 | 29 | import Control.Monad.Trans.Maybe
|
| 30 | +import Control.Monad.Trans.Reader
|
|
| 67 | 31 | |
| 68 | 32 | --------------------------------------------------------------------------------
|
| 69 | 33 | -- * MakeEnv and MakeAction
|
| 70 | 34 | --------------------------------------------------------------------------------
|
| 71 | 35 | |
| 72 | 36 | -- | Environment used when compiling a module
|
| 73 | -data MakeEnv = MakeEnv { hsc_env :: !HscEnv -- The basic HscEnv which will be augmented for each module
|
|
| 74 | - , compile_sem :: !AbstractSem
|
|
| 75 | - -- Modify the environment for module k, with the supplied logger modification function.
|
|
| 76 | - -- For -j1, this wrapper doesn't do anything
|
|
| 77 | - -- For -jn, the wrapper initialised a log queue and then modifies the logger to pipe its output
|
|
| 78 | - -- into the log queue.
|
|
| 79 | - , withLogger :: forall a . Int -> ((Logger -> Logger) -> IO a) -> IO a
|
|
| 80 | - , env_messager :: !(Maybe Messager)
|
|
| 81 | - , diag_wrapper :: GhcMessage -> AnyGhcDiagnostic
|
|
| 82 | - }
|
|
| 37 | +data MakeEnv =
|
|
| 38 | + MakeEnv
|
|
| 39 | + { me_hsc_env :: !HscEnv -- The basic HscEnv which will be augmented for each module
|
|
| 40 | + , me_concurrency :: !Concurrency
|
|
| 41 | + , me_messager :: !(Maybe Messager)
|
|
| 42 | + , me_diag_wrapper :: GhcMessage -> AnyGhcDiagnostic
|
|
| 43 | + }
|
|
| 83 | 44 | |
| 84 | 45 | -- | Come up with a 'MakeEnv' based on the given 'WorkerLimit'.
|
| 85 | 46 | -- For -j1, it will be a trivial 'MakeEnv' not prepared for parallelism.
|
| ... | ... | @@ -91,47 +52,14 @@ withMakeEnv |
| 91 | 52 | -> Maybe Messager -- ^ Optional custom messager to use to report progress
|
| 92 | 53 | -> (MakeEnv -> IO r) -> IO r
|
| 93 | 54 | withMakeEnv worker_limit hsc_env diag_wrapper mHscMessager act =
|
| 94 | - if isWorkerLimitSequential worker_limit
|
|
| 95 | - then withSeqMakeEnv
|
|
| 96 | - else withParMakeEnv
|
|
| 97 | - where
|
|
| 98 | - withSeqMakeEnv = do
|
|
| 99 | - let seq_env = MakeEnv
|
|
| 100 | - { hsc_env = hsc_env
|
|
| 101 | - , withLogger = \_ k -> k id
|
|
| 102 | - , compile_sem = AbstractSem (return ()) (return ())
|
|
| 103 | - , env_messager = mHscMessager
|
|
| 104 | - , diag_wrapper = diag_wrapper
|
|
| 105 | - }
|
|
| 106 | - act seq_env
|
|
| 107 | - |
|
| 108 | - withParMakeEnv = do
|
|
| 109 | - -- A variable which we write to when an error has happened and we have to tell the
|
|
| 110 | - -- logging thread to gracefully shut down.
|
|
| 111 | - stopped_var <- newTVarIO False
|
|
| 112 | - -- The queue of LogQueues which actions are able to write to. When an action starts it
|
|
| 113 | - -- will add it's LogQueue into this queue.
|
|
| 114 | - log_queue_queue_var <- newTVarIO newLogQueueQueue
|
|
| 115 | - -- Thread which coordinates the printing of logs
|
|
| 116 | - wait_log_thread <- logThread (hsc_logger hsc_env) stopped_var log_queue_queue_var
|
|
| 117 | - |
|
| 118 | - |
|
| 119 | - -- Make the logger thread-safe, in case there is some output which isn't sent via the LogQueue.
|
|
| 120 | - thread_safe_logger <- liftIO $ makeThreadSafe (hsc_logger hsc_env)
|
|
| 121 | - let thread_safe_hsc_env = hsc_env { hsc_logger = thread_safe_logger }
|
|
| 122 | - |
|
| 123 | - runWorkerLimit (hsc_logger hsc_env) (hsc_dflags hsc_env) worker_limit $ \abstract_sem -> do
|
|
| 124 | - let env = MakeEnv { hsc_env = thread_safe_hsc_env
|
|
| 125 | - , withLogger = withParLog log_queue_queue_var
|
|
| 126 | - , compile_sem = abstract_sem
|
|
| 127 | - , env_messager = mHscMessager
|
|
| 128 | - , diag_wrapper = diag_wrapper
|
|
| 129 | - }
|
|
| 130 | - -- Reset the number of capabilities once the upsweep ends.
|
|
| 131 | - r <- act env
|
|
| 132 | - atomically $ writeTVar stopped_var True
|
|
| 133 | - wait_log_thread
|
|
| 134 | - pure r
|
|
| 55 | + withWorkerLimitHsc hsc_env worker_limit $ \ conc hsc_env' ->
|
|
| 56 | + act $
|
|
| 57 | + MakeEnv
|
|
| 58 | + { me_hsc_env = hsc_env'
|
|
| 59 | + , me_concurrency = conc
|
|
| 60 | + , me_messager = mHscMessager
|
|
| 61 | + , me_diag_wrapper = diag_wrapper
|
|
| 62 | + }
|
|
| 135 | 63 | |
| 136 | 64 | -- ** MakeAction ---------------------------------------------------------------
|
| 137 | 65 | |
| ... | ... | @@ -139,9 +67,6 @@ data MakeAction = forall a . MakeAction !(RunMakeM a) !(MVar (Maybe a)) |
| 139 | 67 | |
| 140 | 68 | type RunMakeM a = ReaderT MakeEnv (MaybeT IO) a
|
| 141 | 69 | |
| 142 | -waitMakeAction :: MakeAction -> IO ()
|
|
| 143 | -waitMakeAction (MakeAction _ mvar) = () <$ readMVar mvar
|
|
| 144 | - |
|
| 145 | 70 | --------------------------------------------------------------------------------
|
| 146 | 71 | -- * Running the pipelines
|
| 147 | 72 | --------------------------------------------------------------------------------
|
| ... | ... | @@ -155,149 +80,51 @@ runPipelines |
| 155 | 80 | runPipelines n_job hsc_env diag_wrapper mHscMessager all_pipelines = do
|
| 156 | 81 | liftIO $ label_self "main --make thread"
|
| 157 | 82 | withMakeEnv n_job hsc_env diag_wrapper mHscMessager $ \make_env -> do
|
| 158 | - runAllPipelines n_job make_env all_pipelines
|
|
| 83 | + runAllPipelines make_env all_pipelines
|
|
| 159 | 84 | where
|
| 160 | 85 | label_self :: String -> IO ()
|
| 161 | 86 | label_self thread_name = do
|
| 162 | 87 | self_tid <- CC.myThreadId
|
| 163 | 88 | CC.labelThread self_tid thread_name
|
| 164 | 89 | |
| 165 | --- | Run the given actions and then wait for them all to finish.
|
|
| 166 | -runAllPipelines :: WorkerLimit -> MakeEnv -> [MakeAction] -> IO ()
|
|
| 167 | -runAllPipelines worker_limit env acts = do
|
|
| 168 | - let single_worker = isWorkerLimitSequential worker_limit
|
|
| 169 | - spawn_actions :: IO [ThreadId]
|
|
| 170 | - spawn_actions = if single_worker
|
|
| 171 | - then (:[]) <$> (forkIOWithUnmask $ \unmask -> void $ runLoop (\io -> io unmask) env acts)
|
|
| 172 | - else runLoop forkIOWithUnmask env acts
|
|
| 173 | - |
|
| 174 | - kill_actions :: [ThreadId] -> IO ()
|
|
| 175 | - kill_actions tids = mapM_ killThread tids
|
|
| 176 | - |
|
| 177 | - MC.bracket spawn_actions kill_actions $ \_ -> do
|
|
| 178 | - mapM_ waitMakeAction acts
|
|
| 179 | - |
|
| 180 | --- | Execute each action in order, limiting the amount of parallelism by the given
|
|
| 181 | --- semaphore.
|
|
| 182 | -runLoop :: (((forall a. IO a -> IO a) -> IO ()) -> IO a) -> MakeEnv -> [MakeAction] -> IO [a]
|
|
| 183 | -runLoop _ _env [] = return []
|
|
| 184 | -runLoop fork_thread env (MakeAction act res_var :acts) = do
|
|
| 185 | - |
|
| 186 | - -- withLocalTmpFs has to occur outside of fork to remain deterministic
|
|
| 187 | - new_thread <- withLocalTmpFSMake env $ \lcl_env ->
|
|
| 188 | - MC.mask_ $
|
|
| 189 | - fork_thread $ \unmask -> (do
|
|
| 190 | - mres <- (unmask $ run_pipeline lcl_env act)
|
|
| 191 | - `MC.onException` (putMVar res_var Nothing) -- Defensive: If there's an unhandled exception then still signal the failure.
|
|
| 192 | - putMVar res_var mres)
|
|
| 193 | - threads <- runLoop fork_thread env acts
|
|
| 194 | - return (new_thread : threads)
|
|
| 195 | - where
|
|
| 196 | - run_pipeline :: MakeEnv -> RunMakeM a -> IO (Maybe a)
|
|
| 197 | - run_pipeline env p = runMaybeT (runReaderT p env)
|
|
| 198 | - |
|
| 199 | ---------------------------------------------------------------------------------
|
|
| 200 | --- * Worker Limit
|
|
| 201 | ---------------------------------------------------------------------------------
|
|
| 202 | - |
|
| 203 | --- | This describes what we use to limit the number of jobs, either we limit it
|
|
| 204 | --- ourselves to a specific number or we have an external parallelism semaphore
|
|
| 205 | --- limit it for us.
|
|
| 206 | -data WorkerLimit
|
|
| 207 | - = NumProcessorsLimit Int
|
|
| 208 | - | JSemLimit
|
|
| 209 | - SemaphoreIdentifier
|
|
| 210 | - -- ^ Semaphore identifier from @-jsem@
|
|
| 211 | - deriving Eq
|
|
| 212 | - |
|
| 213 | -mkWorkerLimit :: DynFlags -> IO WorkerLimit
|
|
| 214 | -mkWorkerLimit dflags =
|
|
| 215 | - case parMakeCount dflags of
|
|
| 216 | - Nothing -> pure $ num_procs 1
|
|
| 217 | - Just (ParMakeSemaphore h) -> pure (JSemLimit h)
|
|
| 218 | - Just ParMakeNumProcessors -> num_procs <$> getNumProcessors
|
|
| 219 | - Just (ParMakeThisMany n) -> pure $ num_procs n
|
|
| 220 | - where
|
|
| 221 | - num_procs x = NumProcessorsLimit (max 1 x)
|
|
| 222 | - |
|
| 223 | -isWorkerLimitSequential :: WorkerLimit -> Bool
|
|
| 224 | -isWorkerLimitSequential (NumProcessorsLimit x) = x <= 1
|
|
| 225 | -isWorkerLimitSequential (JSemLimit {}) = False
|
|
| 226 | - |
|
| 227 | -runWorkerLimit :: Logger -> DynFlags -> WorkerLimit -> (AbstractSem -> IO a) -> IO a
|
|
| 228 | -#if defined(wasm32_HOST_ARCH) || defined(javascript_HOST_ARCH)
|
|
| 229 | -runWorkerLimit _logger _dflags _ action = do
|
|
| 230 | - lock <- newMVar ()
|
|
| 231 | - action $ AbstractSem (takeMVar lock) (putMVar lock ())
|
|
| 232 | -#else
|
|
| 233 | -runWorkerLimit logger dflags worker_limit action = case worker_limit of
|
|
| 234 | - NumProcessorsLimit n_jobs ->
|
|
| 235 | - runNjobsAbstractSem n_jobs action
|
|
| 236 | - JSemLimit sem_ident -> do
|
|
| 237 | - result <- MC.try @_ @SemaphoreError $ runJSemAbstractSem sem_ident action
|
|
| 238 | - case result of
|
|
| 239 | - Right a -> return a
|
|
| 240 | - Left err -> do
|
|
| 241 | - let diag = DriverSemaphoreOpenFailure (checkBuildingCabalPackage dflags) err
|
|
| 242 | - msg = singleMessage $ mkPlainMsgEnvelope (initDiagOpts dflags) noSrcSpan diag
|
|
| 243 | - printOrThrowDiagnostics logger (initPrintConfig dflags) (initDiagOpts dflags) (GhcDriverMessage <$> msg)
|
|
| 244 | - runNjobsAbstractSem 1 action
|
|
| 245 | -#endif
|
|
| 246 | - |
|
| 247 | -#if !(defined(wasm32_HOST_ARCH) || defined(javascript_HOST_ARCH))
|
|
| 248 | -runNjobsAbstractSem :: Int -> (AbstractSem -> IO a) -> IO a
|
|
| 249 | -runNjobsAbstractSem n_jobs action = do
|
|
| 250 | - compile_sem <- newQSem n_jobs
|
|
| 251 | - n_capabilities <- getNumCapabilities
|
|
| 252 | - n_cpus <- getNumProcessors
|
|
| 253 | - let
|
|
| 254 | - asem = AbstractSem (waitQSem compile_sem) (signalQSem compile_sem)
|
|
| 255 | - set_num_caps n = unless (n_capabilities /= 1) $ setNumCapabilities n
|
|
| 256 | - updNumCapabilities = do
|
|
| 257 | - -- Setting number of capabilities more than
|
|
| 258 | - -- CPU count usually leads to high userspace
|
|
| 259 | - -- lock contention. #9221
|
|
| 260 | - set_num_caps $ min n_jobs n_cpus
|
|
| 261 | - resetNumCapabilities = set_num_caps n_capabilities
|
|
| 262 | - MC.bracket_ updNumCapabilities resetNumCapabilities $ action asem
|
|
| 263 | - |
|
| 264 | -#endif
|
|
| 90 | +-- | Run the given actions (assumed to be in dependency order) and wait for
|
|
| 91 | +-- them all to finish, rethrowing the first unhandled exception (in action order)
|
|
| 92 | +-- afterwards.
|
|
| 93 | +runAllPipelines :: MakeEnv -> [MakeAction] -> IO ()
|
|
| 94 | +runAllPipelines env acts =
|
|
| 95 | + void $
|
|
| 96 | + mapConcurrentWorkers "make_worker" (me_concurrency env) (viewHscWorkerEnv (me_hsc_env env))
|
|
| 97 | + ( \ work_env (MakeAction act res_var) -> do
|
|
| 98 | + let lcl_env = env { me_hsc_env = setHscWorkerEnv work_env (me_hsc_env env) }
|
|
| 99 | + mres <- runMaybeT (runReaderT act lcl_env)
|
|
| 100 | + `MC.onException` putMVar res_var Nothing
|
|
| 101 | + putMVar res_var mres )
|
|
| 102 | + acts
|
|
| 265 | 103 | |
| 266 | 104 | --------------------------------------------------------------------------------
|
| 267 | 105 | -- * Utility
|
| 268 | 106 | --------------------------------------------------------------------------------
|
| 269 | 107 | |
| 270 | -withLoggerHsc :: Int -> MakeEnv -> (HscEnv -> IO a) -> IO a
|
|
| 271 | -withLoggerHsc k MakeEnv{withLogger, hsc_env} cont = do
|
|
| 272 | - withLogger k $ \modifyLogger -> do
|
|
| 273 | - let lcl_logger = modifyLogger (hsc_logger hsc_env)
|
|
| 274 | - hsc_env' = hsc_env { hsc_logger = lcl_logger }
|
|
| 275 | - -- Run continuation with modified logger
|
|
| 276 | - cont hsc_env'
|
|
| 277 | - |
|
| 278 | -withParLog :: TVar LogQueueQueue -> Int -> ((Logger -> Logger) -> IO b) -> IO b
|
|
| 279 | -withParLog lqq_var k cont = do
|
|
| 280 | - let init_log = do
|
|
| 281 | - -- Make a new log queue
|
|
| 282 | - lq <- newLogQueue k
|
|
| 283 | - -- Add it into the LogQueueQueue
|
|
| 284 | - atomically $ initLogQueue lqq_var lq
|
|
| 285 | - return lq
|
|
| 286 | - finish_log lq = liftIO (finishLogQueue lq)
|
|
| 287 | - MC.bracket init_log finish_log $ \lq -> cont (pushLogHook (const (parLogAction lq)))
|
|
| 288 | - |
|
| 289 | -withLocalTmpFS :: TmpFs -> (TmpFs -> IO a) -> IO a
|
|
| 290 | -withLocalTmpFS tmpfs act = do
|
|
| 291 | - let initialiser = do
|
|
| 292 | - liftIO $ forkTmpFsFrom tmpfs
|
|
| 293 | - finaliser tmpfs_local = do
|
|
| 294 | - liftIO $ mergeTmpFsInto tmpfs_local tmpfs
|
|
| 295 | - -- Add remaining files which weren't cleaned up into local tmp fs for
|
|
| 296 | - -- clean-up later.
|
|
| 297 | - -- Clear the logQueue if this node had it's own log queue
|
|
| 298 | - MC.bracket initialiser finaliser act
|
|
| 299 | - |
|
| 300 | -withLocalTmpFSMake :: MakeEnv -> (MakeEnv -> IO a) -> IO a
|
|
| 301 | -withLocalTmpFSMake env k =
|
|
| 302 | - withLocalTmpFS (hsc_tmpfs (hsc_env env)) $ \lcl_tmpfs
|
|
| 303 | - -> k (env { hsc_env = (hsc_env env) { hsc_tmpfs = lcl_tmpfs }}) |
|
| 108 | +-- | A version of 'withWorkerLimit' taking an 'HscEnv'.
|
|
| 109 | +withWorkerLimitHsc :: HscEnv -> WorkerLimit -> (Concurrency -> HscEnv -> IO a) -> IO a
|
|
| 110 | +withWorkerLimitHsc hsc_env limit k =
|
|
| 111 | + withWorkerLimit (hsc_logger hsc_env) (hsc_tmpfs hsc_env)
|
|
| 112 | + (semaphoreOpenFailureHandler (hsc_logger hsc_env) (hsc_dflags hsc_env))
|
|
| 113 | + limit
|
|
| 114 | + (\conc work_env -> k conc (setHscWorkerEnv work_env hsc_env))
|
|
| 115 | + |
|
| 116 | +-- | Like 'withConcurrency', but retrieving the 'Concurrency' and 'HscEnv' from
|
|
| 117 | +-- the 'MakeEnv'.
|
|
| 118 | +withMakeEnvConcurrency :: MakeEnv -> (HscEnv -> IO a) -> IO a
|
|
| 119 | +withMakeEnvConcurrency env cont =
|
|
| 120 | + withConcurrency (me_concurrency env) (cont (me_hsc_env env))
|
|
| 121 | + |
|
| 122 | +-- | The local environment for a concurrent worker derived from an 'HscEnv'.
|
|
| 123 | +viewHscWorkerEnv :: HscEnv -> ConcurrentWorkerEnv
|
|
| 124 | +viewHscWorkerEnv hsc_env =
|
|
| 125 | + ConcurrentWorkerEnv { cwe_logger = hsc_logger hsc_env, cwe_tmpfs = hsc_tmpfs hsc_env }
|
|
| 126 | + |
|
| 127 | +-- | Set the local concurrent worker environment within an 'HscEnv'.
|
|
| 128 | +setHscWorkerEnv :: ConcurrentWorkerEnv -> HscEnv -> HscEnv
|
|
| 129 | +setHscWorkerEnv (ConcurrentWorkerEnv { cwe_logger = logger, cwe_tmpfs = tmpfs }) hsc_env =
|
|
| 130 | + hsc_env { hsc_logger = logger, hsc_tmpfs = tmpfs } |
| ... | ... | @@ -39,6 +39,7 @@ import GHC.Utils.Json |
| 39 | 39 | import System.Semaphore
|
| 40 | 40 | ( AbstractSem(..)
|
| 41 | 41 | , ClientSemaphore
|
| 42 | + , SemaphoreError
|
|
| 42 | 43 | , SemaphoreIdentifier
|
| 43 | 44 | , SemaphoreToken
|
| 44 | 45 | , openSemaphore
|
| ... | ... | @@ -534,18 +535,24 @@ makeJobserver sem_ident = do |
| 534 | 535 | |
| 535 | 536 | -- | Implement an abstract semaphore using a semaphore 'Jobserver'
|
| 536 | 537 | -- which queries the system semaphore of the given name for resources.
|
| 538 | +--
|
|
| 539 | +-- Returns 'Left' if the system semaphore could not be opened, in which case
|
|
| 540 | +-- the operation is not run at all. A 'SemaphoreError' arising after the
|
|
| 541 | +-- semaphore was successfully opened is thrown, not returned.
|
|
| 537 | 542 | runJSemAbstractSem :: SemaphoreIdentifier -- ^ the semaphore identifier (from @-jsem@)
|
| 538 | 543 | -> (AbstractSem -> IO a) -- ^ the operation to run
|
| 539 | 544 | -- which requires a semaphore
|
| 540 | - -> IO a
|
|
| 545 | + -> IO (Either SemaphoreError a)
|
|
| 541 | 546 | runJSemAbstractSem sem_ident action = MC.mask \ unmask -> do
|
| 542 | - (abs, cleanup) <- makeJobserver sem_ident
|
|
| 543 | - r <- try $ unmask $ action abs
|
|
| 544 | - case r of
|
|
| 545 | - Left (e1 :: MC.SomeException) -> do
|
|
| 546 | - (_ :: Either MC.SomeException ()) <- MC.try cleanup
|
|
| 547 | - MC.throwM e1
|
|
| 548 | - Right x -> cleanup $> x
|
|
| 547 | + MC.try @_ @SemaphoreError (makeJobserver sem_ident) >>= \case
|
|
| 548 | + Left open_failure -> return (Left open_failure)
|
|
| 549 | + Right (abs, cleanup) -> do
|
|
| 550 | + r <- try $ unmask $ action abs
|
|
| 551 | + case r of
|
|
| 552 | + Left (e1 :: MC.SomeException) -> do
|
|
| 553 | + (_ :: Either MC.SomeException ()) <- MC.try cleanup
|
|
| 554 | + MC.throwM e1
|
|
| 555 | + Right x -> cleanup $> Right x
|
|
| 549 | 556 | |
| 550 | 557 | {- Note [Architecture of the Job Server]
|
| 551 | 558 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... | ... | @@ -6,6 +6,7 @@ module GHC.Utils.TmpFs |
| 6 | 6 | , initTmpFs
|
| 7 | 7 | , forkTmpFsFrom
|
| 8 | 8 | , mergeTmpFsInto
|
| 9 | + , withLocalTmpFS
|
|
| 9 | 10 | , PathsToClean(..)
|
| 10 | 11 | , emptyPathsToClean
|
| 11 | 12 | , TempFileLifetime(..)
|
| ... | ... | @@ -157,6 +158,16 @@ mergeTmpFsInto src dst = do |
| 157 | 158 | atomicModifyIORef' (tmp_files_to_clean dst) (\s -> (mergePathsToClean src_files s, ()))
|
| 158 | 159 | atomicModifyIORef' (tmp_subdirs_to_clean dst) (\s -> (mergePathsToClean src_subdirs s, ()))
|
| 159 | 160 | |
| 161 | +-- | Run an action with a local 'TmpFs' forked from the given 'TmpFs'.
|
|
| 162 | +--
|
|
| 163 | +-- The remaining files of the local 'TmpFs' which weren't cleaned up by the
|
|
| 164 | +-- action are merged back into the given 'TmpFs', for clean-up later.
|
|
| 165 | +withLocalTmpFS :: TmpFs -> (TmpFs -> IO a) -> IO a
|
|
| 166 | +withLocalTmpFS tmpfs act =
|
|
| 167 | + Exception.bracket
|
|
| 168 | + (forkTmpFsFrom tmpfs)
|
|
| 169 | + (\tmpfs_local -> mergeTmpFsInto tmpfs_local tmpfs)
|
|
| 170 | + act
|
|
| 160 | 171 | |
| 161 | 172 | cleanTempDirs :: Logger -> TmpFs -> IO ()
|
| 162 | 173 | cleanTempDirs logger tmpfs
|
| ... | ... | @@ -487,11 +487,13 @@ Library |
| 487 | 487 | GHC.Driver.ByteCode
|
| 488 | 488 | GHC.Driver.CmdLine
|
| 489 | 489 | GHC.Driver.CodeOutput
|
| 490 | + GHC.Driver.Concurrency
|
|
| 490 | 491 | GHC.Driver.Config
|
| 491 | 492 | GHC.Driver.Config.Cmm
|
| 492 | 493 | GHC.Driver.Config.Cmm.Parser
|
| 493 | 494 | GHC.Driver.Config.CmmToAsm
|
| 494 | 495 | GHC.Driver.Config.CmmToLlvm
|
| 496 | + GHC.Driver.Config.Concurrency
|
|
| 495 | 497 | GHC.Driver.Config.Core.Lint
|
| 496 | 498 | GHC.Driver.Config.Core.Lint.Interactive
|
| 497 | 499 | GHC.Driver.Config.Core.Opt.Arity
|
| ... | ... | @@ -172,7 +172,7 @@ createIfaces verbosity modules flags instIfaceMap = do |
| 172 | 172 | _ <- setSessionDynFlags dflags''
|
| 173 | 173 | targets <- mapM (\(filePath, _) -> guessTarget filePath Nothing Nothing) hs_srcs
|
| 174 | 174 | setTargets targets
|
| 175 | - (_errs, modGraph) <- depanalE mkUnknownDiagnostic (Just batchMsg) [] False
|
|
| 175 | + (_errs, modGraph) <- depanalE [] False
|
|
| 176 | 176 | |
| 177 | 177 | -- Create (if necessary) and load .hi-files. With --no-compilation this happens later.
|
| 178 | 178 | when (Flag_NoCompilation `notElem` flags) $ do
|