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

Commits:

10 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,8 @@ 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
    
    15 7
       )
    
    16 8
     where
    
    17 9
     
    
    ... ... @@ -27,97 +19,62 @@ import GHC.Utils.Exception as Ex
    27 19
     import Data.Binary
    
    28 20
     import System.Exit
    
    29 21
     import System.Process
    
    30
    -import Control.Concurrent.MVar (MVar, withMVar, takeMVar, putMVar, isEmptyMVar)
    
    31
    -
    
    32
    -data DelayedResponse a = DelayedResponse
    
    22
    +import Control.Concurrent
    
    23
    +import Data.IORef
    
    24
    +import Control.Concurrent.STM
    
    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.
    
    45 34
     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
    
    35
    +sendMessage i msg =
    
    36
    +  remoteCall (interpPipe proc) msg
    
    37
    +    `catchException` \(e :: SomeException) -> handleInterpProcessFailure proc e
    
    38
    +  where
    
    39
    +    proc = instProcess i
    
    40
    +
    
    41
    +-- | Send a message with a TH computation to the ext. interpreter (like
    
    42
    +-- 'RunTH', or 'RunModFinalizers') and handle 'THMessage' requests from the
    
    43
    +-- ext. interpreter to us in the meantime.
    
    49 44
     --
    
    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
    
    45
    +-- This function blocks until the TH computation returns a value, but spawns in
    
    46
    +-- the background a thread to handle the 'THMessage's until the TH computation
    
    47
    +-- is fully done.
    
    48
    +sendTHComputation
    
    49
    +  :: Binary a
    
    50
    +  => ExtInterpInstance d
    
    51
    +  -> Message a
    
    52
    +  -> (forall b. Binary b => THMessage b -> IO (Bool, b))
    
    53
    +  -- ^ Handle one message. Return True to end the loop.
    
    54
    +  -- This handler must never uninterrutibly mask an expression which may get
    
    55
    +  -- stuck. If it does, then 'sendTHComputation' will also get uninterruptibly stuck.
    
    56
    +  -> IO a
    
    57
    +sendTHComputation i msg reqHandler = mask $ \restore -> do
    
    58
    +  -- inline "withAsync/wait"
    
    59
    +  var <- newEmptyTMVarIO
    
    60
    +  ht  <- forkFinally (restore handlerLoop) (atomically . putTMVar var)
    
    61
    +  restore (sendMessage i msg <* atomically (readTMVar var))
    
    62
    +    `onException` uninterruptibleMask_ (throwTo ht CancelTHHandler)
    
    63
    +  where
    
    64
    +    proc = instProcess i
    
    65
    +    handleOne doneRef = do
    
    66
    +      handleRemoteTHCall (interpPipe proc) $ \req -> do
    
    67
    +        (stop, res) <- reqHandler req
    
    68
    +        writeIORef doneRef stop
    
    69
    +        return res
    
    70
    +    handlerLoop = do
    
    71
    +      doneRef <- newIORef False
    
    72
    +      let loop = do
    
    73
    +            handleOne doneRef
    
    74
    +            isDone <- readIORef doneRef
    
    75
    +            if isDone then return ()
    
    76
    +                      else loop
    
    77
    +       in loop
    
    121 78
     
    
    122 79
     handleInterpProcessFailure :: InterpProcess -> SomeException -> IO a
    
    123 80
     handleInterpProcessFailure i e = do
    
    ... ... @@ -130,3 +87,8 @@ handleInterpProcessFailure i e = do
    130 87
           terminateProcess hdl
    
    131 88
           _ <- waitForProcess hdl
    
    132 89
           throw e
    
    90
    +
    
    91
    +data CancelTHHandler = CancelTHHandler deriving Show
    
    92
    +instance Exception CancelTHHandler where
    
    93
    +  fromException = asyncExceptionFromException
    
    94
    +  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,25 @@ 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, closePipe
    
    28
    +  , remoteCall, remoteTHCall
    
    29
    +  , handleRemoteCall, handleRemoteTHCall
    
    30
    +  , RemoteProcessException(..)
    
    31
    +
    
    26 32
       , BreakModule
    
    27 33
       , BreakUnitId
    
    28 34
       , LoadedDLL
    
    35
    +
    
    36
    +  -- * Low-level interface for async bidirectional RPC
    
    37
    +  , remoteAsyncReq
    
    38
    +  , handleAsyncReq
    
    39
    +  -- ** Consume `reply` exactly once
    
    40
    +  , WriteReply -- abstract
    
    41
    +  , writeReply
    
    42
    +  -- ** Async matching internals
    
    43
    +  , AsyncManager(..), CorrelationId
    
    44
    +  , IddMsg(..), newAsyncManager
    
    29 45
       ) where
    
    30 46
     
    
    31 47
     import Prelude -- See note [Why do we import Prelude here?]
    
    ... ... @@ -45,6 +61,7 @@ import qualified GHC.Exts.Heap as Heap
    45 61
     import GHC.ForeignSrcLang
    
    46 62
     import GHC.Fingerprint
    
    47 63
     import GHC.Conc (pseq, par)
    
    64
    +import GHC.IO (IO(..))
    
    48 65
     import Control.Concurrent
    
    49 66
     import Control.DeepSeq
    
    50 67
     import Control.Exception
    
    ... ... @@ -63,6 +80,8 @@ import Data.Dynamic
    63 80
     import Data.Typeable (TypeRep)
    
    64 81
     import Data.IORef
    
    65 82
     import Data.Map (Map)
    
    83
    +import Data.IntMap (IntMap)
    
    84
    +import qualified Data.IntMap as IntMap
    
    66 85
     import Foreign
    
    67 86
     import GHC.Generics
    
    68 87
     import GHC.Stack.CCS
    
    ... ... @@ -71,6 +90,11 @@ import qualified GHC.Boot.TH.Monad as TH
    71 90
     import System.Exit
    
    72 91
     import System.IO
    
    73 92
     import System.IO.Error
    
    93
    +import Control.Monad
    
    94
    +import Control.Concurrent.STM
    
    95
    +import Data.Bifunctor
    
    96
    +import GHC.Base (State#)
    
    97
    +import GHC.Exts (RealWorld)
    
    74 98
     
    
    75 99
     -- -----------------------------------------------------------------------------
    
    76 100
     -- The RPC protocol between GHC and the interactive server
    
    ... ... @@ -693,50 +717,298 @@ serializeBCOs rbcos = parMap doChunk (chunkList 100 rbcos)
    693 717
     
    
    694 718
     -- | An opaque pipe for bidirectional binary data transmission.
    
    695 719
     data Pipe = Pipe
    
    696
    -  { getSome :: !(IO ByteString)
    
    697
    -  , putAll :: !(B.Builder -> IO ())
    
    698
    -  , pipeLeftovers :: !(IORef (Maybe ByteString))
    
    720
    +  { getSome         :: !(IO ByteString)
    
    721
    +  , putAll          :: !(B.Builder -> IO ())
    
    722
    +  , pipeLeftovers   :: !(IORef (Maybe ByteString))
    
    723
    +  , pipeLock        :: !(MVar ()) -- ^ Lock to prevent concurrent writes to the stream
    
    724
    +  , pipeAsyncMngr   :: !AsyncManager
    
    725
    +  , pipeCleanupRW   :: !(IO ())
    
    726
    +    -- ^ Clean up the read/write resources, e.g. closes the handles if 'mkPipeFromHandles'
    
    727
    +  , pipeIsClosed    :: !(TVar Bool)
    
    699 728
       }
    
    700 729
     
    
    701 730
     -- | Make a 'Pipe' from a 'Handle' to read and a 'Handle' to write.
    
    731
    +-- Must call 'closePipe' at the end to clean up and close the handles.
    
    702 732
     mkPipeFromHandles :: Handle -> Handle -> IO Pipe
    
    703 733
     mkPipeFromHandles pipeRead pipeWrite = do
    
    704 734
       let getSome = B.hGetSome pipeRead (32*1024)
    
    705 735
           putAll b = do
    
    706 736
             B.hPutBuilder pipeWrite b
    
    707 737
             hFlush pipeWrite
    
    708
    -  pipeLeftovers <- newIORef Nothing
    
    709
    -  pure $ Pipe { getSome, putAll, pipeLeftovers }
    
    738
    +  mkPipeFromContinuations getSome putAll (hClose pipeRead >> hClose pipeWrite)
    
    739
    +
    
    740
    +-- | Make a 'Pipe' from a reader function and a writer function, plus an action
    
    741
    +-- to clean up the reader and writer resources. The pipe should be closed with
    
    742
    +-- 'closePipe', which will call this clean up action too.
    
    743
    +mkPipeFromContinuations :: IO ByteString -> (B.Builder -> IO ()) -> IO () -> IO Pipe
    
    744
    +mkPipeFromContinuations getSome putAll pipeCleanupRW = mdo
    
    745
    +  pipeLeftovers   <- newIORef Nothing
    
    746
    +  pipeLock        <- newMVar ()
    
    747
    +  pipeIsClosed    <- newTVarIO False
    
    748
    +  mdo
    
    749
    +    pipeAsyncMngr <- newAsyncManager p
    
    750
    +    let p = Pipe { .. }
    
    751
    +    pure p
    
    752
    +
    
    753
    +-- | Cleanly close our end of the Pipe, terminating the async manager's
    
    754
    +-- drain-read-end thread. (This will close the handles if created from
    
    755
    +-- 'mkPipeFromHandles').
    
    756
    +--
    
    757
    +-- The remote process does not necessarily detect that this end of the pipe was
    
    758
    +-- closed, but it must handle cleanly e.g. a handle being closed (perhaps by
    
    759
    +-- closing the pipe on its end too)
    
    760
    +--
    
    761
    +-- The requests we've sent that are pending are all canceled with
    
    762
    +-- 'PipeClosedException' by ourselves.
    
    763
    +closePipe :: Pipe -> IO ()
    
    764
    +closePipe Pipe{..} = do
    
    765
    +
    
    766
    +  -- releases handle lock taken by reading, and cleans up by replying to every
    
    767
    +  -- pending request with 'PipeClosedException'.
    
    768
    +  throwTo (asyncDrainTid pipeAsyncMngr) PipeClosedException
    
    710 769
     
    
    711
    --- | Make a 'Pipe' from a reader function and a writer function.
    
    712
    -mkPipeFromContinuations :: IO ByteString -> (B.Builder -> IO ()) -> IO Pipe
    
    713
    -mkPipeFromContinuations getSome putAll = do
    
    714
    -  pipeLeftovers <- newIORef Nothing
    
    715
    -  pure $ Pipe { getSome, putAll, pipeLeftovers }
    
    770
    +  pipeCleanupRW -- hClose won't deadlock now that the drainPipe thread is killed
    
    716 771
     
    
    772
    +-- | Send a 'Message' request to and wait for a reply from the interpreter
    
    773
    +-- (blocks on the calling side, doesn't necessarily block the interpreter)
    
    717 774
     remoteCall :: Binary a => Pipe -> Message a -> IO a
    
    718
    -remoteCall pipe msg = do
    
    719
    -  writePipe pipe (putMessage msg)
    
    720
    -  readPipe pipe get
    
    775
    +remoteCall pipe msg = either throwIO (pure . runGet get)
    
    776
    +                  =<< remoteAsyncReq pipe (putMessage msg)
    
    777
    +
    
    778
    +-- | Handle a single 'Message' request on the interpreter and reply to the host
    
    779
    +handleRemoteCall :: Pipe
    
    780
    +                 -> (Msg -> IO Msg) {-^ Msg hook -}
    
    781
    +                 -> (forall a. (Show a, Binary a) => Message a -> IO a)
    
    782
    +                 -> IO ()
    
    783
    +handleRemoteCall pipe hook k =
    
    784
    +  handleAsyncReq pipe getMessage \writer msg0 -> IO \s0 ->
    
    785
    +    case hook msg0 |#> s0 of -- unIO for linearity checking
    
    786
    +      (# s1, Msg msg #) -> case k msg |#> s1 of
    
    787
    +        (# s2, a #) -> writeReply writer (put a) |#> s2
    
    788
    +
    
    789
    +-- | Send a 'THMessage' request to the host and wait for a reply from the host
    
    790
    +-- to the interpreter
    
    791
    +remoteTHCall :: Binary a => Pipe -> THMessage a -> IO a
    
    792
    +remoteTHCall pipe msg = either throwIO (pure . runGet get)
    
    793
    +                    =<< remoteAsyncReq pipe (putTHMessage msg)
    
    794
    +
    
    795
    +-- | Handle a 'THMessage' request on the host and reply to the interpreter.
    
    796
    +handleRemoteTHCall
    
    797
    +  :: Pipe -> (forall a. (Show a, Binary a) => THMessage a -> IO a) -> IO ()
    
    798
    +handleRemoteTHCall pipe k =
    
    799
    +  handleAsyncReq pipe getTHMessage \writer (THMsg msg) -> IO \s0 ->
    
    800
    +    case k msg |#> s0 of -- unIO for linearity checking
    
    801
    +      (# s1, a #) -> writeReply writer (put a) |#> s1
    
    802
    +
    
    803
    +--------------------------------------------------------------------------------
    
    804
    +-- * Matching requests with async replies
    
    805
    +--------------------------------------------------------------------------------
    
    806
    +
    
    807
    +-- | Send an asynchronous request and block waiting for a matching reply
    
    808
    +remoteAsyncReq :: Pipe -> Put -> IO (Either SomeException LB.ByteString)
    
    809
    +remoteAsyncReq pipe msg = do
    
    810
    +
    
    811
    +  let pendingReplies = pendingReply (pipeAsyncMngr pipe)
    
    812
    +
    
    813
    +  isClosed <- readTVarIO (pipeIsClosed pipe)
    
    814
    +  when isClosed $
    
    815
    +    throwIO PipeClosedException
    
    816
    +
    
    817
    +  -- Mark pending request
    
    818
    +  uq <- freshReqId (pipeAsyncMngr pipe)
    
    819
    +  wv <- newEmptyMVar
    
    820
    +  atomicModifyIORef' pendingReplies $
    
    821
    +    \m -> (IntMap.insert uq wv m, ())
    
    822
    +
    
    823
    +  -- Write request
    
    824
    +  writePipe pipe (Request uq (runPut msg))
    
    825
    +    `onException`
    
    826
    +      -- the req wasn't done, or no one will be reading the reply:
    
    827
    +      -- in either case, drop it from the pending map.
    
    828
    +      atomicModifyIORef' pendingReplies
    
    829
    +        (\m -> (IntMap.delete uq m, ()))
    
    830
    +
    
    831
    +  -- Block waiting for reply
    
    832
    +  takeMVar wv
    
    833
    +
    
    834
    +-- | Receive a single asynchronous request and send a matching reply
    
    835
    +-- asynchronously (the handler may fork a computation that eventually replies
    
    836
    +-- and can return before actually replying, as long as it eventually does).
    
    837
    +--
    
    838
    +-- The handler receives as an argument the function to use to reply to the request.
    
    839
    +-- It must be used exactly once (all requests expect a reply, and you can't
    
    840
    +-- reply more than once). Linear types enforce the handler replies in every
    
    841
    +-- branch, and if the handler otherwise throws, it is handled by 'withAsyncHandler'.
    
    842
    +handleAsyncReq :: Pipe -> Get a -> (WriteReply Put %1 -> a -> IO r) -> IO r
    
    843
    +handleAsyncReq pipe getit k = do
    
    844
    +  ack <- newEmptyMVar
    
    845
    +
    
    846
    +  isOver <- atomically $ do
    
    847
    +    isClosed <- readTVar (pipeIsClosed pipe)
    
    848
    +    isEmpty  <- isEmptyTQueue (requestsQueue (pipeAsyncMngr pipe))
    
    849
    +    return (isClosed && isEmpty)
    
    850
    +
    
    851
    +  when isOver $ do
    
    852
    +    throwIO PipeClosedException
    
    853
    +
    
    854
    +  -- Block waiting for request
    
    855
    +  (uq, bs) <- atomically $ readTQueue (requestsQueue (pipeAsyncMngr pipe))
    
    856
    +
    
    857
    +  let reply p = do
    
    858
    +        writePipe pipe (Reply uq (bimap toSerializableException runPut p))
    
    859
    +        putMVar ack ()
    
    860
    +
    
    861
    +  -- Run the continuation which must reply exactly once with 'writeReply'
    
    862
    +  case runGetOrFail getit bs of
    
    863
    +    Left (_, _, err) -> do
    
    864
    +      let exc = DecodeAsyncReqException err
    
    865
    +      reply (Left (toException exc))
    
    866
    +      throwIO exc
    
    867
    +    Right (_, _, it) ->
    
    868
    +      k (WriteReply reply) it
    
    869
    +
    
    870
    +        -- If there's an exception before the reply is sent, reply with an error instead.
    
    871
    +        `catch` \(e :: SomeException) -> do
    
    872
    +
    
    873
    +            tryTakeMVar ack >>= \case
    
    874
    +              Just () ->
    
    875
    +                -- Already replied
    
    876
    +                throwIO e
    
    877
    +              Nothing -> do
    
    878
    +                -- Reply with an error
    
    879
    +                reply (Left e)
    
    880
    +                throwIO e
    
    881
    +
    
    882
    +-- | An exception that can be thrown on the host process representing an
    
    883
    +-- expection that happened on the remote one.
    
    884
    +newtype RemoteProcessException = RemoteProcessException SerializableException deriving Show
    
    885
    +instance Exception RemoteProcessException
    
    886
    +
    
    887
    +-- | 'handleSyncReq' failed to decode an incoming request payload using the given @'Get' a@
    
    888
    +newtype DecodeAsyncReqException = DecodeAsyncReqException String deriving Show
    
    889
    +instance Exception DecodeAsyncReqException
    
    890
    +
    
    891
    +-- | We've closed the pipe on our end and we terminate all pending requests with this.
    
    892
    +data PipeClosedException = PipeClosedException deriving Show
    
    893
    +instance Exception PipeClosedException
    
    894
    +
    
    895
    +-- ** Async matching "internals" -----------------------------------------------
    
    896
    +
    
    897
    +data AsyncManager = AsyncManager
    
    898
    +  { pendingReply :: !(IORef (IntMap (MVar (Either SomeException LB.ByteString))))
    
    899
    +    -- ^ When a request is sent, keep track that we're expecting a reply. The
    
    900
    +    -- MVar is signaled when the reply arrives. The exception can originate
    
    901
    +    -- from the host process, or from the remote one (with 'RemoteProcessException').
    
    902
    +  , requestsQueue :: !(TQueue (CorrelationId, LB.ByteString))
    
    903
    +    -- ^ A queue of pending received requests. When a request is received,
    
    904
    +    -- queue it here. A request handler will pop the next request from it.
    
    905
    +    -- (Nothing except the 'drainPipe' thread should read from the pipe).
    
    906
    +  , asyncDrainTid :: !ThreadId
    
    907
    +  , asyncNextReq  :: !(IORef Int)
    
    908
    +  }
    
    721 909
     
    
    722
    -writePipe :: Pipe -> Put -> IO ()
    
    723
    -writePipe Pipe{..} put = putAll $ execPut put
    
    910
    +type CorrelationId = Int
    
    724 911
     
    
    725
    -remoteTHCall :: Binary a => Pipe -> THMessage a -> IO a
    
    726
    -remoteTHCall pipe msg = do
    
    727
    -  writePipe pipe (putTHMessage msg)
    
    728
    -  readPipe pipe get
    
    912
    +-- | We have a single type for requests and replies because both the host and
    
    913
    +-- interpreter may send requests *and* replies on the write end *and* on recv
    
    914
    +-- both on the read end.
    
    915
    +--
    
    916
    +-- For instance:
    
    917
    +--  1. The host sends a RunTH request on its Host Write End (HWE)
    
    918
    +--  2. The interpreter sends a NewName TH request on its Interpreter Write End (IWE)
    
    919
    +--  3. The host sends a reply to the TH request on its HWE
    
    920
    +--  4. The interpreter sends the reply to the RunTH request on its IWE
    
    921
    +data IddMsg  = Request !CorrelationId LB.ByteString
    
    922
    +             | Reply   !CorrelationId (Either SerializableException LB.ByteString)
    
    923
    +
    
    924
    +instance Binary IddMsg where
    
    925
    +  put (Request i bs) = putWord8 0 >> put i >> put bs
    
    926
    +  put (Reply i bs)   = putWord8 1 >> put i >> put bs
    
    927
    +  get = do
    
    928
    +    b <- getWord8
    
    929
    +    case b of
    
    930
    +      0 -> Request <$> get <*> get
    
    931
    +      1 -> Reply   <$> get <*> get
    
    932
    +      _ -> error $ "impossible: Binary IddMsg - " ++ show b
    
    933
    +
    
    934
    +newAsyncManager :: Pipe -> IO AsyncManager
    
    935
    +newAsyncManager pipe = do
    
    936
    +  asyncNextReq <- newIORef 0
    
    937
    +  pendingReply  <- newIORef IntMap.empty
    
    938
    +  requestsQueue  <- newTQueueIO
    
    939
    +
    
    940
    +  -- Fork thread to drain read end.
    
    941
    +  -- (cleaned up on 'closePipe')
    
    942
    +  asyncDrainTid  <- forkIO (drainPipe pipe)
    
    729 943
     
    
    730
    -readPipe :: Pipe -> Get a -> IO a
    
    731
    -readPipe Pipe{..} get = do
    
    944
    +  let am = AsyncManager{..}
    
    945
    +  pure am
    
    946
    +
    
    947
    +-- | Drains the read end of the Pipe and match 'Reply'es against pending 'Request's.
    
    948
    +--
    
    949
    +-- In pratice, we'll have both a thread draining the read end of the host's
    
    950
    +-- 'Pipe' and a thread draining the read end of the interpreter's 'Pipe', since
    
    951
    +-- both processes construct a 'Pipe' and send and receive messages
    
    952
    +drainPipe :: Pipe -> IO ()
    
    953
    +drainPipe p = catch loop cancelPending where
    
    954
    +  loop = do
    
    955
    +    readPipe p >>= \case
    
    956
    +      -- Receive a reply to a pending notification
    
    957
    +      Just (Reply uq bs) -> do
    
    958
    +        mwv <- atomicModifyIORef' (pendingReply am) $ \m -> do
    
    959
    +          case IntMap.lookup uq m of
    
    960
    +            Nothing -> (m, Nothing)
    
    961
    +            Just wv -> (IntMap.delete uq m, Just wv)
    
    962
    +        case mwv of
    
    963
    +          Just wv ->
    
    964
    +            putMVar wv (bimap (toException . RemoteProcessException) id bs) -- signal matching request
    
    965
    +          Nothing ->
    
    966
    +            -- the req was dropped from pending bc there was an exception, and no
    
    967
    +            -- one's listening any more. the reply is ignored
    
    968
    +            pure ()
    
    969
    +        loop
    
    970
    +      -- Receive a request and queue it
    
    971
    +      Just (Request uq bs) -> do
    
    972
    +        atomically $ writeTQueue (requestsQueue am) (uq,bs)
    
    973
    +        loop
    
    974
    +      -- EOF on read handle
    
    975
    +      Nothing -> do
    
    976
    +        atomically $ writeTVar (pipeIsClosed p) True
    
    977
    +        pure () -- stops thread
    
    978
    +
    
    979
    +  cancelPending (e::SomeException) = do
    
    980
    +    pendings <- readIORef (pendingReply am)
    
    981
    +    mapM_ (\p -> putMVar p (Left e {-host exception-})) pendings
    
    982
    +
    
    983
    +  am = pipeAsyncMngr p
    
    984
    +
    
    985
    +freshReqId :: AsyncManager -> IO CorrelationId
    
    986
    +freshReqId AsyncManager{asyncNextReq} =
    
    987
    +  atomicModifyIORef' asyncNextReq $ \i -> (i+1, i)
    
    988
    +
    
    989
    +--------------------------------------------------------------------------------
    
    990
    +-- * Pipe thread-safe read/write
    
    991
    +--------------------------------------------------------------------------------
    
    992
    +-- All messages on the wire are 'IddMsg', which contain a correlation header
    
    993
    +-- and identify whether they are requests or replies.
    
    994
    +
    
    995
    +writePipe :: Pipe -> IddMsg -> IO ()
    
    996
    +writePipe p@Pipe{..} = withLock p . putAll . execPut . put
    
    997
    +
    
    998
    +-- | Only the 'drainPipe' thread should read from the pipe directly. Other
    
    999
    +-- functions should wait for the matching reply to be delivered asynchronously.
    
    1000
    +-- (see 'withAsyncReq'). It doesn't lock to read bc it assumes single reader is 'drainPipe'.
    
    1001
    +--
    
    1002
    +-- Returns @Nothing@ on EOF.
    
    1003
    +readPipe :: Pipe -> IO (Maybe IddMsg)
    
    1004
    +readPipe Pipe{..} = do
    
    732 1005
       leftovers <- readIORef pipeLeftovers
    
    733 1006
       m <- getBin getSome get leftovers
    
    734 1007
       case m of
    
    735
    -    Nothing -> throw $
    
    736
    -      mkIOError eofErrorType "GHCi.Message.readPipe" Nothing Nothing
    
    1008
    +    Nothing -> return Nothing
    
    737 1009
         Just (result, new_leftovers) -> do
    
    738 1010
           writeIORef pipeLeftovers new_leftovers
    
    739
    -      return result
    
    1011
    +      return (Just result)
    
    740 1012
     
    
    741 1013
     getBin
    
    742 1014
       :: (IO ByteString) -> Get a -> Maybe ByteString
    
    ... ... @@ -758,3 +1030,22 @@ getBin getsome get leftover = go leftover (runGetIncremental get)
    758 1030
             else go Nothing (fun (Just b))
    
    759 1031
        go _lft (Fail _rest _off str) =
    
    760 1032
          throwIO (ErrorCall ("getBin: " ++ str))
    
    1033
    +
    
    1034
    +withLock :: Pipe -> IO c -> IO c
    
    1035
    +withLock Pipe{..} = bracket (takeMVar pipeLock) (putMVar pipeLock) . const
    
    1036
    +
    
    1037
    +--------------------------------------------------------------------------------
    
    1038
    +-- * Consume `reply` exactly once
    
    1039
    +--------------------------------------------------------------------------------
    
    1040
    +
    
    1041
    +-- | Every request should get exactly one reply.
    
    1042
    +data WriteReply a where
    
    1043
    +  WriteReply :: (Either SomeException a -> IO ()) %1 -> WriteReply a
    
    1044
    +
    
    1045
    +-- | Write a reply exactly once (consuming the ability to write more replies)
    
    1046
    +writeReply :: WriteReply a %1 -> a -> IO ()
    
    1047
    +writeReply (WriteReply r) = \x -> r (Right x)
    
    1048
    +
    
    1049
    +-- | Infix, linear, 'unIO'
    
    1050
    +(|#>) :: IO a %1 -> State# RealWorld -> (# State# RealWorld, a #)
    
    1051
    +(|#>) (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 closePipe
    
    83
    +              else loop doneRef
    
    84 84
     
    
    85 85
       handleCustom tag payload = do
    
    86 86
         mresp <- customHandler tag payload
    
    ... ... @@ -89,17 +89,22 @@ 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
    
    103 108
         case r of
    
    104 109
           Left e
    
    105 110
             | Just (GHCiQException _ err) <- fromException e  -> do
    
    ... ... @@ -179,7 +184,7 @@ defaultServerWithCustom customHandler = do
    179 184
     #if defined(wasm32_HOST_ARCH)
    
    180 185
       -- See Note [wasm ghci signal handlers] for details
    
    181 186
       installSignalHandlers $ js_register_signal_handler cb_sig
    
    182
    -  pipe <- mkPipeFromContinuations (recv_buf cb_recv) (send_buf cb_send)
    
    187
    +  pipe <- mkPipeFromContinuations (recv_buf cb_recv) (send_buf cb_send) (pure ())
    
    183 188
     #else
    
    184 189
       when verbose $
    
    185 190
         printf "GHC iserv starting (in: %s; out: %s)\n" (show inh) (show outh)
    

  • 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
    ... ... @@ -12,6 +12,7 @@ import GHCi.Message
    12 12
       , mkPipeFromHandles
    
    13 13
       , remoteCall
    
    14 14
       , Pipe
    
    15
    +  , closePipe
    
    15 16
       )
    
    16 17
     import GHCi.Server
    
    17 18
       ( CustomMessageHandler
    
    ... ... @@ -153,14 +154,13 @@ customMessage pipe c = do
    153 154
     runClient :: Int -> IO ()
    
    154 155
     runClient input = do
    
    155 156
       serverExe <- getExecutablePath
    
    156
    -  withServer serverExe $ \hFromServer hToServer -> do
    
    157
    -    pipe <- mkPipeFromHandles hFromServer hToServer
    
    157
    +  withServer serverExe $ \pipe -> do
    
    158 158
         res  <- customMessage pipe (SquareCommand input)
    
    159 159
         putStrLn $ "Square returned: " ++ show res
    
    160 160
         res2  <- customMessage pipe (MulCommand 2 res)
    
    161 161
         putStrLn $ "Mul returned: " ++ show res2
    
    162 162
     
    
    163
    -withServer :: FilePath -> (Handle -> Handle -> IO a) -> IO a
    
    163
    +withServer :: FilePath -> (Pipe -> IO a) -> IO a
    
    164 164
     withServer serverExe action = do
    
    165 165
       (ghcRead, serverWrite) <- createPipe
    
    166 166
       (serverRead, ghcWrite) <- createPipe
    
    ... ... @@ -171,13 +171,12 @@ withServer serverExe action = do
    171 171
         , std_out = Inherit
    
    172 172
         , std_err = Inherit
    
    173 173
         }
    
    174
    -  bracket (mkHandles ghcRead ghcWrite)
    
    175
    -          (\(hFromServer, hToServer) -> do
    
    176
    -             hClose hFromServer
    
    177
    -             hClose hToServer
    
    174
    +  bracket (mkHandles ghcRead ghcWrite >>= uncurry mkPipeFromHandles)
    
    175
    +          (\pipe -> do
    
    176
    +             closePipe pipe
    
    178 177
                  terminateProcess ph
    
    179 178
                  void (waitForProcess ph))
    
    180
    -          (\(hFromServer, hToServer) -> action hFromServer hToServer)
    
    179
    +          (\pipe -> action pipe)
    
    181 180
       where
    
    182 181
         mkHandles r w = do
    
    183 182
           hR <- fdToHandle r