sheaf pushed to branch wip/romes/27514 at Glasgow Haskell Compiler / GHC

Commits:

9 changed files:

Changes:

  • compiler/GHC/Driver/Concurrency.hs
    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

  • compiler/GHC/Driver/Config/Concurrency.hs
    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)

  • compiler/GHC/Driver/Downsweep.hs
    ... ... @@ -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
    +-}

  • compiler/GHC/Driver/Make.hs
    ... ... @@ -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
    

  • compiler/GHC/Driver/MakeAction.hs
    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 }

  • compiler/GHC/Driver/MakeSem.hs
    ... ... @@ -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
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

  • compiler/GHC/Utils/TmpFs.hs
    ... ... @@ -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
    

  • compiler/ghc.cabal.in
    ... ... @@ -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
    

  • utils/haddock/haddock-api/src/Haddock/Interface.hs
    ... ... @@ -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