Rodrigo Mesquita pushed to branch wip/romes/async-rpc at Glasgow Haskell Compiler / GHC
Commits:
-
11710a3d
by Rodrigo Mesquita at 2026-10-12T18:12:00+01:00
11 changed files:
- 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
- compiler/GHC/Tc/Gen/Splice.hs
- libraries/ghci/GHCi/Message.hs
- libraries/ghci/GHCi/Server.hs
- libraries/ghci/GHCi/TH.hs
- libraries/ghci/ghci.cabal.in
- testsuite/tests/ghci/custom-external-interpreter-commands/Main.hs
Changes:
| ... | ... | @@ -626,11 +626,9 @@ spawnIServ conf = do |
| 626 | 626 | []
|
| 627 | 627 | (iservConfOpts conf)
|
| 628 | 628 | interpPipe <- mkPipeFromHandles rh wh
|
| 629 | - lock <- newMVar ()
|
|
| 630 | 629 | let process = InterpProcess
|
| 631 | 630 | { interpHandle = ph
|
| 632 | 631 | , interpPipe
|
| 633 | - , interpLock = lock
|
|
| 634 | 632 | }
|
| 635 | 633 | |
| 636 | 634 | pending_frees <- newMVar []
|
| ... | ... | @@ -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,9 @@ 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
|
|
| 6 | + , sendTHComputation
|
|
| 7 | + , RemoteCallFailed(..)
|
|
| 15 | 8 | )
|
| 16 | 9 | where
|
| 17 | 10 | |
| ... | ... | @@ -27,97 +20,67 @@ import GHC.Utils.Exception as Ex |
| 27 | 20 | import Data.Binary
|
| 28 | 21 | import System.Exit
|
| 29 | 22 | import System.Process
|
| 30 | -import Control.Concurrent.MVar (MVar, withMVar, takeMVar, putMVar, isEmptyMVar)
|
|
| 31 | - |
|
| 32 | -data DelayedResponse a = DelayedResponse
|
|
| 23 | +import Control.Concurrent
|
|
| 24 | +import Data.IORef
|
|
| 33 | 25 | |
| 34 | 26 | -- -----------------------------------------------------------------------------
|
| 35 | 27 | -- Top-level Message API
|
| 36 | 28 | |
| 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)
|
|
| 29 | +-- | Send a message to the interpreter and expect a response, blocking until it arrives.
|
|
| 30 | +--
|
|
| 31 | +-- This issues an asynchronous RPC on the external interpreter which does not
|
|
| 32 | +-- block the external interpreter thread. We block here on the host because we
|
|
| 33 | +-- wait for the asynchronous response to arrive.
|
|
| 34 | +--
|
|
| 35 | +-- Throws 'RemoteCallFailed' if the remote call threw an exception in the
|
|
| 36 | +-- external interpreter
|
|
| 45 | 37 | 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
|
|
| 38 | +sendMessage i msg = unwrapRight =<<
|
|
| 39 | + remoteCall (interpPipe proc) msg
|
|
| 40 | + `catchException` \(e :: SomeException) -> handleInterpProcessFailure proc e
|
|
| 41 | + where
|
|
| 42 | + proc = instProcess i
|
|
| 43 | + unwrapRight :: Either SomeException b -> IO b
|
|
| 44 | + unwrapRight (Right a) = pure a
|
|
| 45 | + unwrapRight (Left er) = do
|
|
| 46 | + throwIO (RemoteCallFailed er)
|
|
| 47 | + |
|
| 48 | +-- | Send a message with a TH computation to the ext. interpreter (like
|
|
| 49 | +-- 'RunTH', or 'RunModFinalizers') and handle 'THMessage' requests from the
|
|
| 50 | +-- ext. interpreter to us in the meantime.
|
|
| 49 | 51 | --
|
| 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 | - |
|
| 103 | - |
|
| 104 | --- | Send a 'Message' and receive the response from the interpreter process
|
|
| 105 | -callInterpProcess :: Binary a => InterpProcess -> Message a -> IO a
|
|
| 106 | -callInterpProcess i msg =
|
|
| 107 | - remoteCall (interpPipe i) msg
|
|
| 108 | - `catchException` \(e :: SomeException) -> handleInterpProcessFailure i e
|
|
| 109 | - |
|
| 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
|
|
| 52 | +-- This function blocks until the TH computation returns a value, but spawns in
|
|
| 53 | +-- the background a thread to handle the 'THMessage's until the TH computation
|
|
| 54 | +-- is fully done.
|
|
| 55 | +sendTHComputation
|
|
| 56 | + :: Binary a
|
|
| 57 | + => ExtInterpInstance d
|
|
| 58 | + -> Message a
|
|
| 59 | + -> (forall b. Binary b => THMessage b -> IO (Bool, b))
|
|
| 60 | + -- ^ Handle one message. Return True to end the loop.
|
|
| 61 | + -> IO a
|
|
| 62 | +sendTHComputation i msg reqHandler = mask $ \restore -> do
|
|
| 63 | + -- inline "withAsync/wait"
|
|
| 64 | + var <- newEmptyMVar
|
|
| 65 | + ht <- forkFinally (restore handlerLoop) (putMVar var)
|
|
| 66 | + restore (sendMessage i msg <* takeMVar var)
|
|
| 67 | + `finally` cancel ht var
|
|
| 68 | + where
|
|
| 69 | + proc = instProcess i
|
|
| 70 | + handleOne doneRef = do
|
|
| 71 | + handleRemoteTHCall (interpPipe proc) $ \req -> do
|
|
| 72 | + (stop, res) <- reqHandler req
|
|
| 73 | + writeIORef doneRef stop
|
|
| 74 | + return res
|
|
| 75 | + handlerLoop = do
|
|
| 76 | + doneRef <- newIORef False
|
|
| 77 | + let loop = do
|
|
| 78 | + handleOne doneRef
|
|
| 79 | + isDone <- readIORef doneRef
|
|
| 80 | + if isDone then return ()
|
|
| 81 | + else loop
|
|
| 82 | + in loop
|
|
| 83 | + cancel ht var = uninterruptibleMask_ (throwTo ht CancelTHHandler <* takeMVar var)
|
|
| 121 | 84 | |
| 122 | 85 | handleInterpProcessFailure :: InterpProcess -> SomeException -> IO a
|
| 123 | 86 | handleInterpProcessFailure i e = do
|
| ... | ... | @@ -130,3 +93,17 @@ handleInterpProcessFailure i e = do |
| 130 | 93 | terminateProcess hdl
|
| 131 | 94 | _ <- waitForProcess hdl
|
| 132 | 95 | throw e
|
| 96 | + |
|
| 97 | +-- | The external interpreter server threw an exception while executing this
|
|
| 98 | +-- remote call. The ext. interp is still live and ready to take further
|
|
| 99 | +-- commands (such as 'Shutdown').
|
|
| 100 | +data RemoteCallFailed = RemoteCallFailed SomeException deriving Show
|
|
| 101 | +instance Exception RemoteCallFailed where
|
|
| 102 | + displayException (RemoteCallFailed e)
|
|
| 103 | + = "A remote call in the external interpreter threw an exception:\n"
|
|
| 104 | + ++ displayException e
|
|
| 105 | + |
|
| 106 | +data CancelTHHandler = CancelTHHandler deriving Show
|
|
| 107 | +instance Exception CancelTHHandler where
|
|
| 108 | + fromException = asyncExceptionFromException
|
|
| 109 | + toException = asyncExceptionToException |
| ... | ... | @@ -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 = ()
|
| ... | ... | @@ -170,6 +170,7 @@ import Data.IORef |
| 170 | 170 | import GHC.Parser.HaddockLex (lexHsDoc)
|
| 171 | 171 | import GHC.Parser (parseIdentifier)
|
| 172 | 172 | import GHC.Rename.Doc (rnHsDoc)
|
| 173 | +import Data.Function
|
|
| 173 | 174 | |
| 174 | 175 | {-
|
| 175 | 176 | Note [Template Haskell state diagram]
|
| ... | ... | @@ -1165,11 +1166,12 @@ runRemoteModFinalizers (ThModFinalizers finRefs) = do |
| 1165 | 1166 | case th_state of
|
| 1166 | 1167 | Nothing -> return () -- TH was not started, nothing to do
|
| 1167 | 1168 | Just fhv -> do
|
| 1168 | - r <- liftIO $ withForeignRef fhv $ \st ->
|
|
| 1169 | - withForeignRefs finRefs $ \qrefs ->
|
|
| 1170 | - sendMessageDelayedResponse inst (RunModFinalizers st qrefs)
|
|
| 1171 | - () <- runRemoteTH inst []
|
|
| 1172 | - qr <- liftIO $ receiveDelayedResponse inst r
|
|
| 1169 | + qr <- withRunInIO $ \runInIO -> do
|
|
| 1170 | + recovers <- newIORef []
|
|
| 1171 | + withForeignRef fhv $ \st ->
|
|
| 1172 | + withForeignRefs finRefs $ \qrefs ->
|
|
| 1173 | + sendTHComputation inst (RunModFinalizers st qrefs) $ \msg ->
|
|
| 1174 | + runInIO (runRemoteTH recovers msg)
|
|
| 1173 | 1175 | checkQResult qr
|
| 1174 | 1176 | |
| 1175 | 1177 | runQResult
|
| ... | ... | @@ -1824,48 +1826,47 @@ runTH ty fhv = do |
| 1824 | 1826 | rstate <- getTHState inst
|
| 1825 | 1827 | loc <- location
|
| 1826 | 1828 | -- run a remote TH request
|
| 1827 | - r <- liftIO $
|
|
| 1829 | + qr <- withRunInIO $ \runInIO -> do
|
|
| 1830 | + recovers <- newIORef []
|
|
| 1828 | 1831 | withForeignRef rstate $ \state_hv ->
|
| 1829 | - withForeignRef fhv $ \q_hv ->
|
|
| 1830 | - sendMessageDelayedResponse inst (RunTH state_hv q_hv ty (Just loc))
|
|
| 1831 | - -- respond to requests from the interpreter
|
|
| 1832 | - runRemoteTH inst []
|
|
| 1833 | - -- get the final result
|
|
| 1834 | - qr <- liftIO $ receiveDelayedResponse inst r
|
|
| 1832 | + withForeignRef fhv $ \q_hv ->
|
|
| 1833 | + sendTHComputation inst (RunTH state_hv q_hv ty (Just loc)) $ \msg ->
|
|
| 1834 | + runInIO (runRemoteTH recovers msg)
|
|
| 1835 | 1835 | bs <- checkQResult qr
|
| 1836 | 1836 | return $! runGet get (LB.fromStrict bs)
|
| 1837 | 1837 | |
| 1838 | 1838 | |
| 1839 | --- | communicate with a remotely-running TH computation until it finishes.
|
|
| 1839 | +-- | Communicate with a remotely-running TH computation until it finishes.
|
|
| 1840 | 1840 | -- See Note [Remote Template Haskell] in libraries/ghci/GHCi/TH.hs.
|
| 1841 | 1841 | runRemoteTH
|
| 1842 | - :: ExtInterpInstance d
|
|
| 1843 | - -> [Messages TcRnMessage] -- saved from nested calls to qRecover
|
|
| 1844 | - -> TcM ()
|
|
| 1845 | -runRemoteTH inst recovers = do
|
|
| 1846 | - THMsg msg <- liftIO $ receiveTHMessage inst
|
|
| 1842 | + :: Binary a
|
|
| 1843 | + => IORef [Messages TcRnMessage] -- ^ saved from nested calls to qRecover
|
|
| 1844 | + -> THMessage a
|
|
| 1845 | + -> TcM (Bool, a)
|
|
| 1846 | +runRemoteTH recovers msg = do
|
|
| 1847 | 1847 | case msg of
|
| 1848 | - RunTHDone -> return ()
|
|
| 1848 | + RunTHDone -> return (True, ())
|
|
| 1849 | 1849 | StartRecover -> do -- Note [TH recover with -fexternal-interpreter]
|
| 1850 | 1850 | v <- getErrsVar
|
| 1851 | 1851 | msgs <- readTcRef v
|
| 1852 | 1852 | writeTcRef v emptyMessages
|
| 1853 | - runRemoteTH inst (msgs : recovers)
|
|
| 1853 | + modifyIORef recovers (msgs:) & liftIO
|
|
| 1854 | + return (False, ())
|
|
| 1854 | 1855 | EndRecover caught_error -> do
|
| 1855 | - let (prev_msgs, rest) = case recovers of
|
|
| 1856 | - [] -> panic "EndRecover"
|
|
| 1857 | - a : b -> (a,b)
|
|
| 1856 | + (prev_msgs, rest) <- liftIO $
|
|
| 1857 | + readIORef recovers >>= \case
|
|
| 1858 | + [] -> panic "EndRecover"
|
|
| 1859 | + a : b -> pure (a,b)
|
|
| 1858 | 1860 | v <- getErrsVar
|
| 1859 | 1861 | warn_msgs <- getWarningMessages <$> readTcRef v
|
| 1860 | 1862 | -- keep the warnings only if there were no errors
|
| 1861 | 1863 | writeTcRef v $ if caught_error
|
| 1862 | 1864 | then prev_msgs
|
| 1863 | 1865 | else mkMessages warn_msgs `unionMessages` prev_msgs
|
| 1864 | - runRemoteTH inst rest
|
|
| 1866 | + writeIORef recovers rest & liftIO
|
|
| 1867 | + return (False, ())
|
|
| 1865 | 1868 | _other -> do
|
| 1866 | - r <- handleTHMessage msg
|
|
| 1867 | - liftIO $ sendAnyValue inst r
|
|
| 1868 | - runRemoteTH inst recovers
|
|
| 1869 | + (False,) <$> handleTHMessage msg
|
|
| 1869 | 1870 | |
| 1870 | 1871 | -- | Check a QResult
|
| 1871 | 1872 | checkQResult :: QResult a -> TcM a
|
| 1 | -{-# LANGUAGE GADTs, DeriveGeneric, StandaloneDeriving, ScopedTypeVariables,
|
|
| 2 | - GeneralizedNewtypeDeriving, ExistentialQuantification, RecordWildCards,
|
|
| 3 | - CPP, NamedFieldPuns #-}
|
|
| 1 | +{-# LANGUAGE RecursiveDo, GADTs, DeriveGeneric, StandaloneDeriving, ScopedTypeVariables,
|
|
| 2 | + GeneralizedNewtypeDeriving, ExistentialQuantification, RecordWildCards, CPP,
|
|
| 3 | + NamedFieldPuns, LambdaCase, TypeAbstractions, TypeApplications,
|
|
| 4 | + LinearTypes, RankNTypes, UnboxedTuples, BlockArguments, TupleSections, MagicHash #-}
|
|
| 4 | 5 | {-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}
|
| 5 | 6 | |
| 6 | 7 | -- |
|
| ... | ... | @@ -22,10 +23,21 @@ module GHCi.Message |
| 22 | 23 | , ResumeContext(..)
|
| 23 | 24 | , QState(..)
|
| 24 | 25 | , getMessage, putMessage, getTHMessage, putTHMessage
|
| 25 | - , Pipe, mkPipeFromHandles, mkPipeFromContinuations, remoteCall, remoteTHCall, readPipe, writePipe
|
|
| 26 | + |
|
| 27 | + , Pipe, mkPipeFromHandles, mkPipeFromContinuations
|
|
| 28 | + , remoteCall, remoteTHCall
|
|
| 29 | + , handleRemoteCall, handleRemoteTHCall
|
|
| 30 | + |
|
| 26 | 31 | , BreakModule
|
| 27 | 32 | , BreakUnitId
|
| 28 | 33 | , LoadedDLL
|
| 34 | + |
|
| 35 | + -- * Low-level interface for async bidirectional RPC
|
|
| 36 | + , withAsyncReq
|
|
| 37 | + , withAsyncHandler
|
|
| 38 | + -- ** Consume `reply` exactly once
|
|
| 39 | + , WriteReply -- abstract
|
|
| 40 | + , writeReply
|
|
| 29 | 41 | ) where
|
| 30 | 42 | |
| 31 | 43 | import Prelude -- See note [Why do we import Prelude here?]
|
| ... | ... | @@ -45,6 +57,7 @@ import qualified GHC.Exts.Heap as Heap |
| 45 | 57 | import GHC.ForeignSrcLang
|
| 46 | 58 | import GHC.Fingerprint
|
| 47 | 59 | import GHC.Conc (pseq, par)
|
| 60 | +import GHC.IO (IO(..))
|
|
| 48 | 61 | import Control.Concurrent
|
| 49 | 62 | import Control.DeepSeq
|
| 50 | 63 | import Control.Exception
|
| ... | ... | @@ -63,6 +76,8 @@ import Data.Dynamic |
| 63 | 76 | import Data.Typeable (TypeRep)
|
| 64 | 77 | import Data.IORef
|
| 65 | 78 | import Data.Map (Map)
|
| 79 | +import Data.IntMap (IntMap)
|
|
| 80 | +import qualified Data.IntMap as IntMap
|
|
| 66 | 81 | import Foreign
|
| 67 | 82 | import GHC.Generics
|
| 68 | 83 | import GHC.Stack.CCS
|
| ... | ... | @@ -71,6 +86,11 @@ import qualified GHC.Boot.TH.Monad as TH |
| 71 | 86 | import System.Exit
|
| 72 | 87 | import System.IO
|
| 73 | 88 | import System.IO.Error
|
| 89 | +import Control.Monad
|
|
| 90 | +import Control.Concurrent.STM
|
|
| 91 | +import Data.Bifunctor
|
|
| 92 | +import GHC.Base (State#)
|
|
| 93 | +import GHC.Exts (RealWorld)
|
|
| 74 | 94 | |
| 75 | 95 | -- -----------------------------------------------------------------------------
|
| 76 | 96 | -- The RPC protocol between GHC and the interactive server
|
| ... | ... | @@ -693,9 +713,11 @@ serializeBCOs rbcos = parMap doChunk (chunkList 100 rbcos) |
| 693 | 713 | |
| 694 | 714 | -- | An opaque pipe for bidirectional binary data transmission.
|
| 695 | 715 | data Pipe = Pipe
|
| 696 | - { getSome :: !(IO ByteString)
|
|
| 697 | - , putAll :: !(B.Builder -> IO ())
|
|
| 698 | - , pipeLeftovers :: !(IORef (Maybe ByteString))
|
|
| 716 | + { getSome :: !(IO ByteString)
|
|
| 717 | + , putAll :: !(B.Builder -> IO ())
|
|
| 718 | + , pipeLeftovers :: !(IORef (Maybe ByteString))
|
|
| 719 | + , pipeLock :: !(MVar ()) -- ^ Lock to prevent concurrent access to the stream
|
|
| 720 | + , pipeAsyncMngr :: !AsyncManager
|
|
| 699 | 721 | }
|
| 700 | 722 | |
| 701 | 723 | -- | Make a 'Pipe' from a 'Handle' to read and a 'Handle' to write.
|
| ... | ... | @@ -705,30 +727,193 @@ mkPipeFromHandles pipeRead pipeWrite = do |
| 705 | 727 | putAll b = do
|
| 706 | 728 | B.hPutBuilder pipeWrite b
|
| 707 | 729 | hFlush pipeWrite
|
| 708 | - pipeLeftovers <- newIORef Nothing
|
|
| 709 | - pure $ Pipe { getSome, putAll, pipeLeftovers }
|
|
| 730 | + mkPipeFromContinuations getSome putAll
|
|
| 710 | 731 | |
| 711 | 732 | -- | Make a 'Pipe' from a reader function and a writer function.
|
| 712 | 733 | mkPipeFromContinuations :: IO ByteString -> (B.Builder -> IO ()) -> IO Pipe
|
| 713 | -mkPipeFromContinuations getSome putAll = do
|
|
| 714 | - pipeLeftovers <- newIORef Nothing
|
|
| 715 | - pure $ Pipe { getSome, putAll, pipeLeftovers }
|
|
| 734 | +mkPipeFromContinuations getSome putAll = mdo
|
|
| 735 | + pipeLeftovers <- newIORef Nothing
|
|
| 736 | + pipeLock <- newMVar ()
|
|
| 737 | + mdo
|
|
| 738 | + pipeAsyncMngr <- newAsyncManager p
|
|
| 739 | + let p = Pipe { .. }
|
|
| 740 | + pure p
|
|
| 741 | + |
|
| 742 | +-- | Send a 'Message' request to and wait for a reply from the interpreter
|
|
| 743 | +-- (blocks on the calling side, doesn't necessarily block the interpreter)
|
|
| 744 | +remoteCall :: Binary a => Pipe -> Message a -> IO (Either SomeException a)
|
|
| 745 | +remoteCall pipe msg = bimap fromSerializableException id . runGet get
|
|
| 746 | + <$> withAsyncReq pipe (putMessage msg)
|
|
| 747 | + |
|
| 748 | +-- | Handle a single 'Message' request on the interpreter and reply to the host
|
|
| 749 | +handleRemoteCall :: Pipe
|
|
| 750 | + -> (Msg -> IO Msg) {-^ Msg hook -}
|
|
| 751 | + -> (forall a. (Show a, Binary a) => Message a -> IO a)
|
|
| 752 | + -> IO ()
|
|
| 753 | +handleRemoteCall pipe hook k =
|
|
| 754 | + withAsyncHandler pipe getMessage \writer msg0 -> IO \s0 ->
|
|
| 755 | + case hook msg0 |#> s0 of -- unIO for linearity checking
|
|
| 756 | + (# s1, Msg msg #) -> case k msg |#> s1 of
|
|
| 757 | + (# s2, a #) -> writeReply writer (put a) |#> s2
|
|
| 758 | + |
|
| 759 | +-- | Send a 'THMessage' request to the host and wait for a reply from the host
|
|
| 760 | +-- to the interpreter
|
|
| 761 | +remoteTHCall :: Binary a => Pipe -> THMessage a -> IO (Either SomeException a)
|
|
| 762 | +remoteTHCall pipe msg = bimap fromSerializableException id . runGet get
|
|
| 763 | + <$> withAsyncReq pipe (putTHMessage msg)
|
|
| 764 | + |
|
| 765 | +-- | Handle a 'THMessage' request on the host and reply to the interpreter.
|
|
| 766 | +handleRemoteTHCall
|
|
| 767 | + :: Pipe -> (forall a. (Show a, Binary a) => THMessage a -> IO a) -> IO ()
|
|
| 768 | +handleRemoteTHCall pipe k =
|
|
| 769 | + withAsyncHandler pipe getTHMessage \writer (THMsg msg) -> IO \s0 ->
|
|
| 770 | + case k msg |#> s0 of -- unIO for linearity checking
|
|
| 771 | + (# s1, a #) -> writeReply writer (put a) |#> s1
|
|
| 772 | + |
|
| 773 | +--------------------------------------------------------------------------------
|
|
| 774 | +-- * Matching requests with async replies
|
|
| 775 | +--------------------------------------------------------------------------------
|
|
| 776 | + |
|
| 777 | +-- | Send an asynchronous request and block waiting for a matching reply
|
|
| 778 | +withAsyncReq :: Pipe -> Put -> IO LB.ByteString
|
|
| 779 | +withAsyncReq pipe msg = do
|
|
| 780 | + |
|
| 781 | + -- Mark pending request
|
|
| 782 | + uq <- freshReqId (pipeAsyncMngr pipe)
|
|
| 783 | + wv <- newEmptyMVar
|
|
| 784 | + atomicModifyIORef' (pendingSent (pipeAsyncMngr pipe)) $
|
|
| 785 | + \m -> (IntMap.insert uq wv m, ())
|
|
| 786 | + |
|
| 787 | + -- Write request
|
|
| 788 | + writePipe pipe (Request uq (runPut msg))
|
|
| 789 | + |
|
| 790 | + -- Block waiting for reply
|
|
| 791 | + takeMVar wv
|
|
| 792 | + |
|
| 793 | +-- | Receive a single asynchronous request and send a matching reply
|
|
| 794 | +-- asynchronously (the handler may fork a computation that eventually replies
|
|
| 795 | +-- and can return before actually replying, as long as it eventually does).
|
|
| 796 | +--
|
|
| 797 | +-- The handler receives as an argument the function to use to reply to the request.
|
|
| 798 | +-- It must be used exactly once (all requests expect a reply, and you can't
|
|
| 799 | +-- reply more than once). Linear types enforce the handler replies in every
|
|
| 800 | +-- branch, and if the handler otherwise throws, it is handled by 'withAsyncHandler'.
|
|
| 801 | +withAsyncHandler :: Pipe -> Get a -> (WriteReply Put %1 -> a -> IO r) -> IO r
|
|
| 802 | +withAsyncHandler pipe getit k = do
|
|
| 803 | + ack <- newEmptyMVar
|
|
| 804 | + |
|
| 805 | + -- Block waiting for request
|
|
| 806 | + (uq, bs) <- atomically (readTQueue (pendingRecv (pipeAsyncMngr pipe)))
|
|
| 807 | + |
|
| 808 | + let reply p = do
|
|
| 809 | + writePipe pipe (Reply uq (runPut $ either (put . toSerializableException) id p))
|
|
| 810 | + putMVar ack ()
|
|
| 811 | + |
|
| 812 | + -- Run the continuation which must reply exactly once with 'writeReply'
|
|
| 813 | + k (WriteReply reply) (runGet getit bs)
|
|
| 814 | + |
|
| 815 | + -- If there's an exception before the reply is sent, reply with an error instead.
|
|
| 816 | + `catch` \(e :: SomeException) -> do
|
|
| 817 | + |
|
| 818 | + tryTakeMVar ack >>= \case
|
|
| 819 | + Just () ->
|
|
| 820 | + -- Already replied
|
|
| 821 | + throwIO e
|
|
| 822 | + Nothing -> do
|
|
| 823 | + -- Reply with an error
|
|
| 824 | + reply (Left e)
|
|
| 825 | + throwIO e
|
|
| 826 | + |
|
| 827 | +-- ** Async matching "internals" -----------------------------------------------
|
|
| 828 | + |
|
| 829 | +data AsyncManager = AsyncManager
|
|
| 830 | + { pendingSent :: !(IORef (IntMap (MVar LB.ByteString)))
|
|
| 831 | + -- ^ When a request is sent, keep track that we're expecting a reply. The
|
|
| 832 | + -- MVar is signaled when the reply arrives.
|
|
| 833 | + , pendingRecv :: !(TQueue (CorrelationId, LB.ByteString))
|
|
| 834 | + -- ^ A queue of pending received requests. When a request is received,
|
|
| 835 | + -- queue it here. A request handler will pop the next request from it.
|
|
| 836 | + -- (Nothing except the 'drainPipe' thread should read from the pipe).
|
|
| 837 | + --
|
|
| 838 | + -- Seq contract: Dequeue from left, enqueue on right
|
|
| 839 | + , asyncDrainTid :: !ThreadId
|
|
| 840 | + , asyncNextReq :: !(IORef Int)
|
|
| 841 | + }
|
|
| 842 | + |
|
| 843 | +type CorrelationId = Int
|
|
| 844 | + |
|
| 845 | +-- | We have a single type for requests and replies because both the host and
|
|
| 846 | +-- interpreter may send requests *and* replies on the write end *and* on the read end.
|
|
| 847 | +--
|
|
| 848 | +-- For instance:
|
|
| 849 | +-- 1. The host sends a RunTH request on its Host Write End (HWE)
|
|
| 850 | +-- 2. The interpreter sends a NewName TH request on its Interpreter Write End (IWE)
|
|
| 851 | +-- 3. The host sends a reply to the TH request on its HWE
|
|
| 852 | +-- 4. The interpreter sends the reply to the RunTH request on its IWE
|
|
| 853 | +data IddMsg = Request !CorrelationId LB.ByteString
|
|
| 854 | + | Reply !CorrelationId LB.ByteString
|
|
| 855 | + |
|
| 856 | +instance Binary IddMsg where
|
|
| 857 | + put (Request i bs) = putWord8 0 >> put i >> put bs
|
|
| 858 | + put (Reply i bs) = putWord8 1 >> put i >> put bs
|
|
| 859 | + get = do
|
|
| 860 | + b <- getWord8
|
|
| 861 | + case b of
|
|
| 862 | + 0 -> Request <$> get <*> get
|
|
| 863 | + 1 -> Reply <$> get <*> get
|
|
| 864 | + _ -> error $ "impossible: Binary IddMsg - " ++ show b
|
|
| 716 | 865 | |
| 717 | -remoteCall :: Binary a => Pipe -> Message a -> IO a
|
|
| 718 | -remoteCall pipe msg = do
|
|
| 719 | - writePipe pipe (putMessage msg)
|
|
| 720 | - readPipe pipe get
|
|
| 866 | +newAsyncManager :: Pipe -> IO AsyncManager
|
|
| 867 | +newAsyncManager pipe = do
|
|
| 868 | + asyncNextReq <- newIORef 0
|
|
| 869 | + pendingSent <- newIORef IntMap.empty
|
|
| 870 | + pendingRecv <- newTQueueIO
|
|
| 721 | 871 | |
| 722 | -writePipe :: Pipe -> Put -> IO ()
|
|
| 723 | -writePipe Pipe{..} put = putAll $ execPut put
|
|
| 872 | + mdo
|
|
| 873 | + -- Fork thread to drain read end.
|
|
| 874 | + asyncDrainTid <- forkIO (drainPipe pipe am)
|
|
| 875 | + -- todo: proper thread clean up and no leaks
|
|
| 724 | 876 | |
| 725 | -remoteTHCall :: Binary a => Pipe -> THMessage a -> IO a
|
|
| 726 | -remoteTHCall pipe msg = do
|
|
| 727 | - writePipe pipe (putTHMessage msg)
|
|
| 728 | - readPipe pipe get
|
|
| 877 | + let am = AsyncManager{..}
|
|
| 878 | + pure am
|
|
| 729 | 879 | |
| 730 | -readPipe :: Pipe -> Get a -> IO a
|
|
| 731 | -readPipe Pipe{..} get = do
|
|
| 880 | +-- | Drains the read end of the Pipe and match 'Reply'es against pending 'Request's.
|
|
| 881 | +--
|
|
| 882 | +-- In pratice, we'll have both a thread draining the read end of the host's
|
|
| 883 | +-- 'Pipe' and a thread draining the read end of the interpreter's 'Pipe', since
|
|
| 884 | +-- both processes construct a 'Pipe' and send and receive messages:
|
|
| 885 | +drainPipe :: Pipe -> AsyncManager -> IO ()
|
|
| 886 | +drainPipe p am = forever $ do
|
|
| 887 | + readPipe p >>= \case
|
|
| 888 | + -- Receive a reply to a pending notification
|
|
| 889 | + Reply uq bs -> do
|
|
| 890 | + wv <- atomicModifyIORef' (pendingSent am) $ \m -> do
|
|
| 891 | + case IntMap.lookup uq m of
|
|
| 892 | + Nothing -> error "drainPipe: Received reply to no pending request"
|
|
| 893 | + Just wv -> (IntMap.delete uq m, wv)
|
|
| 894 | + putMVar wv bs -- signal matching request
|
|
| 895 | + -- Receive a request and queue it
|
|
| 896 | + Request uq bs ->
|
|
| 897 | + atomically $ writeTQueue (pendingRecv am) (uq,bs)
|
|
| 898 | + |
|
| 899 | +freshReqId :: AsyncManager -> IO CorrelationId
|
|
| 900 | +freshReqId AsyncManager{asyncNextReq} =
|
|
| 901 | + atomicModifyIORef' asyncNextReq $ \i -> (i+1, i)
|
|
| 902 | + |
|
| 903 | +--------------------------------------------------------------------------------
|
|
| 904 | +-- * Pipe thread-safe read/write
|
|
| 905 | +--------------------------------------------------------------------------------
|
|
| 906 | +-- All messages on the wire are 'IddMsg', which contain a correlation header
|
|
| 907 | +-- and identify whether they are requests or replies.
|
|
| 908 | + |
|
| 909 | +writePipe :: Pipe -> IddMsg -> IO ()
|
|
| 910 | +writePipe p@Pipe{..} = withLock p . putAll . execPut . put
|
|
| 911 | + |
|
| 912 | +-- | Only the 'drainPipe' thread should read from the pipe directly. Other
|
|
| 913 | +-- functions should wait for the matching reply to be delivered asynchronously.
|
|
| 914 | +-- (see 'withAsyncReq'). It doesn't lock to read bc it assumes single reader is 'drainPipe'.
|
|
| 915 | +readPipe :: Pipe -> IO IddMsg
|
|
| 916 | +readPipe Pipe{..} = do
|
|
| 732 | 917 | leftovers <- readIORef pipeLeftovers
|
| 733 | 918 | m <- getBin getSome get leftovers
|
| 734 | 919 | case m of
|
| ... | ... | @@ -758,3 +943,22 @@ getBin getsome get leftover = go leftover (runGetIncremental get) |
| 758 | 943 | else go Nothing (fun (Just b))
|
| 759 | 944 | go _lft (Fail _rest _off str) =
|
| 760 | 945 | throwIO (ErrorCall ("getBin: " ++ str))
|
| 946 | + |
|
| 947 | +withLock :: Pipe -> IO c -> IO c
|
|
| 948 | +withLock Pipe{..} = bracket (takeMVar pipeLock) (putMVar pipeLock) . const
|
|
| 949 | + |
|
| 950 | +--------------------------------------------------------------------------------
|
|
| 951 | +-- * Consume `reply` exactly once
|
|
| 952 | +--------------------------------------------------------------------------------
|
|
| 953 | + |
|
| 954 | +-- | Every request should get exactly one reply.
|
|
| 955 | +data WriteReply a where
|
|
| 956 | + WriteReply :: (Either SomeException a -> IO ()) %1 -> WriteReply a
|
|
| 957 | + |
|
| 958 | +-- | Write a reply exactly once (consuming the ability to write more replies)
|
|
| 959 | +writeReply :: WriteReply a %1 -> a -> IO ()
|
|
| 960 | +writeReply (WriteReply r) = \x -> r (Right x)
|
|
| 961 | + |
|
| 962 | +-- | Infix, linear, 'unIO'
|
|
| 963 | +(|#>) :: IO a %1 -> State# RealWorld -> (# State# RealWorld, a #)
|
|
| 964 | +(|#>) (IO fs) s = fs s |
| 1 | -{-# LANGUAGE CPP, RankNTypes, RecordWildCards, GADTs, ScopedTypeVariables #-}
|
|
| 1 | +{-# LANGUAGE LambdaCase, CPP, RankNTypes, RecordWildCards, GADTs, ScopedTypeVariables #-}
|
|
| 2 | 2 | module GHCi.Server
|
| 3 | 3 | ( MessageHook
|
| 4 | 4 | , CustomMessageHandler
|
| ... | ... | @@ -15,6 +15,7 @@ import GHCi.Signals |
| 15 | 15 | import GHCi.TH
|
| 16 | 16 | import GHCi.Message
|
| 17 | 17 | import Data.ByteString (ByteString)
|
| 18 | +import Data.IORef
|
|
| 18 | 19 | #if defined(wasm32_HOST_ARCH)
|
| 19 | 20 | import qualified Data.ByteString.Builder as B
|
| 20 | 21 | import qualified Data.ByteString.Internal as B
|
| ... | ... | @@ -60,27 +61,26 @@ servWithCustom |
| 60 | 61 | -> (forall a .IO a -> IO a)
|
| 61 | 62 | -> CustomMessageHandler
|
| 62 | 63 | -> IO ()
|
| 63 | -servWithCustom verbose hook pipe restore customHandler = loop
|
|
| 64 | +servWithCustom verbose hook pipe restore customHandler = newIORef False >>= loop
|
|
| 64 | 65 | where
|
| 65 | - loop = do
|
|
| 66 | + loop doneRef = do
|
|
| 66 | 67 | when verbose $ trace "reading pipe..."
|
| 67 | - Msg msg <- readPipe pipe getMessage >>= hook
|
|
| 68 | + handleRemoteCall pipe hook $ \(msg :: Message a) -> do
|
|
| 68 | 69 | |
| 69 | - discardCtrlC
|
|
| 70 | + discardCtrlC
|
|
| 70 | 71 | |
| 71 | - when verbose $ trace ("msg: " ++ (show msg))
|
|
| 72 | - case msg of
|
|
| 73 | - CustomMessage tag payload -> handleCustom tag payload
|
|
| 74 | - Shutdown -> return ()
|
|
| 75 | - RunTH st q ty loc -> wrapRunTH $ runTH pipe st q ty loc
|
|
| 76 | - RunModFinalizers st qrefs -> wrapRunTH $ runModFinalizerRefs pipe st qrefs
|
|
| 77 | - _other -> run msg >>= reply
|
|
| 72 | + when verbose $ trace ("msg: " ++ (show msg))
|
|
| 73 | + case msg of
|
|
| 74 | + CustomMessage tag payload -> handleCustom tag payload
|
|
| 75 | + Shutdown -> writeIORef doneRef True >> reply ()
|
|
| 76 | + -- TODO: These two should probably be done off the main thread
|
|
| 77 | + RunTH st q ty loc -> wrapRunTH $ runTH pipe st q ty loc
|
|
| 78 | + RunModFinalizers st qrefs -> wrapRunTH $ runModFinalizerRefs pipe st qrefs
|
|
| 79 | + _other -> run msg >>= reply
|
|
| 78 | 80 | |
| 79 | - reply :: forall a. (Binary a, Show a) => a -> IO ()
|
|
| 80 | - reply r = do
|
|
| 81 | - when verbose $ trace ("writing pipe: " ++ show r)
|
|
| 82 | - writePipe pipe (put r)
|
|
| 83 | - loop
|
|
| 81 | + isDone <- readIORef doneRef
|
|
| 82 | + if isDone then return ()
|
|
| 83 | + else loop doneRef
|
|
| 84 | 84 | |
| 85 | 85 | handleCustom tag payload = do
|
| 86 | 86 | mresp <- customHandler tag payload
|
| ... | ... | @@ -89,17 +89,24 @@ servWithCustom verbose hook pipe restore customHandler = loop |
| 89 | 89 | Nothing ->
|
| 90 | 90 | error $ "GHCi.Server: unhandled CustomMessage with tag " ++ show tag
|
| 91 | 91 | |
| 92 | + reply :: forall a. Show a => a -> IO a
|
|
| 93 | + reply r = do
|
|
| 94 | + when verbose $ trace ("writing pipe: " ++ show r)
|
|
| 95 | + return r -- the result of the handler gets sent
|
|
| 96 | + |
|
| 92 | 97 | -- Run some TH code, which may interact with GHC by sending
|
| 93 | 98 | -- THMessage requests, and then finally send RunTHDone followed by a
|
| 94 | 99 | -- QResult. For an overview of how TH works with Remote GHCi, see
|
| 95 | 100 | -- Note [Remote Template Haskell] in libraries/ghci/GHCi/TH.hs.
|
| 96 | - wrapRunTH :: forall a. (Binary a, Show a) => IO a -> IO ()
|
|
| 101 | + wrapRunTH :: forall a. (Binary a, Show a) => IO a -> IO (QResult a)
|
|
| 97 | 102 | wrapRunTH io = do
|
| 98 | 103 | when verbose $ trace "wrapRunTH..."
|
| 99 | 104 | r <- try io
|
| 100 | 105 | when verbose $ trace "wrapRunTH done."
|
| 101 | 106 | when verbose $ trace "writing RunTHDone."
|
| 102 | - writePipe pipe (putTHMessage RunTHDone)
|
|
| 107 | + remoteTHCall pipe RunTHDone >>= \case
|
|
| 108 | + Right () -> pure ()
|
|
| 109 | + Left err -> throwIO (GHCiQUncaughtExceptionOnHost err)
|
|
| 103 | 110 | case r of
|
| 104 | 111 | Left e
|
| 105 | 112 | | Just (GHCiQException _ err) <- fromException e -> do
|
| ... | ... | @@ -10,6 +10,7 @@ module GHCi.TH |
| 10 | 10 | , runModFinalizerRefs
|
| 11 | 11 | , runTH
|
| 12 | 12 | , GHCiQException(..)
|
| 13 | + , GHCiQUncaughtExceptionOnHost(..)
|
|
| 13 | 14 | ) where
|
| 14 | 15 | |
| 15 | 16 | {- Note [Remote Template Haskell]
|
| ... | ... | @@ -158,8 +159,13 @@ ghcCmd m = GHCiQ $ \sRef -> do |
| 158 | 159 | s <- readIORef sRef
|
| 159 | 160 | r <- remoteTHCall (qsPipe s) m
|
| 160 | 161 | case r of
|
| 161 | - THException str -> throwIO (GHCiQException s str)
|
|
| 162 | - THComplete res -> return res
|
|
| 162 | + Right (THException str) -> throwIO (GHCiQException s str)
|
|
| 163 | + Right (THComplete res) -> return res
|
|
| 164 | + Left e -> liftIO $ throwIO (GHCiQUncaughtExceptionOnHost e)
|
|
| 165 | + |
|
| 166 | +-- | An exception was thrown on the host while processing a 'remoteTHCall'
|
|
| 167 | +data GHCiQUncaughtExceptionOnHost = GHCiQUncaughtExceptionOnHost SomeException deriving Show
|
|
| 168 | +instance Exception GHCiQUncaughtExceptionOnHost
|
|
| 163 | 169 | |
| 164 | 170 | instance MonadIO GHCiQ where
|
| 165 | 171 | liftIO m = GHCiQ $ \_ -> m
|
| ... | ... | @@ -189,9 +195,9 @@ metaHandlersGHCiQ runInIO = TH.MetaHandlers { |
| 189 | 195 | -- See Note [TH recover with -fexternal-interpreter] in GHC.Tc.Gen.Splice
|
| 190 | 196 | , mRecover = \h a -> runInIO $ GHCiQ $ \sRef -> mask $ \unmask -> do
|
| 191 | 197 | s <- readIORef sRef
|
| 192 | - remoteTHCall (qsPipe s) StartRecover
|
|
| 198 | + remoteTHCall (qsPipe s) StartRecover >>= either (throwIO . GHCiQUncaughtExceptionOnHost) pure
|
|
| 193 | 199 | e <- try $ unmask $ runGHCiQ (runQinGHCiQ a <* ghcCmd FailIfErrs) sRef
|
| 194 | - remoteTHCall (qsPipe s) (EndRecover (isLeft e))
|
|
| 200 | + remoteTHCall (qsPipe s) (EndRecover (isLeft e)) >>= either (throwIO . GHCiQUncaughtExceptionOnHost) pure
|
|
| 195 | 201 | case e of
|
| 196 | 202 | Left GHCiQException{} ->
|
| 197 | 203 | runGHCiQ (runQinGHCiQ h) sRef
|
| ... | ... | @@ -95,7 +95,8 @@ library |
| 95 | 95 | filepath >= 1.4 && < 1.6,
|
| 96 | 96 | ghc-boot == @ProjectVersionMunged@,
|
| 97 | 97 | ghc-heap >= 9.10.1 && <=@ProjectVersionMunged@,
|
| 98 | - transformers >= 0.5 && < 0.7
|
|
| 98 | + transformers >= 0.5 && < 0.7,
|
|
| 99 | + stm >= 2.4 && < 2.6
|
|
| 99 | 100 | |
| 100 | 101 | if flag(bootstrap)
|
| 101 | 102 | build-depends:
|
| ... | ... | @@ -144,7 +144,7 @@ customMessage :: (Show a, Bin.Binary a) => Pipe -> ClientCommand a -> IO a |
| 144 | 144 | customMessage pipe c = do
|
| 145 | 145 | let payload = encodeLazy (Some @Bin.Binary c)
|
| 146 | 146 | putStrLn $ "Sending: " ++ show c
|
| 147 | - respBytes <- remoteCall pipe (CustomMessage customTag payload)
|
|
| 147 | + Right respBytes <- remoteCall pipe (CustomMessage customTag payload)
|
|
| 148 | 148 | case decodeLazy respBytes of
|
| 149 | 149 | Left err -> error $ "Decode error: " ++ err
|
| 150 | 150 | Right res -> pure res
|