Rodrigo Mesquita pushed to branch wip/romes/multi-threaded-debugger at Glasgow Haskell Compiler / GHC

Commits:

6 changed files:

Changes:

  • compiler/GHC/Runtime/Eval.hs
    ... ... @@ -188,12 +188,13 @@ execOptions = ExecOptions
    188 188
         -- by default we just care about breakpoints hit in this eval thread
    
    189 189
       }
    
    190 190
     
    
    191
    --- | Run a statement in the current interactive context.
    
    191
    +-- | Run a statement in the current interactive context, as a new thread in the interpreter.
    
    192
    +-- Returns an action which blocks waiting for the execution to finish and yield a result.
    
    192 193
     execStmt
    
    193 194
       :: GhcMonad m
    
    194 195
       => String             -- ^ a statement (bind or expression)
    
    195 196
       -> ExecOptions
    
    196
    -  -> m ExecResult
    
    197
    +  -> m (m ExecResult)
    
    197 198
     execStmt input exec_opts@ExecOptions{..} = do
    
    198 199
         hsc_env <- getSession
    
    199 200
     
    
    ... ... @@ -204,13 +205,15 @@ execStmt input exec_opts@ExecOptions{..} = do
    204 205
     
    
    205 206
         case mb_stmt of
    
    206 207
           -- empty statement / comment
    
    207
    -      Nothing -> return (ExecComplete (Right []) 0)
    
    208
    +      Nothing -> pure (pure (ExecComplete (Right []) 0))
    
    208 209
           Just stmt -> execStmt' stmt input exec_opts
    
    209 210
     
    
    210 211
     -- | Like `execStmt`, but takes a parsed statement as argument. Useful when
    
    211 212
     -- doing preprocessing on the AST before execution, e.g. in GHCi (see
    
    212 213
     -- GHCi.UI.runStmt).
    
    213
    -execStmt' :: GhcMonad m => GhciLStmt GhcPs -> String -> ExecOptions -> m ExecResult
    
    214
    +--
    
    215
    +-- Returns an action which blocks waiting for the execution to finish and yield a result.
    
    216
    +execStmt' :: GhcMonad m => GhciLStmt GhcPs -> String -> ExecOptions -> m (m ExecResult)
    
    214 217
     execStmt' stmt stmt_text ExecOptions{..} = do
    
    215 218
         hsc_env <- getSession
    
    216 219
         let interp = hscInterp hsc_env
    
    ... ... @@ -226,21 +229,22 @@ execStmt' stmt stmt_text ExecOptions{..} = do
    226 229
         case r of
    
    227 230
           Nothing ->
    
    228 231
             -- empty statement / comment
    
    229
    -        return (ExecComplete (Right []) 0)
    
    232
    +        pure (pure (ExecComplete (Right []) 0))
    
    230 233
           Just (ids, hval, fix_env) -> do
    
    231 234
             updateFixityEnv fix_env
    
    232 235
     
    
    233 236
             let eval_opts = initEvalOpts idflags' (enableGhcStepMode execSingleStep)
    
    234 237
                               (enableIsolateThreadBreaks execIsolateMode)
    
    235
    -        status <- liftIO $ evalStmt interp eval_opts (execWrap hval)
    
    238
    +        rtid <- liftIO $ evalStmt interp eval_opts (execWrap hval)
    
    236 239
     
    
    237 240
             let ic = hsc_IC hsc_env
    
    238 241
                 bindings = (ic_tythings ic, ic_gre_cache ic)
    
    239 242
     
    
    240 243
                 size = ghciHistSize idflags'
    
    241
    -
    
    242
    -        handleRunStatus execSingleStep eval_opts stmt_text bindings ids
    
    243
    -                        status (emptyHistory size)
    
    244
    +        pure $ do
    
    245
    +          status <- liftIO (waitThreadResult interp rtid)
    
    246
    +          handleRunStatus execSingleStep eval_opts stmt_text bindings ids
    
    247
    +                          status (emptyHistory size)
    
    244 248
     
    
    245 249
     runDecls :: GhcMonad m => String -> m [TyThing]
    
    246 250
     runDecls = runDeclsWithLocation "<interactive>" 1
    
    ... ... @@ -326,16 +330,16 @@ handleRunStatus step eval_opts expr bindings final_ids status history0 = do
    326 330
           return (ExecComplete (Left (fromSerializableException e)) alloc)
    
    327 331
     
    
    328 332
         -- Nothing case: we stopped when an exception was raised, not at a breakpoint.
    
    329
    -    EvalBreak apStack_ref Nothing resume_ctxt ccs -> do
    
    330
    -      resume_ctxt_fhv <- liftIO $ mkFinalizedHValue interp resume_ctxt
    
    331
    -      apStack_fhv     <- liftIO $ mkFinalizedHValue interp apStack_ref
    
    333
    +    EvalBreak apStack_ref Nothing resume_tid ccs -> do
    
    334
    +      resume_tid_fhv <- liftIO $ mkFinalizedHValue interp resume_tid
    
    335
    +      apStack_fhv    <- liftIO $ mkFinalizedHValue interp apStack_ref
    
    332 336
           let span = mkGeneralSrcSpan (fsLit "<unknown>")
    
    333 337
           (hsc_env1, break_ids) <- liftIO $
    
    334 338
             bindLocalsAtBreakpoint hsc_env apStack_fhv span Nothing
    
    335 339
           let
    
    336 340
             resume = Resume
    
    337 341
               { resumeStmt = expr
    
    338
    -          , resumeContext = resume_ctxt_fhv
    
    342
    +          , resumeContext = resume_tid_fhv
    
    339 343
               , resumeBindings = bindings
    
    340 344
               , resumeFinalIds = final_ids
    
    341 345
               , resumeApStack = apStack_fhv
    
    ... ... @@ -356,7 +360,7 @@ handleRunStatus step eval_opts expr bindings final_ids status history0 = do
    356 360
         -- The interpreter yields on a breakpoint if:
    
    357 361
         --  - the breakpoint was explicitly enabled (in @BreakArray@)
    
    358 362
         --  - or one of the stepping options in @EvalOpts@ caused us to stop at one
    
    359
    -    EvalBreak apStack_ref (Just eval_break) resume_ctxt ccs -> do
    
    363
    +    EvalBreak apStack_ref (Just eval_break) resume_tid ccs -> do
    
    360 364
           let ibi = evalBreakpointToId eval_break
    
    361 365
           let hug = hsc_HUG hsc_env
    
    362 366
           info_brks  <- liftIO $ readIModBreaks hug ibi
    
    ... ... @@ -368,8 +372,8 @@ handleRunStatus step eval_opts expr bindings final_ids status history0 = do
    368 372
             breakArray <- getBreakArray interp ibi info_brks
    
    369 373
             breakpointStatus interp breakArray (ibi_info_index ibi)
    
    370 374
     
    
    371
    -      apStack_fhv <- liftIO $ mkFinalizedHValue interp apStack_ref
    
    372
    -      resume_ctxt_fhv   <- liftIO $ mkFinalizedHValue interp resume_ctxt
    
    375
    +      apStack_fhv    <- liftIO $ mkFinalizedHValue interp apStack_ref
    
    376
    +      resume_tid_fhv <- liftIO $ mkFinalizedHValue interp resume_tid
    
    373 377
     
    
    374 378
           -- This breakpoint is enabled or we mean to break here;
    
    375 379
           -- we want to stop instead of just logging it.
    
    ... ... @@ -382,7 +386,7 @@ handleRunStatus step eval_opts expr bindings final_ids status history0 = do
    382 386
             let
    
    383 387
               resume = Resume
    
    384 388
                 { resumeStmt = expr
    
    385
    -            , resumeContext = resume_ctxt_fhv
    
    389
    +            , resumeContext = resume_tid_fhv
    
    386 390
                 , resumeBindings = bindings
    
    387 391
                 , resumeFinalIds = final_ids
    
    388 392
                 , resumeApStack = apStack_fhv
    
    ... ... @@ -398,7 +402,9 @@ handleRunStatus step eval_opts expr bindings final_ids status history0 = do
    398 402
             return (ExecBreak break_ids (Just ibi) resume)
    
    399 403
           else do
    
    400 404
             -- resume with the same step type
    
    401
    -        status <- liftIO $ GHCi.resumeStmt interp eval_opts resume_ctxt_fhv
    
    405
    +        status <- liftIO $ do
    
    406
    +          GHCi.resumeStmt interp eval_opts resume_tid_fhv
    
    407
    +          GHCi.waitThreadResult interp resume_tid_fhv
    
    402 408
             history <- if not tracing then pure history0 else do
    
    403 409
               history1 <- liftIO $ mkHistory hug apStack_fhv ibi
    
    404 410
               let !history' = history1 `consBL` history0
    
    ... ... @@ -414,7 +420,9 @@ resumeExec :: GhcMonad m
    414 420
                -> ThreadBreaksIsolationMode
    
    415 421
                -> Maybe Int
    
    416 422
                -> Resume
    
    417
    -           -> m ExecResult
    
    423
    +           -> m (m ExecResult)
    
    424
    +           -- ^ Returns an action which blocks waiting for the resumed thread
    
    425
    +           -- to break again or yield a result
    
    418 426
     resumeExec step isolateMode mbCnt r = do
    
    419 427
       hsc_env <- getSession
    
    420 428
       let ic = hsc_IC hsc_env
    
    ... ... @@ -438,7 +446,7 @@ resumeExec step isolateMode mbCnt r = do
    438 446
     
    
    439 447
       case r of
    
    440 448
         Resume { resumeStmt = expr
    
    441
    -           , resumeContext = fhv
    
    449
    +           , resumeContext = rtid
    
    442 450
                , resumeBindings = bindings
    
    443 451
                , resumeFinalIds = final_ids
    
    444 452
                , resumeApStack = apStack
    
    ... ... @@ -454,17 +462,19 @@ resumeExec step isolateMode mbCnt r = do
    454 462
     
    
    455 463
               let eval_opts = initEvalOpts dflags (enableGhcStepMode step)
    
    456 464
                                 (enableIsolateThreadBreaks isolateMode)
    
    457
    -          status <- liftIO $ GHCi.resumeStmt interp eval_opts fhv
    
    458
    -          let prevHistoryLst = fromListBL 50 hist
    
    459
    -              hug = hsc_HUG hsc_env
    
    460
    -              hist' = case mb_brkpt of
    
    461
    -                 Nothing -> pure prevHistoryLst
    
    462
    -                 Just bi
    
    463
    -                   | breakHere False step span -> do
    
    464
    -                      hist1 <- liftIO (mkHistory hug apStack bi)
    
    465
    -                      return $ hist1 `consBL` fromListBL 50 hist
    
    466
    -                   | otherwise -> pure prevHistoryLst
    
    467
    -          handleRunStatus step eval_opts expr bindings final_ids status =<< hist'
    
    465
    +          liftIO $ GHCi.resumeStmt interp eval_opts rtid
    
    466
    +          pure $ do
    
    467
    +            status <- liftIO $ GHCi.waitThreadResult interp rtid
    
    468
    +            let prevHistoryLst = fromListBL 50 hist
    
    469
    +                hug = hsc_HUG hsc_env
    
    470
    +                hist' = case mb_brkpt of
    
    471
    +                   Nothing -> pure prevHistoryLst
    
    472
    +                   Just bi
    
    473
    +                     | breakHere False step span -> do
    
    474
    +                        hist1 <- liftIO (mkHistory hug apStack bi)
    
    475
    +                        return $ hist1 `consBL` fromListBL 50 hist
    
    476
    +                     | otherwise -> pure prevHistoryLst
    
    477
    +            handleRunStatus step eval_opts expr bindings final_ids status =<< hist'
    
    468 478
     
    
    469 479
     setupBreakpoint :: GhcMonad m => Interp -> InternalBreakpointId -> Int -> m ()   -- #19157
    
    470 480
     setupBreakpoint interp ibi cnt = do
    
    ... ... @@ -1275,7 +1285,9 @@ compileParsedExprRemote expr@(L loc _) = withSession $ \hsc_env -> do
    1275 1285
     
    
    1276 1286
       updateFixityEnv fix_env
    
    1277 1287
       let eval_opts = initEvalOpts dflags EvalStepNone True
    
    1278
    -  status <- liftIO $ evalStmt interp eval_opts (EvalThis hvals_io)
    
    1288
    +  status <- liftIO $ do
    
    1289
    +    rtid <- evalStmt interp eval_opts (EvalThis hvals_io)
    
    1290
    +    waitThreadResult interp rtid
    
    1279 1291
       case status of
    
    1280 1292
         EvalComplete _ (EvalSuccess [hval]) -> return hval
    
    1281 1293
         EvalComplete _ (EvalException e) ->
    

  • compiler/GHC/Runtime/Interpreter.hs
    ... ... @@ -11,6 +11,7 @@ module GHC.Runtime.Interpreter
    11 11
       , evalStmt, EvalStatus_(..), EvalStatus, EvalResult(..), EvalExpr(..)
    
    12 12
       , resumeStmt
    
    13 13
       , abandonStmt
    
    14
    +  , waitThreadResult
    
    14 15
       , evalIO
    
    15 16
       , evalString
    
    16 17
       , evalStringToIOString
    
    ... ... @@ -286,11 +287,11 @@ evalStmt
    286 287
       :: Interp
    
    287 288
       -> EvalOpts
    
    288 289
       -> EvalExpr ForeignHValue
    
    289
    -  -> IO (EvalStatus_ [ForeignHValue] [HValueRef])
    
    290
    +  -> IO (ForeignRef ThreadId)
    
    290 291
     evalStmt interp opts foreign_expr = do
    
    291
    -  status <- withExpr foreign_expr $ \expr ->
    
    292
    +  r <- withExpr foreign_expr $ \expr ->
    
    292 293
         interpCmd interp (EvalStmt opts expr)
    
    293
    -  handleEvalStatus interp status
    
    294
    +  mkFinalizedHValue interp r
    
    294 295
      where
    
    295 296
       withExpr :: EvalExpr ForeignHValue -> (EvalExpr HValueRef -> IO a) -> IO a
    
    296 297
       withExpr (EvalThis fhv) cont =
    
    ... ... @@ -304,17 +305,25 @@ resumeStmt
    304 305
       :: Interp
    
    305 306
       -> EvalOpts
    
    306 307
       -> ForeignRef ThreadId
    
    307
    -  -> IO (EvalStatus_ [ForeignHValue] [HValueRef])
    
    308
    +  -> IO ()
    
    308 309
     resumeStmt interp opts resume_tid = do
    
    309
    -  status <- withForeignRef resume_tid $ \rhv ->
    
    310
    +  withForeignRef resume_tid $ \rhv ->
    
    310 311
         interpCmd interp (ResumeStmt opts rhv)
    
    311
    -  handleEvalStatus interp status
    
    312 312
     
    
    313 313
     abandonStmt :: Interp -> ForeignRef ThreadId -> IO ()
    
    314 314
     abandonStmt interp resume_tid =
    
    315 315
       withForeignRef resume_tid $ \rhv ->
    
    316 316
         interpCmd interp (AbandonStmt rhv)
    
    317 317
     
    
    318
    +waitThreadResult
    
    319
    +  :: Interp
    
    320
    +  -> ForeignRef ThreadId
    
    321
    +  -> IO (EvalStatus_ [ForeignHValue] [HValueRef])
    
    322
    +waitThreadResult interp tid = do
    
    323
    +  status <- withForeignRef tid $ \rhv -> do
    
    324
    +    interpCmd interp (WaitThreadResult rhv)
    
    325
    +  handleEvalStatus interp status
    
    326
    +
    
    318 327
     handleEvalStatus
    
    319 328
       :: Interp
    
    320 329
       -> EvalStatus [HValueRef]
    
    ... ... @@ -417,8 +426,9 @@ whereFrom interp ref =
    417 426
     seqHValue :: Interp -> UnitEnv -> Logger -> ForeignHValue -> IO (EvalResult ())
    
    418 427
     seqHValue interp unit_env logger ref =
    
    419 428
       withForeignRef ref $ \hval -> do
    
    420
    -    status <- interpCmd interp (Seq hval)
    
    421
    -    handleSeqHValueStatus interp unit_env logger status
    
    429
    +    rtid   <- interpCmd interp (Seq hval)
    
    430
    +    status <- interpCmd interp (WaitThreadResult rtid)
    
    431
    +    handleSeqHValueStatus interp unit_env logger (clearEvalStatus status)
    
    422 432
     
    
    423 433
     evalBreakpointToId :: EvalBreakpoint -> InternalBreakpointId
    
    424 434
     evalBreakpointToId eval_break =
    
    ... ... @@ -460,9 +470,10 @@ handleSeqHValueStatus interp unit_env logger eval_status =
    460 470
                 Just modbreaks -> put =<< getBreakLoc (readIModModBreaks hug) ibi modbreaks
    
    461 471
     
    
    462 472
           -- resume the seq (:force) processing in the iserv process
    
    463
    -      withForeignRef resume_ctxt_fhv $ \hval -> do
    
    464
    -        status <- interpCmd interp (ResumeSeq hval)
    
    465
    -        handleSeqHValueStatus interp unit_env logger status
    
    473
    +      withForeignRef resume_ctxt_fhv $ \rtid -> do
    
    474
    +        interpCmd interp (ResumeSeq rtid)
    
    475
    +        status <- interpCmd interp (WaitThreadResult rtid)
    
    476
    +        handleSeqHValueStatus interp unit_env logger (clearEvalStatus status)
    
    466 477
         (EvalComplete _ r) -> return r
    
    467 478
     
    
    468 479
     
    
    ... ... @@ -771,3 +782,9 @@ readIModModBreaks hug mod = imodBreaks_modBreaks . expectJust <$> readIModBreaks
    771 782
     fromEvalResult :: EvalResult a -> IO a
    
    772 783
     fromEvalResult (EvalException e) = throwIO (fromSerializableException e)
    
    773 784
     fromEvalResult (EvalSuccess a) = return a
    
    785
    +
    
    786
    +clearEvalStatus :: EvalStatus a -> EvalStatus ()
    
    787
    +clearEvalStatus = \case
    
    788
    +  EvalComplete w (EvalException se) -> EvalComplete w (EvalException se)
    
    789
    +  EvalComplete w (EvalSuccess _)    -> EvalComplete w (EvalSuccess ())
    
    790
    +  EvalBreak ap mb rt rccs           -> EvalBreak ap mb rt rccs

  • libraries/ghci/GHCi/Message.hs
    ... ... @@ -62,6 +62,8 @@ import Data.Dynamic
    62 62
     import Data.Typeable (TypeRep)
    
    63 63
     import Data.IORef
    
    64 64
     import Data.Map (Map)
    
    65
    +import Data.IntMap (IntMap)
    
    66
    +import qualified Data.IntMap as IM
    
    65 67
     import Foreign
    
    66 68
     import GHC.Generics
    
    67 69
     import GHC.Stack.CCS
    
    ... ... @@ -132,13 +134,13 @@ data Message a where
    132 134
       EvalStmt
    
    133 135
         :: EvalOpts
    
    134 136
         -> EvalExpr HValueRef {- IO [a] -}
    
    135
    -    -> Message (EvalStatus [HValueRef]) {- [a] -}
    
    137
    +    -> Message (RemoteRef ThreadId) {- [a] -}
    
    136 138
     
    
    137 139
       -- | Resume evaluation of a statement after a breakpoint
    
    138 140
       ResumeStmt
    
    139 141
        :: EvalOpts
    
    140 142
        -> RemoteRef ThreadId
    
    141
    -   -> Message (EvalStatus [HValueRef])
    
    143
    +   -> Message ()
    
    142 144
     
    
    143 145
       -- | Abandon evaluation of a statement after a breakpoint
    
    144 146
       AbandonStmt
    
    ... ... @@ -240,12 +242,23 @@ data Message a where
    240 242
       -- | Evaluate something. This is used to support :force in GHCi.
    
    241 243
       Seq
    
    242 244
         :: HValueRef
    
    243
    -    -> Message (EvalStatus ())
    
    245
    +    -> Message (RemoteRef ThreadId)
    
    244 246
     
    
    245 247
       -- | Resume forcing a free variable in a breakpoint (#2950)
    
    246 248
       ResumeSeq
    
    247 249
         :: RemoteRef ThreadId
    
    248
    -    -> Message (EvalStatus ())
    
    250
    +    -> Message ()
    
    251
    +
    
    252
    +  WaitThreadResult
    
    253
    +    :: RemoteRef ThreadId
    
    254
    +    -> Message (EvalStatus [HValueRef])
    
    255
    +
    
    256
    +  WaitAnyThreadBreak
    
    257
    +    :: Message EvalBreak
    
    258
    +
    
    259
    +  WaitThreadResultOrAnyThreadBreak
    
    260
    +    :: RemoteRef ThreadId
    
    261
    +    -> Message (EvalStatus [HValueRef])
    
    249 262
     
    
    250 263
       -- | User-defined request encoded as a tag/payload pair.  This is left
    
    251 264
       -- uninterpreted by GHC and is meant for GHC API applications to be able to supply
    
    ... ... @@ -724,6 +737,7 @@ data Pipe = Pipe
    724 737
       { getSome :: !(IO ByteString)
    
    725 738
       , putAll :: !(B.Builder -> IO ())
    
    726 739
       , pipeLeftovers :: !(IORef (Maybe ByteString))
    
    740
    +  -- , waitingStatus :: !(IORef (Map (RemoteRef ThreadId) (MVar (EvalStatus ()))))
    
    727 741
       }
    
    728 742
     
    
    729 743
     -- | Make a 'Pipe' from a 'Handle' to read and a 'Handle' to write.
    
    ... ... @@ -744,8 +758,19 @@ mkPipeFromContinuations getSome putAll = do
    744 758
     
    
    745 759
     remoteCall :: Binary a => Pipe -> Message a -> IO a
    
    746 760
     remoteCall pipe msg = do
    
    747
    -  writePipe pipe (putMessage msg)
    
    748
    -  readPipe pipe get
    
    761
    +  -- synchronous, write, read
    
    762
    +  writePipe pipe (putMessage msg) -- write goes in the handle
    
    763
    +  readPipe pipe get -- instead, this takes from the channel
    
    764
    +  -- the channel is fed from the handle by a different thread that creates a
    
    765
    +  -- stream of notifications and a stream of replies.
    
    766
    +  --
    
    767
    +  -- Since a reply will only come in response to a write request that happens
    
    768
    +  -- while the entire is thing is locked, we'll only get 1 reply per write, and
    
    769
    +  -- in fact the channel of replies is just 1 cell (MVar)
    
    770
    +  --
    
    771
    +  -- ATOMIC:
    
    772
    +  --  write pipe
    
    773
    +  --  read reply -- will never have more than 1 reply, because the Lock guarantees only 1 write.
    
    749 774
     
    
    750 775
     writePipe :: Pipe -> Put -> IO ()
    
    751 776
     writePipe Pipe{..} put = putAll $ execPut put
    
    ... ... @@ -755,6 +780,11 @@ remoteTHCall pipe msg = do
    755 780
       writePipe pipe (putTHMessage msg)
    
    756 781
       readPipe pipe get
    
    757 782
     
    
    783
    +-- todo: this will have to drain the pipe and write to a queue?
    
    784
    +--  Normal replies pushed to reply channel
    
    785
    +--  Remote call instead of ReadPipe, reads from the channel.
    
    786
    +--
    
    787
    +--  We need to drain channel for notifications...
    
    758 788
     readPipe :: Pipe -> Get a -> IO a
    
    759 789
     readPipe Pipe{..} get = do
    
    760 790
       leftovers <- readIORef pipeLeftovers
    

  • libraries/ghci/GHCi/Run.hs
    ... ... @@ -128,6 +128,15 @@ run m = case m of
    128 128
       Seq ref -> doSeq ref
    
    129 129
       ResumeSeq ref -> resumeSeq ref
    
    130 130
     
    
    131
    +  WaitThreadResult tid -> localRef tid >>= atomically . readThreadEvalStatus
    
    132
    +  WaitAnyThreadBreak   -> atomically $ readAnyThreadEvalBreak
    
    133
    +  WaitThreadResultOrAnyThreadBreak t -> do
    
    134
    +    tid <- localRef t
    
    135
    +    atomically $
    
    136
    +      readThreadEvalStatus tid
    
    137
    +        `orElse`
    
    138
    +      (EvalPaused <$> readAnyThreadEvalBreak)
    
    139
    +
    
    131 140
       Shutdown            -> unexpectedMessage m
    
    132 141
       RunTH {}            -> unexpectedMessage m
    
    133 142
       RunModFinalizers {} -> unexpectedMessage m
    
    ... ... @@ -206,7 +215,7 @@ tryEval io = do
    206 215
     
    
    207 216
     --------------------------------------------------------------------------------
    
    208 217
     
    
    209
    -evalStmt :: EvalOpts -> EvalExpr HValueRef -> IO (EvalStatus [HValueRef])
    
    218
    +evalStmt :: EvalOpts -> EvalExpr HValueRef -> IO (RemoteRef ThreadId)
    
    210 219
     evalStmt opts expr = do
    
    211 220
       io <- mkIO expr
    
    212 221
       sandboxIO opts $ do
    
    ... ... @@ -248,16 +257,16 @@ evalStringToString r str = do
    248 257
     -- The UI process has more and therefore also can show more
    
    249 258
     -- information about the breakpoint than the current iserv
    
    250 259
     -- process.
    
    251
    -doSeq :: RemoteRef a -> IO (EvalStatus ())
    
    252
    -doSeq ref = clearEvalStatus <$> do
    
    260
    +doSeq :: RemoteRef a -> IO (RemoteRef ThreadId)
    
    261
    +doSeq ref = do
    
    253 262
         sandboxIO evalOptsSeq $ do
    
    254 263
           _ <- (void $ evaluate =<< localRef ref)
    
    255 264
           return []
    
    256 265
     
    
    257 266
     -- | Process a ResumeSeq message. Continue the :force processing     #2950
    
    258 267
     -- after a breakpoint.
    
    259
    -resumeSeq :: RemoteRef ThreadId -> IO (EvalStatus ())
    
    260
    -resumeSeq hvref = clearEvalStatus <$> resumeStmt evalOptsSeq hvref
    
    268
    +resumeSeq :: RemoteRef ThreadId -> IO ()
    
    269
    +resumeSeq hvref = resumeStmt evalOptsSeq hvref
    
    261 270
     
    
    262 271
     evalOptsSeq :: EvalOpts
    
    263 272
     evalOptsSeq = EvalOpts
    
    ... ... @@ -296,15 +305,16 @@ abandonStmt hvref = do
    296 305
     
    
    297 306
     --------------------------------------------------------------------------------
    
    298 307
     
    
    299
    -resumeStmt :: EvalOpts -> RemoteRef ThreadId -> IO (EvalStatus [HValueRef])
    
    308
    +resumeStmt :: EvalOpts -> RemoteRef ThreadId -> IO ()
    
    300 309
     resumeStmt opts rtid = do
    
    301 310
       resumeThreadId <- localRef rtid
    
    302 311
       ResumeContext{..} <- getThreadResumeContext resumeThreadId
    
    303
    -  withBreakAction opts resumeThreadId $ \waitForResult ->
    
    304
    -    mask_ $ do
    
    305
    -      putMVar resumeBreakMVar () -- this awakens the stopped thread...
    
    306
    -      redirectInterrupts resumeThreadId $
    
    307
    -        waitForResult
    
    312
    +  setBreakAction opts resumeThreadId -- See Note [TODO: all break options must be set every time]
    
    313
    +  -- withBreakAction opts resumeThreadId $ \waitForResult ->
    
    314
    +    -- mask_ $ do
    
    315
    +  putMVar resumeBreakMVar () -- this awakens the stopped thread...
    
    316
    +      -- redirectInterrupts resumeThreadId $
    
    317
    +      --   waitForResult
    
    308 318
     
    
    309 319
     -- When running a computation, we redirect ^C exceptions to the running
    
    310 320
     -- thread.  ToDo: we might want a way to continue even if the target
    
    ... ... @@ -316,26 +326,51 @@ resumeStmt opts rtid = do
    316 326
     -- only while we execute the user's code.  We can't afford to lose the final
    
    317 327
     -- putMVar, otherwise deadlock ensues. (#1583, #1922, #1946)
    
    318 328
     
    
    319
    -sandboxIO :: EvalOpts -> IO [HValueRef] -> IO (EvalStatus [HValueRef])
    
    329
    +sandboxIO :: EvalOpts -> IO [HValueRef] -> IO (RemoteRef ThreadId)
    
    320 330
     sandboxIO opts io
    
    321 331
         | useSandboxThread opts
    
    322 332
         = do
    
    323 333
             wait <- newEmptyMVar
    
    324 334
     
    
    335
    +        -- If we are evaluating expressions (sandboxIO), then make sure the
    
    336
    +        -- global breakpoint action is set.
    
    337
    +        --
    
    338
    +        -- TODO: Is this thread unsafe? Already running threads may access this
    
    339
    +        -- pointer as we write it when evaluating a new expression.
    
    340
    +        --
    
    341
    +        -- A solution would be to have an "initialize interpreter" command
    
    342
    +        -- which sets this at the start. A different solution is to have a
    
    343
    +        -- global lock and only write this once here, then lock.
    
    344
    +        poke breakPointIOAction globalBreakStablePtr
    
    345
    +
    
    325 346
             tid <- forkIO $ do
    
    326 347
     
    
    327 348
               takeMVar wait -- don't run anything in this thread before
    
    328
    -                        -- `withBreakAction` has a chance to set up.
    
    349
    +                        -- `setBreakAction` has a chance to set up.
    
    329 350
     
    
    330 351
               tid <- myThreadId
    
    331 352
               labelThread tid "GHCi sandbox"
    
    353
    +
    
    354
    +            -- WHEN THE THREAD IS FINISHED, write the value itself.
    
    355
    +            --
    
    356
    +            -- we only write the TID status map in two places:
    
    357
    +            --
    
    358
    +            -- When the "main" thread finishes (here)
    
    359
    +            -- When any thread hits a breakpoint (global break action)
    
    332 360
               unsafeUnmask runIt >>= writeThreadEvalStatus tid
    
    361
    +            -- TODO: This unsafe-unmask is very dubious. Consult with Andrea. I
    
    362
    +            -- don't think we should be uninterruptable masking interp commands
    
    363
    +            -- somewhere else. This should just be runIt?
    
    333 364
     
    
    334
    -        -- We are running in uninterruptibleMask
    
    335
    -        withBreakAction opts tid $ \waitForResult -> do
    
    336
    -          putMVar wait ()
    
    337
    -          redirectInterrupts tid $ unsafeUnmask $
    
    338
    -            waitForResult
    
    365
    +        -- We are running in uninterruptibleMask -- TODO WHY??
    
    366
    +        setBreakAction opts tid
    
    367
    +        putMVar wait ()
    
    368
    +
    
    369
    +        mkRemoteRef tid
    
    370
    +        -- withBreakAction opts tid $ \waitForResult -> do
    
    371
    +        --   putMVar wait ()
    
    372
    +        --   redirectInterrupts tid $ unsafeUnmask $
    
    373
    +        --     waitForResult
    
    339 374
     
    
    340 375
         | otherwise
    
    341 376
         = do
    
    ... ... @@ -349,7 +384,17 @@ sandboxIO opts io
    349 384
           -- tracing, etc.) need the expression to be running in a
    
    350 385
           -- separate thread, so debugging is only enabled when
    
    351 386
           -- using the sandbox.
    
    352
    -      runIt
    
    387
    +      tid <- forkIO (pure ())
    
    388
    +      runIt >>= writeThreadEvalStatus tid
    
    389
    +      mkRemoteRef tid
    
    390
    +        -- We `runIt` on the main thread, but fork a new thread to store to the
    
    391
    +        -- result with `writeThreadEvalStatus` to fit the asynchronous API. We
    
    392
    +        -- can't use myThreadId because if the user happens to runIt twice
    
    393
    +        -- before awaiting, the second result will clobber the first.
    
    394
    +
    
    395
    +        -- TODO We have to be careful evacuating this map bc there may be
    
    396
    +        -- uncollected results from finished threads. So the thread map can't
    
    397
    +        -- be weak refs ? Or maybe because of the RemoteRef it's fine. Ah, prob.
    
    353 398
       where
    
    354 399
         runIt = measureAlloc $ tryEval $ rethrow opts $ clearCCS io
    
    355 400
     
    
    ... ... @@ -404,17 +449,17 @@ sandboxIO opts io
    404 449
     -- whenever "main" or one of the threads spawned by "main" hit a breakpoint,
    
    405 450
     -- rather than just "main" being observed. This should be used for the main
    
    406 451
     -- debuggee execution of a debugger.
    
    407
    -withBreakAction :: EvalOpts -> ThreadId -> (IO (EvalStatus [HValueRef]) -> IO a) -> IO a
    
    408
    -withBreakAction opts tid@(ThreadId tid#) act
    
    409
    -  = bracket setBreakAction resetBreakAction (\_ -> act waitForResult)
    
    452
    +-- withBreakAction :: EvalOpts -> ThreadId -> (IO (EvalStatus [HValueRef]) -> IO a) -> IO a
    
    453
    +-- withBreakAction opts tid@(ThreadId tid#) act = do
    
    454
    +
    
    455
    +setBreakAction :: EvalOpts -> ThreadId -> IO ()
    
    456
    +setBreakAction opts tid@(ThreadId tid#) = do
    
    457
    +  ctx <- getThreadResumeContext tid
    
    458
    +  setBreakAction' ctx
    
    459
    +    -- bracket _ (resetBreakAction ctx) (\_ -> act (waitForResult ctx))
    
    410 460
       where
    
    411
    -    setBreakAction = do
    
    412
    -      poke breakPointIOAction globalBreakStablePtr
    
    413
    -         -- TODO: This is poke thread unsafe, as one thread might be accessing this
    
    414
    -         -- global variable while this thread tries to overwrite it. We should
    
    415
    -         -- rather do it when the interpreter process/internal is initialized
    
    416
    -         -- somehow.
    
    417
    -
    
    461
    +    setBreakAction' ctx = do
    
    462
    +      -- TODO: TERRIBLY NOT MULTI-THREAD FRIENDLY. Each thread needs its own RTS flag exceptionFlag, like we have for step-in or step-out.
    
    418 463
           runIf breakOnException $ \_ -> poke exceptionFlag 1
    
    419 464
             -- Breaking on exceptions is not enabled by default, since it
    
    420 465
             -- might be a bit surprising. The exception flag is turned off
    
    ... ... @@ -422,32 +467,34 @@ withBreakAction opts tid@(ThreadId tid#) act
    422 467
     
    
    423 468
           runIf# singleStep rts_enableStopNextBreakpoint
    
    424 469
           runIf# stepOut    rts_enableStopAfterReturn
    
    425
    -      runIf  isolateThreadBreaks setIsolatedThread
    
    426 470
     
    
    427
    -    resetBreakAction () = do
    
    428
    -      poke exceptionFlag 0
    
    471
    +      runIfCtx ctx isolateThreadBreaks setIsolatedCtx
    
    472
    +
    
    473
    +    resetBreakAction ctx () = do
    
    474
    +      poke exceptionFlag 0 -- just wrong, expection flag should be per-thread
    
    429 475
           rts_disableStopAfterReturn    tid#
    
    430 476
           rts_disableStopNextBreakpoint tid#
    
    431
    -      runIf isolateThreadBreaks unsetIsolatedThread
    
    477
    +      runIfCtx ctx isolateThreadBreaks unsetIsolatedCtx
    
    432 478
     
    
    433 479
           -- freeStablePtr stablePtr TODO: we never free the global stablePtr for
    
    434 480
           -- global action. Maybe we should have a de-init for clean up.
    
    435 481
           -- TODO: don't set it in `setBreakAction` either. Should be on
    
    436 482
           -- interpreter initialization.
    
    437 483
     
    
    438
    -    runIf  pred what = when (pred opts) (what tid)
    
    439
    -    runIf# pred what = when (pred opts) (what tid#)
    
    484
    +    runIf    pred what     = when (pred opts) (what tid)
    
    485
    +    runIf#   pred what     = when (pred opts) (what tid#)
    
    486
    +    runIfCtx ctx pred what = when (pred opts) (what ctx)
    
    440 487
     
    
    441
    -    waitForResult
    
    488
    +    waitForResult ctx
    
    442 489
           | isolateThreadBreaks opts
    
    443 490
           = atomically $
    
    444
    -          readThreadEvalStatus tid
    
    491
    +          readCtxEvalStatus ctx
    
    445 492
     
    
    446 493
           | otherwise
    
    447 494
           = atomically $
    
    448 495
               (EvalPaused <$> readAnyThreadEvalBreak)
    
    449 496
               `orElse`
    
    450
    -          readThreadEvalStatus tid
    
    497
    +          readCtxEvalStatus ctx
    
    451 498
     
    
    452 499
     -- While we're waiting for the sandbox thread to return a result, if
    
    453 500
     -- the current thread receives an asynchronous exception we re-throw
    
    ... ... @@ -476,9 +523,3 @@ redirectInterrupts target wait = do
    476 523
            Nothing -> wait
    
    477 524
            Just target -> do throwTo target (e :: SomeException); wait
    
    478 525
     
    479
    -clearEvalStatus :: EvalStatus a -> EvalStatus ()
    
    480
    -clearEvalStatus = \case
    
    481
    -  EvalComplete w (EvalException se) -> EvalComplete w (EvalException se)
    
    482
    -  EvalComplete w (EvalSuccess _)    -> EvalComplete w (EvalSuccess ())
    
    483
    -  EvalBreak ap mb rt rccs           -> EvalBreak ap mb rt rccs
    
    484
    -

  • libraries/ghci/GHCi/Run/Breakpoints.hs
    ... ... @@ -8,10 +8,12 @@ module GHCi.Run.Breakpoints
    8 8
       -- * Per-thread breakpoints
    
    9 9
       , readThreadEvalStatus, readAnyThreadEvalBreak
    
    10 10
       , writeThreadEvalStatus
    
    11
    +  , readCtxEvalStatus, writeCtxEvalStatus
    
    11 12
     
    
    12 13
       -- ** Isolating thread breakpoints
    
    13 14
       , withIsolatedThread
    
    14 15
       , setIsolatedThread, unsetIsolatedThread
    
    16
    +  , setIsolatedCtx, unsetIsolatedCtx
    
    15 17
     
    
    16 18
       -- ** Querying the global thread breakpoint map
    
    17 19
       , listOtherPausedThreads
    
    ... ... @@ -29,18 +31,15 @@ import GHCi.Message
    29 31
     import GHCi.RemoteTypes
    
    30 32
     
    
    31 33
     import qualified Data.Map as Map
    
    32
    -import qualified Data.Set as Set
    
    33 34
     import Control.Concurrent
    
    34 35
     import Control.Exception
    
    35 36
     import qualified Data.ByteString.Short.Internal as BS
    
    36
    -#if defined(PROFILING)
    
    37
    -import GHC.Data.ShortByteString
    
    38
    -#endif
    
    39 37
     import GHC.Exts
    
    40 38
     import GHC.Stack
    
    41 39
     import Foreign hiding (void)
    
    42 40
     import GHC.Conc.Sync
    
    43 41
     import GHC.IO hiding ( bracket )
    
    42
    +import Control.Monad
    
    44 43
     
    
    45 44
     --------------------------------------------------------------------------------
    
    46 45
     -- * Global break action
    
    ... ... @@ -72,7 +71,7 @@ globalBreakStablePtr = unsafePerformIO $ newStablePtr globalBreakAction
    72 71
     globalBreakAction :: BreakpointCallback
    
    73 72
     globalBreakAction info_mod# info_mod_uid# infox# is_exception apStack = do
    
    74 73
       tid <- myThreadId
    
    75
    -  ResumeContext{..} <- getThreadResumeContext tid
    
    74
    +  ctx@ResumeContext{..} <- getThreadResumeContext tid
    
    76 75
       resume_r <- mkRemoteRef tid
    
    77 76
       apStack_r <- mkRemoteRef apStack
    
    78 77
       ccs <- toRemotePtr <$> getCCSOf apStack
    
    ... ... @@ -83,7 +82,7 @@ globalBreakAction info_mod# info_mod_uid# infox# is_exception apStack = do
    83 82
           info_mod <- BS.packCString (Ptr info_mod#)
    
    84 83
           info_mod_uid <- BS.packCString (Ptr info_mod_uid#)
    
    85 84
           pure (Just (EvalBreakpoint info_mod info_mod_uid (I# infox#)))
    
    86
    -  writeThreadEvalStatus tid $ EvalBreak apStack_r mb_breakpoint resume_r ccs
    
    85
    +  writeCtxEvalStatus ctx $ EvalBreak apStack_r mb_breakpoint resume_r ccs
    
    87 86
     
    
    88 87
       -- Block until this thread is resumed (by the thread which took the
    
    89 88
       -- `ResumeContext` from the `statusMVar`).
    
    ... ... @@ -109,14 +108,19 @@ globalBreakAction info_mod# info_mod_uid# infox# is_exception apStack = do
    109 108
     -- Succeeds when the given thread writes to the global debugger map an eval result.
    
    110 109
     readThreadEvalStatus :: ThreadId -> STM (EvalStatus [HValueRef])
    
    111 110
     readThreadEvalStatus tid = do
    
    112
    -  GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
    
    113
    -  case Map.lookup tid threadEvalStatuses of
    
    111
    +  ctxs <- readTVar threadContextsVar
    
    112
    +  case Map.lookup tid ctxs of
    
    113
    +    Just ctx -> readCtxEvalStatus ctx
    
    114
    +    Nothing  -> retry -- to write a thread break or result, the
    
    115
    +                      -- 'threadContextsVar' entry will be created
    
    116
    +
    
    117
    +-- | Like 'readThreadEvalStatus', but when the 'ResumeContext' is already
    
    118
    +-- available (to avoid looking it up unnecessarily).
    
    119
    +readCtxEvalStatus :: ResumeContext -> STM (EvalStatus [HValueRef])
    
    120
    +readCtxEvalStatus ResumeContext{resumeEvalStatus} =
    
    121
    +  readTVar resumeEvalStatus >>= \case
    
    114 122
         Just r  -> do
    
    115
    -      writeTVar globalDbgStatusVar
    
    116
    -        GlobalDbgStatus
    
    117
    -          { threadEvalStatuses = Map.delete tid threadEvalStatuses -- clear it
    
    118
    -          , isolatedThreads
    
    119
    -          }
    
    123
    +      writeTVar resumeEvalStatus Nothing -- clear it
    
    120 124
           pure r
    
    121 125
         Nothing -> retry -- until someone writes the result for this thread
    
    122 126
     
    
    ... ... @@ -126,38 +130,46 @@ readThreadEvalStatus tid = do
    126 130
     -- The debugger will be waiting for threads to break using `readAnyThreadEvalBreak`.
    
    127 131
     --
    
    128 132
     -- Succeeds when any thread hits a breakpoint.
    
    133
    +--
    
    134
    +-- TODO: Instead of reading all elems of the map, we could have the global
    
    135
    +-- break action write breakpoints hit into a global `TChan ThreadId`, and then
    
    136
    +-- `readAnyThreadEvalBreak` would read off of this channel breakpoints that
    
    137
    +-- were hit, and do the lookup on the map by ThreadId, retrying if it has
    
    138
    +-- already been handled (i.e. set to Nothing)
    
    129 139
     readAnyThreadEvalBreak :: STM EvalBreak
    
    130 140
     readAnyThreadEvalBreak = do
    
    131
    -  dbgs <- readTVar globalDbgStatusVar
    
    141
    +  ctxs <- readTVar threadContextsVar
    
    132 142
       -- try reading any thread's 'EvalBreak' and retry if none have yielded one yet
    
    133
    -  foldr (\x next -> readStatus dbgs x `orElse` next) retry (Map.toList (threadEvalStatuses dbgs))
    
    143
    +  foldl' (\next ctx -> next `orElse` readBreak ctx) retry (Map.elems ctxs)
    
    134 144
       where
    
    135
    -    readStatus GlobalDbgStatus{..} (tid, _) -- don't look at isolated threads
    
    136
    -      | Set.member tid isolatedThreads
    
    137
    -      = retry
    
    138
    -
    
    139
    -    readStatus GlobalDbgStatus{..} (tid, status)
    
    140
    -      = do
    
    141
    -      case status of
    
    142
    -        EvalComplete{} -> retry -- successes can be read with `readThreadEvalStatus`
    
    143
    -        EvalPaused b   -> do
    
    144
    -          writeTVar globalDbgStatusVar
    
    145
    -            GlobalDbgStatus
    
    146
    -              { threadEvalStatuses = Map.delete tid threadEvalStatuses
    
    147
    -              , isolatedThreads
    
    148
    -              }
    
    149
    -          pure b
    
    145
    +    readBreak ResumeContext{resumeEvalStatus, resumeIsolated} = do
    
    146
    +      isolated <- readTVar resumeIsolated
    
    147
    +      if isolated
    
    148
    +        then retry -- don't look at isolated threads
    
    149
    +        else readTVar resumeEvalStatus >>= \case
    
    150
    +          Just (EvalPaused b) -> do
    
    151
    +            writeTVar resumeEvalStatus Nothing
    
    152
    +            pure b
    
    153
    +          Just EvalComplete{} ->
    
    154
    +            retry -- successes can be read with `readThreadEvalStatus`
    
    155
    +          Nothing ->
    
    156
    +            retry -- no status for this thread yet
    
    150 157
     
    
    151 158
     writeThreadEvalStatus :: ThreadId -> EvalStatus [HValueRef] -> IO ()
    
    152
    -writeThreadEvalStatus tid status =
    
    159
    +writeThreadEvalStatus tid status = do
    
    160
    +  -- get or create a 'ResumeContext' for the thread (e.g. we may write the
    
    161
    +  -- EvalComplete without breakpoints occurring, so the map won't have an entry):
    
    162
    +  ctx <- getThreadResumeContext tid
    
    163
    +  writeCtxEvalStatus ctx status
    
    164
    +
    
    165
    +-- | Like 'writeThreadEvalStatus', but for when the 'ResumeContext' is already
    
    166
    +-- available
    
    167
    +writeCtxEvalStatus :: ResumeContext -> EvalStatus [HValueRef] -> IO ()
    
    168
    +writeCtxEvalStatus ResumeContext{resumeEvalStatus} status =
    
    153 169
       atomically $ do
    
    154
    -    GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
    
    155
    -    writeTVar globalDbgStatusVar $
    
    156
    -      GlobalDbgStatus
    
    157
    -        { threadEvalStatuses =
    
    158
    -            Map.insertWith (\_ _ -> error "writeThreadEvalStatus: should be impossible")
    
    159
    -                tid status threadEvalStatuses
    
    160
    -        , isolatedThreads }
    
    170
    +    readTVar resumeEvalStatus >>= \case
    
    171
    +      Just{}  -> error "writeCtxEvalStatus: should be impossible"
    
    172
    +      Nothing -> writeTVar resumeEvalStatus (Just status)
    
    161 173
     
    
    162 174
     
    
    163 175
     -- ** Isolating thread breakpoints ---------------------------------------------
    
    ... ... @@ -174,23 +186,15 @@ withIsolatedThread tid = bracket_ (setIsolatedThread tid) (unsetIsolatedThread t
    174 186
     -- expression to run should not be seen at all by the debugger (e.g. when
    
    175 187
     -- evaluating an expr to :force a variable)
    
    176 188
     setIsolatedThread :: ThreadId -> IO ()
    
    177
    -setIsolatedThread tid = atomically $ do
    
    178
    -  GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
    
    179
    -  writeTVar globalDbgStatusVar
    
    180
    -    GlobalDbgStatus
    
    181
    -      { threadEvalStatuses
    
    182
    -      , isolatedThreads = Set.insert tid isolatedThreads
    
    183
    -      }
    
    189
    +setIsolatedThread = getThreadResumeContext >=> setIsolatedCtx
    
    184 190
     
    
    185 191
     -- | Undoes 'setIsolatedThread'
    
    186 192
     unsetIsolatedThread :: ThreadId -> IO ()
    
    187
    -unsetIsolatedThread tid = atomically $ do
    
    188
    -  GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
    
    189
    -  writeTVar globalDbgStatusVar
    
    190
    -    GlobalDbgStatus
    
    191
    -      { threadEvalStatuses
    
    192
    -      , isolatedThreads = Set.delete tid isolatedThreads
    
    193
    -      }
    
    193
    +unsetIsolatedThread = getThreadResumeContext >=> unsetIsolatedCtx
    
    194
    +
    
    195
    +setIsolatedCtx, unsetIsolatedCtx :: ResumeContext -> IO ()
    
    196
    +setIsolatedCtx   ctx = atomically $ writeTVar (resumeIsolated ctx) True
    
    197
    +unsetIsolatedCtx ctx = atomically $ writeTVar (resumeIsolated ctx) False
    
    194 198
     
    
    195 199
     -- ** Querying the global thread breakpoint map --------------------------------
    
    196 200
     
    
    ... ... @@ -201,64 +205,74 @@ unsetIsolatedThread tid = atomically $ do
    201 205
     -- the global thread mapping) and are asking about which other threads are
    
    202 206
     -- paused to list them.
    
    203 207
     listOtherPausedThreads :: IO [ThreadId]
    
    204
    -listOtherPausedThreads = do
    
    205
    -  m <- atomically $ readTVar globalDbgStatusVar
    
    206
    -  pure $ Map.keys $ Map.filter isBreak (threadEvalStatuses m)
    
    208
    +listOtherPausedThreads = atomically $ do
    
    209
    +  ctxs <- readTVar threadContextsVar
    
    210
    +  paused <- mapM isBreak (Map.toList ctxs)
    
    211
    +  pure [tid | (tid, True) <- paused]
    
    207 212
       where
    
    208
    -    isBreak EvalBreak{}     = True
    
    209
    -    isBreak EvalComplete{} = False
    
    210
    -
    
    211
    --- ** Global thread exec status map --------------------------------------------
    
    212
    -
    
    213
    --- | When we launch a new thread using the debugger, we map ... TODO ... all
    
    214
    --- threads launched by that main stmt may further spawn threads, and any of
    
    215
    --- them might hit a breakpoint (break array toggled, or per-thread step in
    
    216
    --- might be used, or pause button, etc...).
    
    217
    ---
    
    218
    --- When we launch that main thread we also write its result to this map. So
    
    219
    --- this map will only contain the results of threads we explicitly care to
    
    220
    --- observe the result of. Not all threads will write their results here,
    
    221
    --- certainly not ones spawned unknowningly by the main stmt.
    
    222
    --- ...
    
    223
    --- todo words
    
    224
    -globalDbgStatusVar :: TVar GlobalDbgStatus
    
    225
    -globalDbgStatusVar = unsafePerformIO $ newTVarIO $ GlobalDbgStatus Map.empty Set.empty
    
    226
    -{-# NOINLINE globalDbgStatusVar #-}
    
    227
    -
    
    228
    -data GlobalDbgStatus = GlobalDbgStatus
    
    229
    -  { threadEvalStatuses :: !(Map.Map ThreadId (EvalStatus [HValueRef]))
    
    230
    -  -- ^ Whenever a thread hits a breakpoint it inserts in this map its 'EvalBreak'.
    
    231
    -  -- We can additionally observe a thread's result by calling
    
    232
    -  -- `writeThreadEvalStatus tid (EvalComplete ...)` when the main expression
    
    233
    -  -- finishes evaluating, see e.g. 'sandboxIO'.
    
    234
    -  , isolatedThreads    :: !(Set.Set ThreadId)
    
    235
    -  -- ^ Threads in this map are meant to be ignored by 'readAnyThreadEvalBreak'.
    
    236
    -  -- That is, even if one of the threads in this set hits a breakpoint, we
    
    237
    -  -- can only read its 'EvalBreak' with @'readThreadEvalStatus' tid@ directly.
    
    238
    -  }
    
    213
    +    isBreak (tid, ResumeContext{resumeEvalStatus}) = do
    
    214
    +      status <- readTVar resumeEvalStatus
    
    215
    +      pure (tid, case status of
    
    216
    +                   Just EvalBreak{}    -> True
    
    217
    +                   Just EvalComplete{} -> False
    
    218
    +                   Nothing             -> False)
    
    239 219
     
    
    240 220
     --------------------------------------------------------------------------------
    
    241 221
     -- * Per-thread resume-ing
    
    242 222
     --------------------------------------------------------------------------------
    
    243 223
     
    
    244 224
     getThreadResumeContext :: ThreadId -> IO ResumeContext
    
    245
    -getThreadResumeContext tid = modifyMVar globalBreakActionMap $ \gbm0 -> do
    
    246
    -  case Map.lookup tid gbm0 of
    
    225
    +getThreadResumeContext tid = do
    
    226
    +  ctxs0 <- readTVarIO threadContextsVar
    
    227
    +  case Map.lookup tid ctxs0 of
    
    228
    +    Just ctx -> pure ctx -- common case: hence read without transaction
    
    247 229
         Nothing  -> do
    
    248
    -      r <- ResumeContext <$> newEmptyMVar
    
    249
    -      let !gbm1 = Map.insert tid r gbm0
    
    250
    -      pure (gbm1, r)
    
    251
    -    Just ctx -> do
    
    252
    -      pure (gbm0, ctx)
    
    230
    +      new_ctx <- ResumeContext <$> newEmptyMVar
    
    231
    +                               <*> newTVarIO Nothing
    
    232
    +                               <*> newTVarIO False
    
    233
    +      atomically $ do -- write new thread context atomically
    
    234
    +        ctxs1 <- readTVar threadContextsVar
    
    235
    +        case Map.lookup tid ctxs1 of
    
    236
    +          Just ctx -> pure ctx -- already exists
    
    237
    +          Nothing  -> do
    
    238
    +            let !ctxs2 = Map.insert tid new_ctx ctxs1
    
    239
    +            writeTVar threadContextsVar ctxs2
    
    240
    +            pure new_ctx
    
    253 241
     
    
    254
    --- | A global mapping from thread ids to the MVars on which they block when stopped.
    
    255
    -globalBreakActionMap :: MVar (Map.Map ThreadId ResumeContext)
    
    256
    -globalBreakActionMap = unsafePerformIO $ newMVar Map.empty
    
    257
    -{-# NOINLINE globalBreakActionMap #-}
    
    242
    +-- | A global mapping from threads to their per-thread state 'ResumeContext'
    
    243
    +-- (that e.g. contain the MVars on which they block when stopped)
    
    244
    +threadContextsVar :: TVar (Map.Map ThreadId ResumeContext)
    
    245
    +threadContextsVar = unsafePerformIO $ newTVarIO Map.empty
    
    246
    +{-# NOINLINE threadContextsVar #-}
    
    258 247
     
    
    259
    -newtype ResumeContext = ResumeContext
    
    260
    -  { resumeBreakMVar :: MVar ()
    
    248
    +-- | Global context for a thread, which is created and inserted in the global
    
    249
    +-- 'threadContextsVar' when a thread hits a breakpoint (and, thus, the
    
    250
    +-- 'globalBreakAction') is run, or when 'withBreakAction' sets the thread
    
    251
    +-- isolation (e.g. in 'sandboxIO'), or in other calls to
    
    252
    +-- 'getThreadResumeContext', the function which gets or creates a thread
    
    253
    +-- 'ResumeContext'.
    
    254
    +--
    
    255
    +-- TODO: Rename ResumeContext to something like ThreadContext or
    
    256
    +-- ThreadDbgContext or (Thread)BreakContext, and fields to something like
    
    257
    +-- dbgEvalStatus, threadIsolated, ...
    
    258
    +data ResumeContext = ResumeContext
    
    259
    +  { resumeBreakMVar :: !(MVar ())
    
    261 260
       -- ^ A thread that hits a breakpoint blocks reading its corresponding MVar
    
    262
    -  -- (gotten from the 'globalBreakActionMap').
    
    261
    +  -- (gotten from the 'threadContextsVar').
    
    263 262
       -- The debugger can unblock that thread by signaling its MVar.
    
    263
    +  , resumeEvalStatus :: !(TVar (Maybe (EvalStatus [HValueRef])))
    
    264
    +  -- ^ When a thread hits a breakpoint, the 'globalBreakAction' is run and
    
    265
    +  -- writes an 'EvalBreak' in this @TVar@ (which was found in 'threadContextsVar').
    
    266
    +  --
    
    267
    +  -- Additionally, the 'EvalComplete' result of a thread may be written here
    
    268
    +  -- using `writeThreadEvalStatus tid (EvalComplete ...)` after the main
    
    269
    +  -- expression finishes evaluating, see e.g. 'sandboxIO'.
    
    270
    +  --
    
    271
    +  -- The variable should be emptied (i.e. set to 'Nothing') by whoever
    
    272
    +  -- reads the status.
    
    273
    +  , resumeIsolated :: !(TVar Bool)
    
    274
    +  -- ^ When set, this thread is meant to be ignored by 'readAnyThreadEvalBreak'.
    
    275
    +  -- That is, if a thread with resumeIsolated=True hits a breakpoint, its
    
    276
    +  -- 'EvalBreak' can only be read with @'readThreadEvalStatus' tid@ for this
    
    277
    +  -- thread's @tid@ directly.
    
    264 278
       }

  • libraries/ghci/GHCi/Server.hs
    ... ... @@ -70,6 +70,14 @@ servWithCustom verbose hook pipe restore customHandler = loop
    70 70
     
    
    71 71
         when verbose $ trace ("msg: " ++ (show msg))
    
    72 72
         case msg of
    
    73
    +      -- The expressions which block waiting for a result musn't block the
    
    74
    +      -- server thread, so send them off and notify over the wire when done.
    
    75
    +      --
    
    76
    +      -- The other end (see 'remoteCall') will be processing notifications, and
    
    77
    +      -- it will use the returned pointer as the index for the matching notification.
    
    78
    +      WaitThreadResult                 {} -> forkIO (run msg >>= (notification =<< myThreadId)) >>= reply . mkRemoteRef
    
    79
    +      WaitAnyThreadBreak               {} -> forkIO (run msg >>= (notification =<< myThreadId)) >>= reply . mkRemoteRef
    
    80
    +      WaitThreadResultOrAnyThreadBreak {} -> forkIO (run msg >>= (notification =<< myThreadId)) >>= reply . mkRemoteRef
    
    73 81
           CustomMessage tag payload -> handleCustom tag payload
    
    74 82
           Shutdown -> return ()
    
    75 83
           RunTH st q ty loc -> wrapRunTH $ runTH pipe st q ty loc