ghc-9.12.1: GHC/Runtime/Interpreter/Process.hs
{-# LANGUAGE LambdaCase #-}
module GHC.Runtime.Interpreter.Process
(
-- * Message API
Message(..)
, DelayedResponse (..)
-- * Top-level message API (these acquire/release a lock)
, sendMessage
, sendMessageNoResponse
, sendMessageDelayedResponse
, receiveDelayedResponse
-- * Nested message API (these require the interpreter to already be locked)
, sendAnyValue
, receiveAnyValue
, receiveTHMessage
)
where
import GHC.Prelude
import GHC.Runtime.Interpreter.Types
import GHCi.Message
import GHC.IO (catchException)
import GHC.Utils.Panic
import GHC.Utils.Exception as Ex
import Data.Binary
import System.Exit
import System.Process
import Control.Concurrent.MVar (MVar, withMVar, takeMVar, putMVar, isEmptyMVar)
data DelayedResponse a = DelayedResponse
-- -----------------------------------------------------------------------------
-- Top-level Message API
-- | Send a message to the interpreter process that doesn't expect a response
-- (locks the interpreter while sending)
sendMessageNoResponse :: ExtInterpInstance d -> Message () -> IO ()
sendMessageNoResponse i m =
withLock i $ writeInterpProcess (instProcess i) (putMessage m)
-- | Send a message to the interpreter that expects a response
-- (locks the interpreter while until the response is received)
sendMessage :: Binary a => ExtInterpInstance d -> Message a -> IO a
sendMessage i m = withLock i $ callInterpProcess (instProcess i) m
-- | Send a message to the interpreter process whose response is expected later
--
-- This is useful to avoid forgetting to receive the value and to ensure that
-- the type of the response isn't lost. Use receiveDelayedResponse to read it.
-- (locks the interpreter until the response is received using
-- `receiveDelayedResponse`)
sendMessageDelayedResponse :: ExtInterpInstance d -> Message a -> IO (DelayedResponse a)
sendMessageDelayedResponse i m = do
lock i
writeInterpProcess (instProcess i) (putMessage m)
pure DelayedResponse
-- | Expect a delayed result to be received now
receiveDelayedResponse :: Binary a => ExtInterpInstance d -> DelayedResponse a -> IO a
receiveDelayedResponse i DelayedResponse = do
ensureLocked i
r <- readInterpProcess (instProcess i) get
unlock i
pure r
-- -----------------------------------------------------------------------------
-- Nested Message API
-- | Send any value (requires locked interpreter)
sendAnyValue :: Binary a => ExtInterpInstance d -> a -> IO ()
sendAnyValue i m = ensureLocked i >> writeInterpProcess (instProcess i) (put m)
-- | Expect a value to be received (requires locked interpreter)
receiveAnyValue :: ExtInterpInstance d -> Get a -> IO a
receiveAnyValue i get = ensureLocked i >> readInterpProcess (instProcess i) get
-- | Wait for a Template Haskell message (requires locked interpreter)
receiveTHMessage :: ExtInterpInstance d -> IO THMsg
receiveTHMessage i = ensureLocked i >> receiveAnyValue i getTHMessage
-- -----------------------------------------------------------------------------
getLock :: ExtInterpInstance d -> MVar ()
getLock = interpLock . instProcess
withLock :: ExtInterpInstance d -> IO a -> IO a
withLock i f = withMVar (getLock i) (const f)
lock :: ExtInterpInstance d -> IO ()
lock i = takeMVar (getLock i)
unlock :: ExtInterpInstance d -> IO ()
unlock i = putMVar (getLock i) ()
ensureLocked :: ExtInterpInstance d -> IO ()
ensureLocked i =
isEmptyMVar (getLock i) >>= \case
False -> panic "ensureLocked: external interpreter not locked"
_ -> pure ()
-- | Send a 'Message' and receive the response from the interpreter process
callInterpProcess :: Binary a => InterpProcess -> Message a -> IO a
callInterpProcess i msg =
remoteCall (interpPipe i) msg
`catchException` \(e :: SomeException) -> handleInterpProcessFailure i e
-- | Read a value from the interpreter process
readInterpProcess :: InterpProcess -> Get a -> IO a
readInterpProcess i get =
readPipe (interpPipe i) get
`catchException` \(e :: SomeException) -> handleInterpProcessFailure i e
-- | Send a value to the interpreter process
writeInterpProcess :: InterpProcess -> Put -> IO ()
writeInterpProcess i put =
writePipe (interpPipe i) put
`catchException` \(e :: SomeException) -> handleInterpProcessFailure i e
handleInterpProcessFailure :: InterpProcess -> SomeException -> IO a
handleInterpProcessFailure i e = do
let hdl = interpHandle i
ex <- getProcessExitCode hdl
case ex of
Just (ExitFailure n) ->
throwIO (InstallationError ("External interpreter terminated (" ++ show n ++ ")"))
_ -> do
terminateProcess hdl
_ <- waitForProcess hdl
throw e