Rodrigo Mesquita pushed to branch wip/romes/async-rpc at Glasgow Haskell Compiler / GHC

Commits:

11 changed files:

Changes:

  • compiler/GHC/Runtime/Interpreter.hs
    ... ... @@ -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 []
    

  • compiler/GHC/Runtime/Interpreter/JS.hs
    ... ... @@ -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
       ----------------------------------------------------------------
    

  • compiler/GHC/Runtime/Interpreter/Process.hs
    ... ... @@ -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

  • compiler/GHC/Runtime/Interpreter/Types.hs
    ... ... @@ -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
    

  • compiler/GHC/Runtime/Interpreter/Wasm.hs
    ... ... @@ -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 = ()
    

  • compiler/GHC/Tc/Gen/Splice.hs
    ... ... @@ -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
    

  • libraries/ghci/GHCi/Message.hs
    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

  • libraries/ghci/GHCi/Server.hs
    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
    

  • libraries/ghci/GHCi/TH.hs
    ... ... @@ -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
    

  • libraries/ghci/ghci.cabal.in
    ... ... @@ -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:
    

  • testsuite/tests/ghci/custom-external-interpreter-commands/Main.hs
    ... ... @@ -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