Rodrigo Mesquita pushed to branch wip/romes/multi-threaded-debugger at Glasgow Haskell Compiler / GHC
Commits:
-
5f600346
by Rodrigo Mesquita at 2026-10-06T17:45:16+01:00
-
1c15fcb9
by Rodrigo Mesquita at 2026-10-06T19:27:25+01:00
-
2540e67a
by Rodrigo Mesquita at 2026-10-06T19:28:18+01:00
9 changed files:
- compiler/GHC/Runtime/Eval.hs
- compiler/GHC/Runtime/Interpreter.hs
- compiler/GHC/Runtime/Interpreter/JS.hs
- compiler/GHC/Runtime/Interpreter/Process.hs
- compiler/GHC/Runtime/Interpreter/Types.hs
- compiler/GHC/Runtime/Interpreter/Wasm.hs
- libraries/ghci/GHCi/Message.hs
- libraries/ghci/GHCi/Run.hs
- libraries/ghci/GHCi/Server.hs
Changes:
| ... | ... | @@ -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) ->
|
| ... | ... | @@ -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 | |
| ... | ... | @@ -626,11 +637,9 @@ spawnIServ conf = do |
| 626 | 637 | []
|
| 627 | 638 | (iservConfOpts conf)
|
| 628 | 639 | interpPipe <- mkPipeFromHandles rh wh
|
| 629 | - lock <- newMVar ()
|
|
| 630 | 640 | let process = InterpProcess
|
| 631 | 641 | { interpHandle = ph
|
| 632 | 642 | , interpPipe
|
| 633 | - , interpLock = lock
|
|
| 634 | 643 | }
|
| 635 | 644 | |
| 636 | 645 | pending_frees <- newMVar []
|
| ... | ... | @@ -771,3 +780,9 @@ readIModModBreaks hug mod = imodBreaks_modBreaks . expectJust <$> readIModBreaks |
| 771 | 780 | fromEvalResult :: EvalResult a -> IO a
|
| 772 | 781 | fromEvalResult (EvalException e) = throwIO (fromSerializableException e)
|
| 773 | 782 | fromEvalResult (EvalSuccess a) = return a
|
| 783 | + |
|
| 784 | +clearEvalStatus :: EvalStatus a -> EvalStatus ()
|
|
| 785 | +clearEvalStatus = \case
|
|
| 786 | + EvalComplete w (EvalException se) -> EvalComplete w (EvalException se)
|
|
| 787 | + EvalComplete w (EvalSuccess _) -> EvalComplete w (EvalSuccess ())
|
|
| 788 | + EvalBreak ap mb rt rccs -> EvalBreak ap mb rt rccs |
| ... | ... | @@ -127,11 +127,9 @@ startTHRunnerProcess interp_js settings = do |
| 127 | 127 | std_in <- readIORef interp_in
|
| 128 | 128 | |
| 129 | 129 | interpPipe <- mkPipeFromHandles rh wh
|
| 130 | - lock <- newMVar ()
|
|
| 131 | 130 | let proc = InterpProcess
|
| 132 | 131 | { interpHandle = hdl
|
| 133 | 132 | , interpPipe
|
| 134 | - , interpLock = lock
|
|
| 135 | 133 | }
|
| 136 | 134 | pure (std_in, proc)
|
| 137 | 135 | |
| ... | ... | @@ -382,7 +380,7 @@ jsLinkPlan logger tmpfs tmp_dir ar_cache link_cfg cfg inst link_plan = do |
| 382 | 380 | -- Haskell server.
|
| 383 | 381 | server_started <- jsServerStarted <$> readMVar (instJSState (instExtra inst))
|
| 384 | 382 | if server_started
|
| 385 | - then sendMessageNoResponse inst $ LoadObj all_js
|
|
| 383 | + then sendMessage inst $ LoadObj all_js
|
|
| 386 | 384 | else jsLoadFile inst all_js
|
| 387 | 385 | |
| 388 | 386 | ----------------------------------------------------------------
|
| ... | ... | @@ -2,16 +2,7 @@ module GHC.Runtime.Interpreter.Process |
| 2 | 2 | (
|
| 3 | 3 | -- * Message API
|
| 4 | 4 | Message(..)
|
| 5 | - , DelayedResponse (..)
|
|
| 6 | - -- * Top-level message API (these acquire/release a lock)
|
|
| 7 | 5 | , sendMessage
|
| 8 | - , sendMessageNoResponse
|
|
| 9 | - , sendMessageDelayedResponse
|
|
| 10 | - , receiveDelayedResponse
|
|
| 11 | - -- * Nested message API (these require the interpreter to already be locked)
|
|
| 12 | - , sendAnyValue
|
|
| 13 | - , receiveAnyValue
|
|
| 14 | - , receiveTHMessage
|
|
| 15 | 6 | )
|
| 16 | 7 | where
|
| 17 | 8 | |
| ... | ... | @@ -27,79 +18,13 @@ import GHC.Utils.Exception as Ex |
| 27 | 18 | import Data.Binary
|
| 28 | 19 | import System.Exit
|
| 29 | 20 | import System.Process
|
| 30 | -import Control.Concurrent.MVar (MVar, withMVar, takeMVar, putMVar, isEmptyMVar)
|
|
| 31 | - |
|
| 32 | -data DelayedResponse a = DelayedResponse
|
|
| 33 | 21 | |
| 34 | 22 | -- -----------------------------------------------------------------------------
|
| 35 | 23 | -- Top-level Message API
|
| 36 | 24 | |
| 37 | --- | Send a message to the interpreter process that doesn't expect a response
|
|
| 38 | --- (locks the interpreter while sending)
|
|
| 39 | -sendMessageNoResponse :: ExtInterpInstance d -> Message () -> IO ()
|
|
| 40 | -sendMessageNoResponse i m =
|
|
| 41 | - withLock i $ writeInterpProcess (instProcess i) (putMessage m)
|
|
| 42 | - |
|
| 43 | --- | Send a message to the interpreter that expects a response
|
|
| 44 | --- (locks the interpreter while until the response is received)
|
|
| 25 | +-- | Send a message to the interpreter and expect a response, blocking until it arrives.
|
|
| 45 | 26 | sendMessage :: Binary a => ExtInterpInstance d -> Message a -> IO a
|
| 46 | -sendMessage i m = withLock i $ callInterpProcess (instProcess i) m
|
|
| 47 | - |
|
| 48 | --- | Send a message to the interpreter process whose response is expected later
|
|
| 49 | ---
|
|
| 50 | --- This is useful to avoid forgetting to receive the value and to ensure that
|
|
| 51 | --- the type of the response isn't lost. Use receiveDelayedResponse to read it.
|
|
| 52 | --- (locks the interpreter until the response is received using
|
|
| 53 | --- `receiveDelayedResponse`)
|
|
| 54 | -sendMessageDelayedResponse :: ExtInterpInstance d -> Message a -> IO (DelayedResponse a)
|
|
| 55 | -sendMessageDelayedResponse i m = do
|
|
| 56 | - lock i
|
|
| 57 | - writeInterpProcess (instProcess i) (putMessage m)
|
|
| 58 | - pure DelayedResponse
|
|
| 59 | - |
|
| 60 | --- | Expect a delayed result to be received now
|
|
| 61 | -receiveDelayedResponse :: Binary a => ExtInterpInstance d -> DelayedResponse a -> IO a
|
|
| 62 | -receiveDelayedResponse i DelayedResponse = do
|
|
| 63 | - ensureLocked i
|
|
| 64 | - r <- readInterpProcess (instProcess i) get
|
|
| 65 | - unlock i
|
|
| 66 | - pure r
|
|
| 67 | - |
|
| 68 | --- -----------------------------------------------------------------------------
|
|
| 69 | --- Nested Message API
|
|
| 70 | - |
|
| 71 | --- | Send any value (requires locked interpreter)
|
|
| 72 | -sendAnyValue :: Binary a => ExtInterpInstance d -> a -> IO ()
|
|
| 73 | -sendAnyValue i m = ensureLocked i >> writeInterpProcess (instProcess i) (put m)
|
|
| 74 | - |
|
| 75 | --- | Expect a value to be received (requires locked interpreter)
|
|
| 76 | -receiveAnyValue :: ExtInterpInstance d -> Get a -> IO a
|
|
| 77 | -receiveAnyValue i get = ensureLocked i >> readInterpProcess (instProcess i) get
|
|
| 78 | - |
|
| 79 | --- | Wait for a Template Haskell message (requires locked interpreter)
|
|
| 80 | -receiveTHMessage :: ExtInterpInstance d -> IO THMsg
|
|
| 81 | -receiveTHMessage i = ensureLocked i >> receiveAnyValue i getTHMessage
|
|
| 82 | - |
|
| 83 | --- -----------------------------------------------------------------------------
|
|
| 84 | - |
|
| 85 | -getLock :: ExtInterpInstance d -> MVar ()
|
|
| 86 | -getLock = interpLock . instProcess
|
|
| 87 | - |
|
| 88 | -withLock :: ExtInterpInstance d -> IO a -> IO a
|
|
| 89 | -withLock i f = withMVar (getLock i) (const f)
|
|
| 90 | - |
|
| 91 | -lock :: ExtInterpInstance d -> IO ()
|
|
| 92 | -lock i = takeMVar (getLock i)
|
|
| 93 | - |
|
| 94 | -unlock :: ExtInterpInstance d -> IO ()
|
|
| 95 | -unlock i = putMVar (getLock i) ()
|
|
| 96 | - |
|
| 97 | -ensureLocked :: ExtInterpInstance d -> IO ()
|
|
| 98 | -ensureLocked i =
|
|
| 99 | - isEmptyMVar (getLock i) >>= \case
|
|
| 100 | - False -> panic "ensureLocked: external interpreter not locked"
|
|
| 101 | - _ -> pure ()
|
|
| 102 | - |
|
| 27 | +sendMessage i m = callInterpProcess (instProcess i) m
|
|
| 103 | 28 | |
| 104 | 29 | -- | Send a 'Message' and receive the response from the interpreter process
|
| 105 | 30 | callInterpProcess :: Binary a => InterpProcess -> Message a -> IO a
|
| ... | ... | @@ -107,18 +32,6 @@ callInterpProcess i msg = |
| 107 | 32 | remoteCall (interpPipe i) msg
|
| 108 | 33 | `catchException` \(e :: SomeException) -> handleInterpProcessFailure i e
|
| 109 | 34 | |
| 110 | --- | Read a value from the interpreter process
|
|
| 111 | -readInterpProcess :: InterpProcess -> Get a -> IO a
|
|
| 112 | -readInterpProcess i get =
|
|
| 113 | - readPipe (interpPipe i) get
|
|
| 114 | - `catchException` \(e :: SomeException) -> handleInterpProcessFailure i e
|
|
| 115 | - |
|
| 116 | --- | Send a value to the interpreter process
|
|
| 117 | -writeInterpProcess :: InterpProcess -> Put -> IO ()
|
|
| 118 | -writeInterpProcess i put =
|
|
| 119 | - writePipe (interpPipe i) put
|
|
| 120 | - `catchException` \(e :: SomeException) -> handleInterpProcessFailure i e
|
|
| 121 | - |
|
| 122 | 35 | handleInterpProcessFailure :: InterpProcess -> SomeException -> IO a
|
| 123 | 36 | handleInterpProcessFailure i e = do
|
| 124 | 37 | let hdl = interpHandle i
|
| ... | ... | @@ -111,7 +111,6 @@ type WasmInterp = ExtInterpState WasmInterpConfig () |
| 111 | 111 | data InterpProcess = InterpProcess
|
| 112 | 112 | { interpPipe :: !Pipe -- ^ Pipe to communicate with the server
|
| 113 | 113 | , interpHandle :: !ProcessHandle -- ^ Process handle of the server
|
| 114 | - , interpLock :: !(MVar ()) -- ^ Lock to prevent concurrent access to the stream
|
|
| 115 | 114 | }
|
| 116 | 115 | |
| 117 | 116 | -- | Status of an external interpreter
|
| ... | ... | @@ -73,14 +73,12 @@ spawnWasmInterp WasmInterpConfig {..} = do |
| 73 | 73 | hSetBuffering rh NoBuffering
|
| 74 | 74 | interpPipe <- mkPipeFromHandles rh wh
|
| 75 | 75 | pending_frees <- newMVar []
|
| 76 | - lock <- newMVar ()
|
|
| 77 | 76 | pure
|
| 78 | 77 | $ ExtInterpInstance
|
| 79 | 78 | { instProcess =
|
| 80 | 79 | InterpProcess
|
| 81 | 80 | { interpHandle = ph,
|
| 82 | - interpPipe,
|
|
| 83 | - interpLock = lock
|
|
| 81 | + interpPipe
|
|
| 84 | 82 | },
|
| 85 | 83 | instPendingFrees = pending_frees,
|
| 86 | 84 | instExtra = ()
|
| ... | ... | @@ -12,7 +12,7 @@ |
| 12 | 12 | module GHCi.Message
|
| 13 | 13 | ( Message(..), Msg(..)
|
| 14 | 14 | , ConInfoTable(..)
|
| 15 | - , THMessage(..), THMsg(..)
|
|
| 15 | + , THMessage(..) -- , THMsg(..)
|
|
| 16 | 16 | , QResult(..)
|
| 17 | 17 | , EvalStatus_(..,EvalBreak), EvalStatus, EvalResult(..), EvalOpts(..), EvalExpr(..)
|
| 18 | 18 | , EvalBreak(..), EvalBreakpoint (..)
|
| ... | ... | @@ -20,7 +20,7 @@ module GHCi.Message |
| 20 | 20 | , toSerializableException, fromSerializableException
|
| 21 | 21 | , THResult(..), THResultType(..)
|
| 22 | 22 | , QState(..)
|
| 23 | - , getMessage, putMessage, getTHMessage, putTHMessage
|
|
| 23 | + , getMessage, putMessage, {- getTHMessage, -} putTHMessage
|
|
| 24 | 24 | , Pipe, mkPipeFromHandles, mkPipeFromContinuations, remoteCall, remoteTHCall, readPipe, writePipe
|
| 25 | 25 | , BreakModule
|
| 26 | 26 | , BreakUnitId
|
| ... | ... | @@ -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 IntMap
|
|
| 65 | 67 | import Foreign
|
| 66 | 68 | import GHC.Generics
|
| 67 | 69 | import GHC.Stack.CCS
|
| ... | ... | @@ -70,6 +72,7 @@ import qualified GHC.Boot.TH.Monad as TH |
| 70 | 72 | import System.Exit
|
| 71 | 73 | import System.IO
|
| 72 | 74 | import System.IO.Error
|
| 75 | +import Control.Monad
|
|
| 73 | 76 | |
| 74 | 77 | -- -----------------------------------------------------------------------------
|
| 75 | 78 | -- The RPC protocol between GHC and the interactive server
|
| ... | ... | @@ -132,13 +135,13 @@ data Message a where |
| 132 | 135 | EvalStmt
|
| 133 | 136 | :: EvalOpts
|
| 134 | 137 | -> EvalExpr HValueRef {- IO [a] -}
|
| 135 | - -> Message (EvalStatus [HValueRef]) {- [a] -}
|
|
| 138 | + -> Message (RemoteRef ThreadId) {- [a] -}
|
|
| 136 | 139 | |
| 137 | 140 | -- | Resume evaluation of a statement after a breakpoint
|
| 138 | 141 | ResumeStmt
|
| 139 | 142 | :: EvalOpts
|
| 140 | 143 | -> RemoteRef ThreadId
|
| 141 | - -> Message (EvalStatus [HValueRef])
|
|
| 144 | + -> Message ()
|
|
| 142 | 145 | |
| 143 | 146 | -- | Abandon evaluation of a statement after a breakpoint
|
| 144 | 147 | AbandonStmt
|
| ... | ... | @@ -240,12 +243,23 @@ data Message a where |
| 240 | 243 | -- | Evaluate something. This is used to support :force in GHCi.
|
| 241 | 244 | Seq
|
| 242 | 245 | :: HValueRef
|
| 243 | - -> Message (EvalStatus ())
|
|
| 246 | + -> Message (RemoteRef ThreadId)
|
|
| 244 | 247 | |
| 245 | 248 | -- | Resume forcing a free variable in a breakpoint (#2950)
|
| 246 | 249 | ResumeSeq
|
| 247 | 250 | :: RemoteRef ThreadId
|
| 248 | - -> Message (EvalStatus ())
|
|
| 251 | + -> Message ()
|
|
| 252 | + |
|
| 253 | + WaitThreadResult
|
|
| 254 | + :: RemoteRef ThreadId
|
|
| 255 | + -> Message (EvalStatus [HValueRef])
|
|
| 256 | + |
|
| 257 | + WaitAnyThreadBreak
|
|
| 258 | + :: Message EvalBreak
|
|
| 259 | + |
|
| 260 | + WaitThreadResultOrAnyThreadBreak
|
|
| 261 | + :: RemoteRef ThreadId
|
|
| 262 | + -> Message (EvalStatus [HValueRef])
|
|
| 249 | 263 | |
| 250 | 264 | -- | User-defined request encoded as a tag/payload pair. This is left
|
| 251 | 265 | -- uninterpreted by GHC and is meant for GHC API applications to be able to supply
|
| ... | ... | @@ -323,40 +337,40 @@ data THMessage a where |
| 323 | 337 | |
| 324 | 338 | deriving instance Show (THMessage a)
|
| 325 | 339 | |
| 326 | -data THMsg = forall a . (Binary a, Show a) => THMsg (THMessage a)
|
|
| 327 | - |
|
| 328 | -getTHMessage :: Get THMsg
|
|
| 329 | -getTHMessage = do
|
|
| 330 | - b <- getWord8
|
|
| 331 | - case b of
|
|
| 332 | - 0 -> THMsg <$> NewName <$> get
|
|
| 333 | - 1 -> THMsg <$> (Report <$> get <*> get)
|
|
| 334 | - 2 -> THMsg <$> (LookupName <$> get <*> get)
|
|
| 335 | - 3 -> THMsg <$> Reify <$> get
|
|
| 336 | - 4 -> THMsg <$> ReifyFixity <$> get
|
|
| 337 | - 5 -> THMsg <$> (ReifyInstances <$> get <*> get)
|
|
| 338 | - 6 -> THMsg <$> ReifyRoles <$> get
|
|
| 339 | - 7 -> THMsg <$> (ReifyAnnotations <$> get <*> get)
|
|
| 340 | - 8 -> THMsg <$> ReifyModule <$> get
|
|
| 341 | - 9 -> THMsg <$> ReifyConStrictness <$> get
|
|
| 342 | - 10 -> THMsg <$> AddDependentFile <$> get
|
|
| 343 | - 11 -> THMsg <$> AddTempFile <$> get
|
|
| 344 | - 12 -> THMsg <$> AddTopDecls <$> get
|
|
| 345 | - 13 -> THMsg <$> (IsExtEnabled <$> get)
|
|
| 346 | - 14 -> THMsg <$> return ExtsEnabled
|
|
| 347 | - 15 -> THMsg <$> return StartRecover
|
|
| 348 | - 16 -> THMsg <$> EndRecover <$> get
|
|
| 349 | - 17 -> THMsg <$> return FailIfErrs
|
|
| 350 | - 18 -> return (THMsg RunTHDone)
|
|
| 351 | - 19 -> THMsg <$> AddModFinalizer <$> get
|
|
| 352 | - 20 -> THMsg <$> (AddForeignFilePath <$> get <*> get)
|
|
| 353 | - 21 -> THMsg <$> AddCorePlugin <$> get
|
|
| 354 | - 22 -> THMsg <$> ReifyType <$> get
|
|
| 355 | - 23 -> THMsg <$> (PutDoc <$> get <*> get)
|
|
| 356 | - 24 -> THMsg <$> GetDoc <$> get
|
|
| 357 | - 25 -> THMsg <$> return GetPackageRoot
|
|
| 358 | - 26 -> THMsg <$> AddDependentDirectory <$> get
|
|
| 359 | - n -> error ("getTHMessage: unknown message " ++ show n)
|
|
| 340 | +-- data THMsg = forall a . (Binary a, Show a) => THMsg (THMessage a)
|
|
| 341 | +--
|
|
| 342 | +-- getTHMessage :: Get THMsg
|
|
| 343 | +-- getTHMessage = do
|
|
| 344 | +-- b <- getWord8
|
|
| 345 | +-- case b of
|
|
| 346 | +-- 0 -> THMsg <$> NewName <$> get
|
|
| 347 | +-- 1 -> THMsg <$> (Report <$> get <*> get)
|
|
| 348 | +-- 2 -> THMsg <$> (LookupName <$> get <*> get)
|
|
| 349 | +-- 3 -> THMsg <$> Reify <$> get
|
|
| 350 | +-- 4 -> THMsg <$> ReifyFixity <$> get
|
|
| 351 | +-- 5 -> THMsg <$> (ReifyInstances <$> get <*> get)
|
|
| 352 | +-- 6 -> THMsg <$> ReifyRoles <$> get
|
|
| 353 | +-- 7 -> THMsg <$> (ReifyAnnotations <$> get <*> get)
|
|
| 354 | +-- 8 -> THMsg <$> ReifyModule <$> get
|
|
| 355 | +-- 9 -> THMsg <$> ReifyConStrictness <$> get
|
|
| 356 | +-- 10 -> THMsg <$> AddDependentFile <$> get
|
|
| 357 | +-- 11 -> THMsg <$> AddTempFile <$> get
|
|
| 358 | +-- 12 -> THMsg <$> AddTopDecls <$> get
|
|
| 359 | +-- 13 -> THMsg <$> (IsExtEnabled <$> get)
|
|
| 360 | +-- 14 -> THMsg <$> return ExtsEnabled
|
|
| 361 | +-- 15 -> THMsg <$> return StartRecover
|
|
| 362 | +-- 16 -> THMsg <$> EndRecover <$> get
|
|
| 363 | +-- 17 -> THMsg <$> return FailIfErrs
|
|
| 364 | +-- 18 -> return (THMsg RunTHDone)
|
|
| 365 | +-- 19 -> THMsg <$> AddModFinalizer <$> get
|
|
| 366 | +-- 20 -> THMsg <$> (AddForeignFilePath <$> get <*> get)
|
|
| 367 | +-- 21 -> THMsg <$> AddCorePlugin <$> get
|
|
| 368 | +-- 22 -> THMsg <$> ReifyType <$> get
|
|
| 369 | +-- 23 -> THMsg <$> (PutDoc <$> get <*> get)
|
|
| 370 | +-- 24 -> THMsg <$> GetDoc <$> get
|
|
| 371 | +-- 25 -> THMsg <$> return GetPackageRoot
|
|
| 372 | +-- 26 -> THMsg <$> AddDependentDirectory <$> get
|
|
| 373 | +-- n -> error ("getTHMessage: unknown message " ++ show n)
|
|
| 360 | 374 | |
| 361 | 375 | putTHMessage :: THMessage a -> Put
|
| 362 | 376 | putTHMessage m = case m of
|
| ... | ... | @@ -719,11 +733,27 @@ serializeBCOs rbcos = parMap doChunk (chunkList 100 rbcos) |
| 719 | 733 | -- -----------------------------------------------------------------------------
|
| 720 | 734 | -- Reading/writing messages
|
| 721 | 735 | |
| 736 | +type CorrelationId = Int
|
|
| 737 | +data Request = Request !CorrelationId Msg
|
|
| 738 | +data Reply = Reply !CorrelationId LB.ByteString
|
|
| 739 | + |
|
| 740 | +instance Binary Request where
|
|
| 741 | + put (Request i (Msg m)) = put i >> putMessage m
|
|
| 742 | + get = Request <$> get <*> getMessage
|
|
| 743 | + |
|
| 744 | +instance Binary Reply where
|
|
| 745 | + put (Reply i bs) = put i >> put bs
|
|
| 746 | + get = Reply <$> get <*> get
|
|
| 747 | + |
|
| 722 | 748 | -- | An opaque pipe for bidirectional binary data transmission.
|
| 723 | 749 | data Pipe = Pipe
|
| 724 | - { getSome :: !(IO ByteString)
|
|
| 725 | - , putAll :: !(B.Builder -> IO ())
|
|
| 726 | - , pipeLeftovers :: !(IORef (Maybe ByteString))
|
|
| 750 | + { getSome :: !(IO ByteString)
|
|
| 751 | + , putAll :: !(B.Builder -> IO ())
|
|
| 752 | + , pipeLeftovers :: !(IORef (Maybe ByteString))
|
|
| 753 | + , pipeLock :: !(MVar ()) -- ^ Lock to prevent concurrent access to the stream
|
|
| 754 | + , pipePending :: !(IORef (IntMap (MVar LB.ByteString)))
|
|
| 755 | + , pipeDrainThread :: !(MVar ThreadId)
|
|
| 756 | + , pipeNextReq :: !(IORef Int)
|
|
| 727 | 757 | }
|
| 728 | 758 | |
| 729 | 759 | -- | Make a 'Pipe' from a 'Handle' to read and a 'Handle' to write.
|
| ... | ... | @@ -733,30 +763,59 @@ mkPipeFromHandles pipeRead pipeWrite = do |
| 733 | 763 | putAll b = do
|
| 734 | 764 | B.hPutBuilder pipeWrite b
|
| 735 | 765 | hFlush pipeWrite
|
| 736 | - pipeLeftovers <- newIORef Nothing
|
|
| 737 | - pure $ Pipe { getSome, putAll, pipeLeftovers }
|
|
| 766 | + mkPipeFromContinuations getSome putAll
|
|
| 738 | 767 | |
| 739 | 768 | -- | Make a 'Pipe' from a reader function and a writer function.
|
| 740 | 769 | mkPipeFromContinuations :: IO ByteString -> (B.Builder -> IO ()) -> IO Pipe
|
| 741 | 770 | mkPipeFromContinuations getSome putAll = do
|
| 742 | - pipeLeftovers <- newIORef Nothing
|
|
| 743 | - pure $ Pipe { getSome, putAll, pipeLeftovers }
|
|
| 771 | + pipeLeftovers <- newIORef Nothing
|
|
| 772 | + pipeLock <- newMVar ()
|
|
| 773 | + pipePending <- newIORef IntMap.empty
|
|
| 774 | + pipeDrainThread <- newEmptyMVar
|
|
| 775 | + pipeNextReq <- newIORef 0
|
|
| 776 | + let p = Pipe { .. }
|
|
| 777 | + |
|
| 778 | + -- Fork thread to drain read end.
|
|
| 779 | + drainTid <- forkIO (drainPipe p)
|
|
| 780 | + putMVar pipeDrainThread drainTid
|
|
| 781 | + -- todo: proper thread clean up and no leaks
|
|
| 744 | 782 | |
| 745 | -remoteCall :: Binary a => Pipe -> Message a -> IO a
|
|
| 783 | + pure p
|
|
| 784 | + |
|
| 785 | +remoteCall :: (Show a, Binary a) => Pipe -> Message a -> IO a
|
|
| 746 | 786 | remoteCall pipe msg = do
|
| 747 | - writePipe pipe (putMessage msg)
|
|
| 748 | - readPipe pipe get
|
|
| 749 | 787 | |
| 750 | -writePipe :: Pipe -> Put -> IO ()
|
|
| 751 | -writePipe Pipe{..} put = putAll $ execPut put
|
|
| 788 | + -- Mark pending
|
|
| 789 | + uq <- freshReqId pipe
|
|
| 790 | + wv <- newEmptyMVar
|
|
| 791 | + atomicModifyIORef' (pipePending pipe) $
|
|
| 792 | + \m -> (IntMap.insert uq wv m, ())
|
|
| 793 | + |
|
| 794 | + -- Write request
|
|
| 795 | + writePipe pipe (put (Request uq (Msg msg)))
|
|
| 796 | + |
|
| 797 | + -- Block waiting for reply
|
|
| 798 | + runGet get <$> takeMVar wv
|
|
| 752 | 799 | |
| 753 | 800 | remoteTHCall :: Binary a => Pipe -> THMessage a -> IO a
|
| 754 | 801 | remoteTHCall pipe msg = do
|
| 755 | 802 | writePipe pipe (putTHMessage msg)
|
| 756 | 803 | readPipe pipe get
|
| 757 | 804 | |
| 805 | +drainPipe :: Pipe -> IO ()
|
|
| 806 | +drainPipe p = forever $ do
|
|
| 807 | + Reply uq bs <- readPipe p get
|
|
| 808 | + wv <- atomicModifyIORef' (pipePending p) $ \m -> do
|
|
| 809 | + case IntMap.lookup uq m of
|
|
| 810 | + Nothing -> error "drainPipe: Received reply to no pending request"
|
|
| 811 | + Just wv -> (IntMap.delete uq m, wv)
|
|
| 812 | + putMVar wv bs -- signal matching request
|
|
| 813 | + |
|
| 814 | +writePipe :: Pipe -> Put -> IO ()
|
|
| 815 | +writePipe p@Pipe{..} put = withLock p $ putAll $ execPut put
|
|
| 816 | + |
|
| 758 | 817 | readPipe :: Pipe -> Get a -> IO a
|
| 759 | -readPipe Pipe{..} get = do
|
|
| 818 | +readPipe p@Pipe{..} get = withLock p $ do
|
|
| 760 | 819 | leftovers <- readIORef pipeLeftovers
|
| 761 | 820 | m <- getBin getSome get leftovers
|
| 762 | 821 | case m of
|
| ... | ... | @@ -786,3 +845,9 @@ getBin getsome get leftover = go leftover (runGetIncremental get) |
| 786 | 845 | else go Nothing (fun (Just b))
|
| 787 | 846 | go _lft (Fail _rest _off str) =
|
| 788 | 847 | throwIO (ErrorCall ("getBin: " ++ str))
|
| 848 | + |
|
| 849 | +withLock :: Pipe -> IO c -> IO c
|
|
| 850 | +withLock Pipe{..} = bracket (takeMVar pipeLock) (putMVar pipeLock) . const
|
|
| 851 | + |
|
| 852 | +freshReqId :: Pipe -> IO Int
|
|
| 853 | +freshReqId Pipe{pipeNextReq} = atomicModifyIORef' pipeNextReq $ \i -> (i+1, i) |
| ... | ... | @@ -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,7 +326,7 @@ 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
|
| ... | ... | @@ -336,17 +346,31 @@ sandboxIO opts io |
| 336 | 346 | tid <- forkIO $ do
|
| 337 | 347 | |
| 338 | 348 | takeMVar wait -- don't run anything in this thread before
|
| 339 | - -- `withBreakAction` has a chance to set up.
|
|
| 349 | + -- `setBreakAction` has a chance to set up.
|
|
| 340 | 350 | |
| 341 | 351 | tid <- myThreadId
|
| 342 | 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)
|
|
| 343 | 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?
|
|
| 344 | 364 | |
| 345 | - -- We are running in uninterruptibleMask
|
|
| 346 | - withBreakAction opts tid $ \waitForResult -> do
|
|
| 347 | - putMVar wait ()
|
|
| 348 | - redirectInterrupts tid $ unsafeUnmask $
|
|
| 349 | - 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
|
|
| 350 | 374 | |
| 351 | 375 | | otherwise
|
| 352 | 376 | = do
|
| ... | ... | @@ -360,7 +384,17 @@ sandboxIO opts io |
| 360 | 384 | -- tracing, etc.) need the expression to be running in a
|
| 361 | 385 | -- separate thread, so debugging is only enabled when
|
| 362 | 386 | -- using the sandbox.
|
| 363 | - 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.
|
|
| 364 | 398 | where
|
| 365 | 399 | runIt = measureAlloc $ tryEval $ rethrow opts $ clearCCS io
|
| 366 | 400 | |
| ... | ... | @@ -415,12 +449,16 @@ sandboxIO opts io |
| 415 | 449 | -- whenever "main" or one of the threads spawned by "main" hit a breakpoint,
|
| 416 | 450 | -- rather than just "main" being observed. This should be used for the main
|
| 417 | 451 | -- debuggee execution of a debugger.
|
| 418 | -withBreakAction :: EvalOpts -> ThreadId -> (IO (EvalStatus [HValueRef]) -> IO a) -> IO a
|
|
| 419 | -withBreakAction opts tid@(ThreadId tid#) act = do
|
|
| 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
|
|
| 420 | 457 | ctx <- getThreadResumeContext tid
|
| 421 | - bracket (setBreakAction ctx) (resetBreakAction ctx) (\_ -> act (waitForResult ctx))
|
|
| 458 | + setBreakAction' ctx
|
|
| 459 | + -- bracket _ (resetBreakAction ctx) (\_ -> act (waitForResult ctx))
|
|
| 422 | 460 | where
|
| 423 | - setBreakAction ctx = do
|
|
| 461 | + setBreakAction' ctx = do
|
|
| 424 | 462 | -- TODO: TERRIBLY NOT MULTI-THREAD FRIENDLY. Each thread needs its own RTS flag exceptionFlag, like we have for step-in or step-out.
|
| 425 | 463 | runIf breakOnException $ \_ -> poke exceptionFlag 1
|
| 426 | 464 | -- Breaking on exceptions is not enabled by default, since it
|
| ... | ... | @@ -433,7 +471,7 @@ withBreakAction opts tid@(ThreadId tid#) act = do |
| 433 | 471 | runIfCtx ctx isolateThreadBreaks setIsolatedCtx
|
| 434 | 472 | |
| 435 | 473 | resetBreakAction ctx () = do
|
| 436 | - poke exceptionFlag 0
|
|
| 474 | + poke exceptionFlag 0 -- just wrong, expection flag should be per-thread
|
|
| 437 | 475 | rts_disableStopAfterReturn tid#
|
| 438 | 476 | rts_disableStopNextBreakpoint tid#
|
| 439 | 477 | runIfCtx ctx isolateThreadBreaks unsetIsolatedCtx
|
| ... | ... | @@ -485,9 +523,3 @@ redirectInterrupts target wait = do |
| 485 | 523 | Nothing -> wait
|
| 486 | 524 | Just target -> do throwTo target (e :: SomeException); wait
|
| 487 | 525 | |
| 488 | -clearEvalStatus :: EvalStatus a -> EvalStatus ()
|
|
| 489 | -clearEvalStatus = \case
|
|
| 490 | - EvalComplete w (EvalException se) -> EvalComplete w (EvalException se)
|
|
| 491 | - EvalComplete w (EvalSuccess _) -> EvalComplete w (EvalSuccess ())
|
|
| 492 | - EvalBreak ap mb rt rccs -> EvalBreak ap mb rt rccs
|
|
| 493 | - |
| ... | ... | @@ -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
|