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

Commits:

3 changed files:

Changes:

  • libraries/ghci/GHCi/Message.hs
    ... ... @@ -69,35 +69,22 @@ import GHC.IO (IO(..))
    69 69
     import Control.Concurrent
    
    70 70
     import Control.DeepSeq
    
    71 71
     import Control.Exception
    
    72
    -#if MIN_VERSION_base(4,20,0)
    
    73
    -import Control.Exception.Context
    
    74
    -#endif
    
    75 72
     import Data.Binary
    
    76 73
     import Data.Binary.Get
    
    77 74
     import Data.Binary.Put
    
    78 75
     import Data.ByteString (ByteString)
    
    79
    -import qualified Data.ByteString as B
    
    80
    -import qualified Data.ByteString.Builder as B
    
    81 76
     import qualified Data.ByteString.Lazy as LB
    
    82 77
     import qualified Data.ByteString.Short as BS
    
    83 78
     import Data.Dynamic
    
    84 79
     import Data.Typeable (TypeRep)
    
    85 80
     import Data.IORef
    
    86 81
     import Data.Map (Map)
    
    87
    -import Data.IntMap (IntMap)
    
    88
    -import qualified Data.IntMap as IntMap
    
    89 82
     import Foreign
    
    90 83
     import GHC.Generics
    
    91 84
     import GHC.Stack.CCS
    
    92 85
     import qualified GHC.Boot.TH.Syntax        as TH
    
    93 86
     import qualified GHC.Boot.TH.Monad         as TH
    
    94
    -import System.Exit
    
    95
    -import System.IO
    
    96
    -import Control.Monad
    
    97
    -import Control.Concurrent.STM
    
    98
    -import Data.Bifunctor
    
    99
    -import GHC.Base (State#)
    
    100
    -import GHC.Exts (RealWorld)
    
    87
    +import GHCi.Message.Async
    
    101 88
     
    
    102 89
     -- -----------------------------------------------------------------------------
    
    103 90
     -- The RPC protocol between GHC and the interactive server
    
    ... ... @@ -487,45 +474,6 @@ data BreakUnitId
    487 474
     -- | A dummy type that tags pointers returned by 'LoadDLLs'.
    
    488 475
     data LoadedDLL
    
    489 476
     
    
    490
    --- SomeException can't be serialized because it contains dynamic
    
    491
    --- types.  However, we do very limited things with the exceptions that
    
    492
    --- are thrown by interpreted computations:
    
    493
    ---
    
    494
    --- * We print them, e.g. "*** Exception: <something>"
    
    495
    --- * UserInterrupt has a special meaning
    
    496
    --- * In ghc -e, exitWith should exit with the appropriate exit code
    
    497
    ---
    
    498
    --- So all we need to do is distinguish UserInterrupt and ExitCode, and
    
    499
    --- all other exceptions can be represented by their 'show' string.
    
    500
    ---
    
    501
    -data SerializableException
    
    502
    -  = EUserInterrupt
    
    503
    -  | EExitCode ExitCode
    
    504
    -  | EOtherException String
    
    505
    -  deriving (Generic, Show)
    
    506
    -
    
    507
    -toSerializableException :: SomeException -> SerializableException
    
    508
    -toSerializableException ex
    
    509
    -  | Just UserInterrupt <- fromException ex  = EUserInterrupt
    
    510
    -  | Just (ec::ExitCode) <- fromException ex = (EExitCode ec)
    
    511
    -  | otherwise = EOtherException $
    
    512
    -#if MIN_VERSION_base(4,20,0)
    
    513
    -      -- Exception plus backtrace as seen in `displayExceptionWithInfo`
    
    514
    -      case displayExceptionContext (someExceptionContext ex) of
    
    515
    -        "" -> displayException (ex :: SomeException)
    
    516
    -        cx -> displayException (ex :: SomeException) ++ "\n\n" ++ cx
    
    517
    -#else
    
    518
    -      show (ex :: SomeException)
    
    519
    -#endif
    
    520
    -
    
    521
    -fromSerializableException :: SerializableException -> SomeException
    
    522
    -fromSerializableException EUserInterrupt = toException UserInterrupt
    
    523
    -fromSerializableException (EExitCode c) = toException c
    
    524
    -fromSerializableException (EOtherException str) = toException (ErrorCall str)
    
    525
    -
    
    526
    -instance Binary ExitCode
    
    527
    -instance Binary SerializableException
    
    528
    -
    
    529 477
     data THResult a
    
    530 478
       = THException String
    
    531 479
       | THComplete a
    
    ... ... @@ -719,65 +667,6 @@ serializeBCOs rbcos = parMap doChunk (chunkList 100 rbcos)
    719 667
       chunkList _ [] = []
    
    720 668
       chunkList n xs = as : chunkList n bs where (as,bs) = splitAt n xs
    
    721 669
     
    
    722
    --- -----------------------------------------------------------------------------
    
    723
    --- Reading/writing messages
    
    724
    -
    
    725
    --- | An opaque pipe for bidirectional binary data transmission.
    
    726
    -data Pipe = Pipe
    
    727
    -  { getSome         :: !(IO ByteString)
    
    728
    -  , putAll          :: !(B.Builder -> IO ())
    
    729
    -  , pipeLeftovers   :: !(IORef (Maybe ByteString))
    
    730
    -  , pipeLock        :: !(MVar ()) -- ^ Lock to prevent concurrent writes to the stream
    
    731
    -  , pipeAsyncMngr   :: !AsyncManager
    
    732
    -  , pipeIsClosed    :: !(TVar Bool)
    
    733
    -    -- ^ After the pipe is closed, requests can no longer be received nor sent
    
    734
    -  }
    
    735
    -
    
    736
    --- | Make a 'Pipe' from a 'Handle' to read and a 'Handle' to write.
    
    737
    --- Must call 'closePipe' at the end to clean up.
    
    738
    -mkPipeFromHandles :: Handle -> Handle -> IO Pipe
    
    739
    -mkPipeFromHandles pipeRead pipeWrite = do
    
    740
    -  let getSome = B.hGetSome pipeRead (32*1024)
    
    741
    -      putAll b = do
    
    742
    -        B.hPutBuilder pipeWrite b
    
    743
    -        hFlush pipeWrite
    
    744
    -  mkPipeFromContinuations getSome putAll
    
    745
    -
    
    746
    --- | Make a 'Pipe' from a reader function and a writer function.
    
    747
    --- The pipe should be closed with 'closePipe'.
    
    748
    -mkPipeFromContinuations :: IO ByteString -> (B.Builder -> IO ()) -> IO Pipe
    
    749
    -mkPipeFromContinuations getSome putAll = mdo
    
    750
    -  pipeLeftovers   <- newIORef Nothing
    
    751
    -  pipeLock        <- newMVar ()
    
    752
    -  pipeIsClosed    <- newTVarIO False
    
    753
    -  mdo
    
    754
    -    pipeAsyncMngr <- newAsyncManager p
    
    755
    -    let p = Pipe { .. }
    
    756
    -    pure p
    
    757
    -
    
    758
    --- | Cleanly close our end of the Pipe, terminating the async manager's
    
    759
    --- drain-read-end thread. Calling 'closePipe' on an already closed pipe is a no-op.
    
    760
    ---
    
    761
    --- If the pipe was created using 'mkPipeFromHandles', the handles must only by
    
    762
    --- closed after calling 'closePipe'.
    
    763
    ---
    
    764
    --- The requests we've sent that are pending are all canceled with
    
    765
    --- 'PipeClosedException' by ourselves.
    
    766
    ---
    
    767
    --- The remote process does not necessarily detect that this end of the pipe was
    
    768
    --- closed, but it must handle cleanly and clean up after exceptions caused from
    
    769
    --- it such as a handle being closed.
    
    770
    -closePipe :: Pipe -> IO ()
    
    771
    -closePipe Pipe{..} = do
    
    772
    -
    
    773
    -  isAlreadyClosed <- atomically $ swapTVar pipeIsClosed True
    
    774
    -
    
    775
    -  unless isAlreadyClosed $ do
    
    776
    -
    
    777
    -    -- releases handle lock taken by reading, and cleans up by replying to every
    
    778
    -    -- pending request with 'PipeClosedException'.
    
    779
    -    throwTo (asyncDrainTid pipeAsyncMngr) PipeClosedException
    
    780
    -
    
    781 670
     -- | Send a 'Message' request to and wait for a reply from the interpreter
    
    782 671
     -- (blocks on the calling side, doesn't necessarily block the interpreter)
    
    783 672
     remoteCall :: Binary a => Pipe -> Message a -> IO a
    
    ... ... @@ -815,314 +704,3 @@ handleRemoteTHCall pipe ii k =
    815 704
         case k msg |#> s0 of -- unIO for linearity checking
    
    816 705
           (# s1, a #) -> writeReply writer (put a) |#> s1
    
    817 706
     
    818
    ---------------------------------------------------------------------------------
    
    819
    --- * Matching requests with async replies
    
    820
    ---------------------------------------------------------------------------------
    
    821
    -
    
    822
    --- | Send an asynchronous request and block waiting for a matching reply
    
    823
    -remoteAsyncReq :: Pipe -> Put -> (Maybe InboxId) -> IO (Either SomeException LB.ByteString)
    
    824
    -remoteAsyncReq pipe msg ii = do
    
    825
    -
    
    826
    -  let pendingReplies = pendingReply (pipeAsyncMngr pipe)
    
    827
    -
    
    828
    -  -- Mark pending request
    
    829
    -  uq <- freshId (pipeAsyncMngr pipe)
    
    830
    -  wv <- newEmptyTMVarIO
    
    831
    -
    
    832
    -  isClosed <- atomically $ do
    
    833
    -    isClosed <- readTVar (pipeIsClosed pipe)
    
    834
    -    if isClosed
    
    835
    -      then pure True
    
    836
    -      else do
    
    837
    -        modifyTVar pendingReplies (IntMap.insert uq wv)
    
    838
    -        pure False
    
    839
    -
    
    840
    -  when isClosed $ do
    
    841
    -    throwIO PipeClosedException
    
    842
    -
    
    843
    -  -- Write request
    
    844
    -  writePipe pipe (Request uq ii (runPut msg))
    
    845
    -    `onException`
    
    846
    -      -- the req wasn't done, or no one will be reading the reply:
    
    847
    -      -- in either case, drop it from the pending map.
    
    848
    -      atomically (modifyTVar pendingReplies (IntMap.delete uq))
    
    849
    -
    
    850
    -  -- Block waiting for reply
    
    851
    -  atomically (takeTMVar wv)
    
    852
    -    `onException`
    
    853
    -      atomically (modifyTVar pendingReplies (IntMap.delete uq))
    
    854
    -
    
    855
    --- | Receive a single asynchronous request and send a matching reply
    
    856
    --- asynchronously (the handler may fork a computation that eventually replies
    
    857
    --- and can return before actually replying, as long as it eventually does).
    
    858
    ---
    
    859
    --- The handler receives as an argument the function to use to reply to the request.
    
    860
    --- It must be used exactly once (all requests expect a reply, and you can't
    
    861
    --- reply more than once). Linear types enforce the handler replies in every
    
    862
    --- branch, and if the handler otherwise throws, it is handled by 'handleAsyncReq'.
    
    863
    -handleAsyncReq :: Pipe -> Get a -> (Maybe InboxId) -> (WriteReply Put %1 -> a -> IO r) -> IO r
    
    864
    -handleAsyncReq pipe getit mii k = mask $ \restore -> do
    
    865
    -  let am = pipeAsyncMngr pipe
    
    866
    -  ack <- newEmptyMVar
    
    867
    -
    
    868
    -  -- Block waiting for request
    
    869
    -  mbreq <- atomically $ do
    
    870
    -    isClosed <- readTVar (pipeIsClosed pipe)
    
    871
    -    if isClosed
    
    872
    -      then return Nothing
    
    873
    -      else Just <$> case mii of
    
    874
    -        Nothing -> readTQueue  $  requestsQueue am
    
    875
    -        Just ii -> readTQueue =<< getMailbox am ii
    
    876
    -
    
    877
    -  case mbreq of
    
    878
    -    Nothing -> throwIO PipeClosedException
    
    879
    -    Just (uq, bs) -> do
    
    880
    -      let reply p = do
    
    881
    -            writePipe pipe (Reply uq (bimap toSerializableException runPut p))
    
    882
    -            putMVar ack ()
    
    883
    -
    
    884
    -      -- Run the continuation which must reply exactly once with 'writeReply'
    
    885
    -      case runGetOrFail getit bs of
    
    886
    -        Left (_, _, err) -> do
    
    887
    -          let exc = DecodeAsyncReqException err
    
    888
    -          reply (Left (toException exc))
    
    889
    -          throwIO exc
    
    890
    -        Right (_, _, it) ->
    
    891
    -          restore (k (WriteReply reply) it)
    
    892
    -
    
    893
    -            -- If there's an exception before the reply is sent, reply with an error instead.
    
    894
    -            `catch` \(e :: SomeException) -> do
    
    895
    -
    
    896
    -                tryTakeMVar ack >>= \case
    
    897
    -                  Just () ->
    
    898
    -                    -- Already replied
    
    899
    -                    throwIO e
    
    900
    -                  Nothing -> do
    
    901
    -                    -- Reply with an error
    
    902
    -                    reply (Left e)
    
    903
    -                    throwIO e
    
    904
    -
    
    905
    --- | An exception that can be thrown on the host process representing an
    
    906
    --- expection that happened on the remote one.
    
    907
    -newtype RemoteProcessException = RemoteProcessException SerializableException deriving Show
    
    908
    -instance Exception RemoteProcessException
    
    909
    -
    
    910
    --- | 'handleSyncReq' failed to decode an incoming request payload using the given @'Get' a@
    
    911
    -newtype DecodeAsyncReqException = DecodeAsyncReqException String deriving Show
    
    912
    -instance Exception DecodeAsyncReqException
    
    913
    -
    
    914
    --- | We've closed the pipe on our end and we terminate all pending requests with this.
    
    915
    -data PipeClosedException = PipeClosedException deriving Show
    
    916
    -instance Exception PipeClosedException
    
    917
    -
    
    918
    --- ** Async matching "internals" -----------------------------------------------
    
    919
    -
    
    920
    -data AsyncManager = AsyncManager
    
    921
    -  { pendingReply :: !(TVar (IntMap (TMVar (Either SomeException LB.ByteString))))
    
    922
    -    -- ^ When a request is sent, keep track that we're expecting a reply. The
    
    923
    -    -- TMVar is signaled when the reply arrives. The exception can originate
    
    924
    -    -- from the host process, or from the remote one (with 'RemoteProcessException').
    
    925
    -  , requestsQueue :: !(TQueue (CorrelationId, LB.ByteString))
    
    926
    -    -- ^ A queue of pending received requests. When a request is received
    
    927
    -    -- /without/ a specific 'InboxId', queue it here. A request handler will
    
    928
    -    -- pop the next request from it. (Nothing except the 'drainPipe' thread
    
    929
    -    -- should read from the pipe). Read with @'handleAsyncReq' p get Nothing@.
    
    930
    -  , mailboxQueues :: !(TVar (IntMap (TQueue (CorrelationId, LB.ByteString))))
    
    931
    -    -- ^ Requests for a specific 'InboxId' that can be read using
    
    932
    -    -- @'handleAsyncReq' p get (Just <mailboxid>)@.
    
    933
    -  , asyncDrainTid :: !ThreadId
    
    934
    -  , asyncFreshId  :: !(IORef Int)
    
    935
    -  }
    
    936
    -
    
    937
    --- | Each 'Request' expects a 'Reply' with a matching 'CorrelationId'.
    
    938
    -type CorrelationId = Int
    
    939
    -
    
    940
    --- | A 'Request' may optionally declare an 'InboxId'. If it does, it can only
    
    941
    --- be read by a 'handleAsyncReq' for that specific 'InboxId'. That is,
    
    942
    --- @'handleAsyncReq' p get Nothing@ will not receive messages meant for a
    
    943
    --- specific inbox.
    
    944
    ---
    
    945
    --- This is useful to prevent mixing up reverse requests, where we want to send
    
    946
    --- many reverse requests but have the caller handle them without interleaving
    
    947
    --- them.
    
    948
    -newtype InboxId = InboxId Int deriving (Show, Binary)
    
    949
    -
    
    950
    --- | Create a new mailbox from where we can read all requests addressed to this 'InboxId'.
    
    951
    -newMailbox :: AsyncManager -> IO InboxId
    
    952
    -newMailbox am = InboxId <$> freshId am
    
    953
    -  -- `mailboxQueues` is written on demand, no need to do it here.
    
    954
    -
    
    955
    --- | Clean up a created mailbox by 'InboxId'.
    
    956
    -closeMailbox :: AsyncManager -> InboxId -> IO ()
    
    957
    -closeMailbox AsyncManager{..} (InboxId ii) =
    
    958
    -  atomically (modifyTVar mailboxQueues (IntMap.delete ii))
    
    959
    -
    
    960
    --- | We have a single type for requests and replies because both the host and
    
    961
    --- interpreter may send requests *and* replies on the write end *and* on recv
    
    962
    --- both on the read end.
    
    963
    ---
    
    964
    --- For instance:
    
    965
    ---  1. The host sends a RunTH request on its Host Write End (HWE)
    
    966
    ---  2. The interpreter sends a NewName TH request on its Interpreter Write End (IWE)
    
    967
    ---  3. The host sends a reply to the TH request on its HWE
    
    968
    ---  4. The interpreter sends the reply to the RunTH request on its IWE
    
    969
    ---
    
    970
    --- Additionally, we want to keep concurrent TH requests for independent RunTH
    
    971
    --- calls separated. Thus the interpreter requests additionally specify a
    
    972
    --- mailbox announced by RunTH:
    
    973
    ---  1. To interpreter:   @Request _ Nothing (RunTH <mailid>)@
    
    974
    ---  2. From interpreter: @Request _ (Just <mailid>) NewName@
    
    975
    --- And the host will relate the <mailid> reverse requests to that RunTH run
    
    976
    --- exclusively.
    
    977
    -data IddMsg  = Request !CorrelationId !(Maybe InboxId) LB.ByteString
    
    978
    -             | Reply   !CorrelationId (Either SerializableException LB.ByteString)
    
    979
    -
    
    980
    -instance Binary IddMsg where
    
    981
    -  put (Request i ii bs) = putWord8 0 >> put i >> put ii >> put bs
    
    982
    -  put (Reply i bs)      = putWord8 1 >> put i >> put bs
    
    983
    -  get = do
    
    984
    -    b <- getWord8
    
    985
    -    case b of
    
    986
    -      0 -> Request <$> get <*> get <*> get
    
    987
    -      1 -> Reply   <$> get <*> get
    
    988
    -      _ -> fail $ "impossible: Binary IddMsg - " ++ show b
    
    989
    -
    
    990
    -newAsyncManager :: Pipe -> IO AsyncManager
    
    991
    -newAsyncManager pipe = do
    
    992
    -  asyncFreshId  <- newIORef 0
    
    993
    -  pendingReply  <- newTVarIO IntMap.empty
    
    994
    -  requestsQueue <- newTQueueIO
    
    995
    -  mailboxQueues <- newTVarIO IntMap.empty
    
    996
    -
    
    997
    -  -- Fork thread to drain read end.
    
    998
    -  -- (cleaned up on 'closePipe')
    
    999
    -  asyncDrainTid <- forkIOWithUnmask (drainPipe pipe)
    
    1000
    -
    
    1001
    -  let am = AsyncManager{..}
    
    1002
    -  pure am
    
    1003
    -
    
    1004
    --- | Drains the read end of the Pipe and match 'Reply'es against pending 'Request's.
    
    1005
    ---
    
    1006
    --- In pratice, we'll have both a thread draining the read end of the host's
    
    1007
    --- 'Pipe' and a thread draining the read end of the interpreter's 'Pipe', since
    
    1008
    --- both processes construct a 'Pipe' and send and receive messages
    
    1009
    -drainPipe :: Pipe -> (forall x. IO x -> IO x) -> IO ()
    
    1010
    -drainPipe p unmask = catch (unmask loop) replyToPendings where
    
    1011
    -  loop = do
    
    1012
    -    readPipe p >>= \case
    
    1013
    -      -- Receive a reply to a pending notification
    
    1014
    -      Just (Reply uq bs) -> do
    
    1015
    -        atomically $ do
    
    1016
    -          m <- readTVar (pendingReply am)
    
    1017
    -          case IntMap.lookup uq m of
    
    1018
    -            Nothing ->
    
    1019
    -              -- the req was dropped from pending bc there was an exception, and no
    
    1020
    -              -- one's listening any more. the reply is ignored
    
    1021
    -              pure ()
    
    1022
    -            Just wv -> do
    
    1023
    -              writeTVar (pendingReply am) (IntMap.delete uq m)
    
    1024
    -              -- signal matching request
    
    1025
    -              putTMVar wv (bimap (toException . RemoteProcessException) id bs)
    
    1026
    -        loop
    
    1027
    -      -- Receive a request and queue it
    
    1028
    -      Just (Request uq mbi bs) -> do
    
    1029
    -        atomically $ case mbi of
    
    1030
    -          Nothing ->
    
    1031
    -            -- "Global" request
    
    1032
    -            writeTQueue (requestsQueue am) (uq,bs)
    
    1033
    -          Just ii -> do
    
    1034
    -            -- "Specific mailbox" request
    
    1035
    -            q <- getMailbox am ii
    
    1036
    -            writeTQueue q (uq,bs)
    
    1037
    -        loop
    
    1038
    -      -- EOF on read handle
    
    1039
    -      Nothing -> do
    
    1040
    -        -- clean up pipe then exit loop
    
    1041
    -        replyToPendings (toException PipeClosedException)
    
    1042
    -        pure ()
    
    1043
    -
    
    1044
    -  replyToPendings e = atomically $ do
    
    1045
    -    writeTVar (pipeIsClosed p) True
    
    1046
    -    pendings <- swapTVar (pendingReply am) IntMap.empty -- take pending
    
    1047
    -    mapM_ (\p -> putTMVar p (Left e {-host exception-})) pendings
    
    1048
    -
    
    1049
    -  am = pipeAsyncMngr p
    
    1050
    -
    
    1051
    -freshId :: AsyncManager -> IO Int
    
    1052
    -freshId AsyncManager{asyncFreshId} =
    
    1053
    -  atomicModifyIORef' asyncFreshId $ \i -> (i+1, i)
    
    1054
    -
    
    1055
    -getMailbox :: AsyncManager -> InboxId -> STM (TQueue (CorrelationId, LB.ByteString))
    
    1056
    -getMailbox am (InboxId ii) = do
    
    1057
    -  mboxes <- readTVar (mailboxQueues am)
    
    1058
    -  case IntMap.lookup ii mboxes of
    
    1059
    -    Nothing -> do
    
    1060
    -      q <- newTQueue
    
    1061
    -      writeTVar (mailboxQueues am) (IntMap.insert ii q mboxes)
    
    1062
    -      pure q
    
    1063
    -    Just q ->
    
    1064
    -      pure q
    
    1065
    -
    
    1066
    ---------------------------------------------------------------------------------
    
    1067
    --- * Pipe thread-safe read/write
    
    1068
    ---------------------------------------------------------------------------------
    
    1069
    --- All messages on the wire are 'IddMsg', which contain a correlation header
    
    1070
    --- and identify whether they are requests or replies.
    
    1071
    -
    
    1072
    -writePipe :: Pipe -> IddMsg -> IO ()
    
    1073
    -writePipe p@Pipe{..} = withLock p . putAll . execPut . put
    
    1074
    -
    
    1075
    --- | Only the 'drainPipe' thread should read from the pipe directly. Other
    
    1076
    --- functions should wait for the matching reply to be delivered asynchronously.
    
    1077
    --- (see 'withAsyncReq'). It doesn't lock to read bc it assumes single reader is 'drainPipe'.
    
    1078
    ---
    
    1079
    --- Returns @Nothing@ on EOF.
    
    1080
    -readPipe :: Pipe -> IO (Maybe IddMsg)
    
    1081
    -readPipe Pipe{..} = do
    
    1082
    -  leftovers <- readIORef pipeLeftovers
    
    1083
    -  m <- getBin getSome get leftovers
    
    1084
    -  case m of
    
    1085
    -    Nothing -> return Nothing
    
    1086
    -    Just (result, new_leftovers) -> do
    
    1087
    -      writeIORef pipeLeftovers new_leftovers
    
    1088
    -      return (Just result)
    
    1089
    -
    
    1090
    -getBin
    
    1091
    -  :: (IO ByteString) -> Get a -> Maybe ByteString
    
    1092
    -  -> IO (Maybe (a, Maybe ByteString))
    
    1093
    -
    
    1094
    -getBin getsome get leftover = go leftover (runGetIncremental get)
    
    1095
    - where
    
    1096
    -   go Nothing (Done leftover _ msg) =
    
    1097
    -     return (Just (msg, if B.null leftover then Nothing else Just leftover))
    
    1098
    -   go _ Done{} = throwIO (ErrorCall "getBin: Done with leftovers")
    
    1099
    -   go (Just leftover) (Partial fun) = do
    
    1100
    -     go Nothing (fun (Just leftover))
    
    1101
    -   go Nothing (Partial fun) = do
    
    1102
    -     -- putStrLn "before hGetSome"
    
    1103
    -     b <- getsome
    
    1104
    -     -- putStrLn $ "hGetSome: " ++ show (B.length b)
    
    1105
    -     if B.null b
    
    1106
    -        then return Nothing
    
    1107
    -        else go Nothing (fun (Just b))
    
    1108
    -   go _lft (Fail _rest _off str) =
    
    1109
    -     throwIO (ErrorCall ("getBin: " ++ str))
    
    1110
    -
    
    1111
    -withLock :: Pipe -> IO c -> IO c
    
    1112
    -withLock Pipe{..} = bracket (takeMVar pipeLock) (putMVar pipeLock) . const
    
    1113
    -
    
    1114
    ---------------------------------------------------------------------------------
    
    1115
    --- * Consume `reply` exactly once
    
    1116
    ---------------------------------------------------------------------------------
    
    1117
    -
    
    1118
    --- | Every request should get exactly one reply.
    
    1119
    -data WriteReply a where
    
    1120
    -  WriteReply :: (Either SomeException a -> IO ()) %1 -> WriteReply a
    
    1121
    -
    
    1122
    --- | Write a reply exactly once (consuming the ability to write more replies)
    
    1123
    -writeReply :: WriteReply a %1 -> a -> IO ()
    
    1124
    -writeReply (WriteReply r) = \x -> r (Right x)
    
    1125
    -
    
    1126
    --- | Infix, linear, 'unIO'
    
    1127
    -(|#>) :: IO a %1 -> State# RealWorld -> (# State# RealWorld, a #)
    
    1128
    -(|#>) (IO fs) s = fs s

  • libraries/ghci/GHCi/Message/Async.hs
    1
    +{-# LANGUAGE RecursiveDo, GADTs, DeriveGeneric, StandaloneDeriving, ScopedTypeVariables,
    
    2
    +    GeneralizedNewtypeDeriving, ExistentialQuantification, RecordWildCards, CPP,
    
    3
    +    NamedFieldPuns, LambdaCase, TypeAbstractions, TypeApplications,
    
    4
    +    LinearTypes, RankNTypes, UnboxedTuples, BlockArguments, TupleSections, MagicHash #-}
    
    5
    +{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}
    
    6
    +module GHCi.Message.Async
    
    7
    +  (
    
    8
    +    -- * Pipes
    
    9
    +    Pipe(..), mkPipeFromHandles, mkPipeFromContinuations
    
    10
    +  , closePipe, PipeClosedException(..)
    
    11
    +
    
    12
    +    -- * Interface for async bidirectional RPC
    
    13
    +  , remoteAsyncReq, handleAsyncReq
    
    14
    +  , RemoteProcessException(..), DecodeAsyncReqException(..)
    
    15
    +
    
    16
    +    -- ** Mailboxes
    
    17
    +  , InboxId(..)
    
    18
    +  , newMailbox
    
    19
    +  , closeMailbox
    
    20
    +
    
    21
    +    -- ** Async matching internals
    
    22
    +  , AsyncManager(..), CorrelationId
    
    23
    +  , IddMsg(..), newAsyncManager
    
    24
    +
    
    25
    +    -- ** Consume `reply` exactly once
    
    26
    +  , WriteReply(..)
    
    27
    +  , writeReply, (|#>)
    
    28
    +
    
    29
    +    -- * Serializable exceptions
    
    30
    +  , SerializableException(..)
    
    31
    +  , toSerializableException, fromSerializableException
    
    32
    +  ) where
    
    33
    +
    
    34
    +import Prelude -- See note [Why do we import Prelude here?]
    
    35
    +import GHCi.TH.Binary () -- For Binary instances
    
    36
    +
    
    37
    +import GHC.IO (IO(..))
    
    38
    +import Control.Concurrent
    
    39
    +import Control.Exception
    
    40
    +#if MIN_VERSION_base(4,20,0)
    
    41
    +import Control.Exception.Context
    
    42
    +#endif
    
    43
    +import Data.Binary
    
    44
    +import Data.Binary.Get
    
    45
    +import Data.Binary.Put
    
    46
    +import Data.ByteString (ByteString)
    
    47
    +import qualified Data.ByteString as B
    
    48
    +import qualified Data.ByteString.Builder as B
    
    49
    +import qualified Data.ByteString.Lazy as LB
    
    50
    +import Data.IORef
    
    51
    +import Data.IntMap (IntMap)
    
    52
    +import qualified Data.IntMap as IntMap
    
    53
    +import GHC.Generics
    
    54
    +import System.Exit
    
    55
    +import System.IO
    
    56
    +import Control.Monad
    
    57
    +import Control.Concurrent.STM
    
    58
    +import Data.Bifunctor
    
    59
    +import GHC.Base (State#)
    
    60
    +import GHC.Exts (RealWorld)
    
    61
    +
    
    62
    +--------------------------------------------------------------------------------
    
    63
    +-- * Reading/writing and Pipes
    
    64
    +--------------------------------------------------------------------------------
    
    65
    +
    
    66
    +-- | An opaque pipe for bidirectional binary data transmission.
    
    67
    +data Pipe = Pipe
    
    68
    +  { getSome         :: !(IO ByteString)
    
    69
    +  , putAll          :: !(B.Builder -> IO ())
    
    70
    +  , pipeLeftovers   :: !(IORef (Maybe ByteString))
    
    71
    +  , pipeLock        :: !(MVar ()) -- ^ Lock to prevent concurrent writes to the stream
    
    72
    +  , pipeAsyncMngr   :: !AsyncManager
    
    73
    +  , pipeIsClosed    :: !(TVar Bool)
    
    74
    +    -- ^ After the pipe is closed, requests can no longer be received nor sent
    
    75
    +  }
    
    76
    +
    
    77
    +-- | Make a 'Pipe' from a 'Handle' to read and a 'Handle' to write.
    
    78
    +-- Must call 'closePipe' at the end to clean up.
    
    79
    +mkPipeFromHandles :: Handle -> Handle -> IO Pipe
    
    80
    +mkPipeFromHandles pipeRead pipeWrite = do
    
    81
    +  let getSome = B.hGetSome pipeRead (32*1024)
    
    82
    +      putAll b = do
    
    83
    +        B.hPutBuilder pipeWrite b
    
    84
    +        hFlush pipeWrite
    
    85
    +  mkPipeFromContinuations getSome putAll
    
    86
    +
    
    87
    +-- | Make a 'Pipe' from a reader function and a writer function.
    
    88
    +-- The pipe should be closed with 'closePipe'.
    
    89
    +mkPipeFromContinuations :: IO ByteString -> (B.Builder -> IO ()) -> IO Pipe
    
    90
    +mkPipeFromContinuations getSome putAll = mdo
    
    91
    +  pipeLeftovers   <- newIORef Nothing
    
    92
    +  pipeLock        <- newMVar ()
    
    93
    +  pipeIsClosed    <- newTVarIO False
    
    94
    +  mdo
    
    95
    +    pipeAsyncMngr <- newAsyncManager p
    
    96
    +    let p = Pipe { .. }
    
    97
    +    pure p
    
    98
    +
    
    99
    +-- | Cleanly close our end of the Pipe, terminating the async manager's
    
    100
    +-- drain-read-end thread. Calling 'closePipe' on an already closed pipe is a no-op.
    
    101
    +--
    
    102
    +-- If the pipe was created using 'mkPipeFromHandles', the handles must only by
    
    103
    +-- closed after calling 'closePipe'.
    
    104
    +--
    
    105
    +-- The requests we've sent that are pending are all canceled with
    
    106
    +-- 'PipeClosedException' by ourselves.
    
    107
    +--
    
    108
    +-- The remote process does not necessarily detect that this end of the pipe was
    
    109
    +-- closed, but it must handle cleanly and clean up after exceptions caused from
    
    110
    +-- it such as a handle being closed.
    
    111
    +closePipe :: Pipe -> IO ()
    
    112
    +closePipe Pipe{..} = do
    
    113
    +
    
    114
    +  isAlreadyClosed <- atomically $ swapTVar pipeIsClosed True
    
    115
    +
    
    116
    +  unless isAlreadyClosed $ do
    
    117
    +
    
    118
    +    -- releases handle lock taken by reading, and cleans up by replying to every
    
    119
    +    -- pending request with 'PipeClosedException'.
    
    120
    +    throwTo (asyncDrainTid pipeAsyncMngr) PipeClosedException
    
    121
    +
    
    122
    +-- | We've closed the pipe on our end and we terminate all pending requests with this.
    
    123
    +data PipeClosedException = PipeClosedException deriving Show
    
    124
    +instance Exception PipeClosedException
    
    125
    +
    
    126
    +--------------------------------------------------------------------------------
    
    127
    +-- ** Pipe thread-safe read/write
    
    128
    +--------------------------------------------------------------------------------
    
    129
    +-- All messages on the wire are 'IddMsg', which contain a correlation header
    
    130
    +-- and identify whether they are requests or replies.
    
    131
    +
    
    132
    +writePipe :: Pipe -> IddMsg -> IO ()
    
    133
    +writePipe p@Pipe{..} = withLock p . putAll . execPut . put
    
    134
    +
    
    135
    +-- | Only the 'drainPipe' thread should read from the pipe directly. Other
    
    136
    +-- functions should wait for the matching reply to be delivered asynchronously.
    
    137
    +-- (see 'withAsyncReq'). It doesn't lock to read bc it assumes single reader is 'drainPipe'.
    
    138
    +--
    
    139
    +-- Returns @Nothing@ on EOF.
    
    140
    +readPipe :: Pipe -> IO (Maybe IddMsg)
    
    141
    +readPipe Pipe{..} = do
    
    142
    +  leftovers <- readIORef pipeLeftovers
    
    143
    +  m <- getBin getSome get leftovers
    
    144
    +  case m of
    
    145
    +    Nothing -> return Nothing
    
    146
    +    Just (result, new_leftovers) -> do
    
    147
    +      writeIORef pipeLeftovers new_leftovers
    
    148
    +      return (Just result)
    
    149
    +
    
    150
    +getBin
    
    151
    +  :: (IO ByteString) -> Get a -> Maybe ByteString
    
    152
    +  -> IO (Maybe (a, Maybe ByteString))
    
    153
    +
    
    154
    +getBin getsome get leftover = go leftover (runGetIncremental get)
    
    155
    + where
    
    156
    +   go Nothing (Done leftover _ msg) =
    
    157
    +     return (Just (msg, if B.null leftover then Nothing else Just leftover))
    
    158
    +   go _ Done{} = throwIO (ErrorCall "getBin: Done with leftovers")
    
    159
    +   go (Just leftover) (Partial fun) = do
    
    160
    +     go Nothing (fun (Just leftover))
    
    161
    +   go Nothing (Partial fun) = do
    
    162
    +     -- putStrLn "before hGetSome"
    
    163
    +     b <- getsome
    
    164
    +     -- putStrLn $ "hGetSome: " ++ show (B.length b)
    
    165
    +     if B.null b
    
    166
    +        then return Nothing
    
    167
    +        else go Nothing (fun (Just b))
    
    168
    +   go _lft (Fail _rest _off str) =
    
    169
    +     throwIO (ErrorCall ("getBin: " ++ str))
    
    170
    +
    
    171
    +withLock :: Pipe -> IO c -> IO c
    
    172
    +withLock Pipe{..} = bracket (takeMVar pipeLock) (putMVar pipeLock) . const
    
    173
    +
    
    174
    +--------------------------------------------------------------------------------
    
    175
    +-- * Matching requests with async replies
    
    176
    +--------------------------------------------------------------------------------
    
    177
    +
    
    178
    +-- | Send an asynchronous request and block waiting for a matching reply
    
    179
    +remoteAsyncReq :: Pipe -> Put -> (Maybe InboxId) -> IO (Either SomeException LB.ByteString)
    
    180
    +remoteAsyncReq pipe msg ii = do
    
    181
    +
    
    182
    +  let pendingReplies = pendingReply (pipeAsyncMngr pipe)
    
    183
    +
    
    184
    +  -- Mark pending request
    
    185
    +  uq <- freshId (pipeAsyncMngr pipe)
    
    186
    +  wv <- newEmptyTMVarIO
    
    187
    +
    
    188
    +  isClosed <- atomically $ do
    
    189
    +    isClosed <- readTVar (pipeIsClosed pipe)
    
    190
    +    if isClosed
    
    191
    +      then pure True
    
    192
    +      else do
    
    193
    +        modifyTVar pendingReplies (IntMap.insert uq wv)
    
    194
    +        pure False
    
    195
    +
    
    196
    +  when isClosed $ do
    
    197
    +    throwIO PipeClosedException
    
    198
    +
    
    199
    +  -- Write request
    
    200
    +  writePipe pipe (Request uq ii (runPut msg))
    
    201
    +    `onException`
    
    202
    +      -- the req wasn't done, or no one will be reading the reply:
    
    203
    +      -- in either case, drop it from the pending map.
    
    204
    +      atomically (modifyTVar pendingReplies (IntMap.delete uq))
    
    205
    +
    
    206
    +  -- Block waiting for reply
    
    207
    +  atomically (takeTMVar wv)
    
    208
    +    `onException`
    
    209
    +      atomically (modifyTVar pendingReplies (IntMap.delete uq))
    
    210
    +
    
    211
    +-- | Receive a single asynchronous request and send a matching reply
    
    212
    +-- asynchronously (the handler may fork a computation that eventually replies
    
    213
    +-- and can return before actually replying, as long as it eventually does).
    
    214
    +--
    
    215
    +-- The handler receives as an argument the function to use to reply to the request.
    
    216
    +-- It must be used exactly once (all requests expect a reply, and you can't
    
    217
    +-- reply more than once). Linear types enforce the handler replies in every
    
    218
    +-- branch, and if the handler otherwise throws, it is handled by 'handleAsyncReq'.
    
    219
    +handleAsyncReq :: Pipe -> Get a -> (Maybe InboxId) -> (WriteReply Put %1 -> a -> IO r) -> IO r
    
    220
    +handleAsyncReq pipe getit mii k = mask $ \restore -> do
    
    221
    +  let am = pipeAsyncMngr pipe
    
    222
    +  ack <- newEmptyMVar
    
    223
    +
    
    224
    +  -- Block waiting for request
    
    225
    +  mbreq <- atomically $ do
    
    226
    +    isClosed <- readTVar (pipeIsClosed pipe)
    
    227
    +    if isClosed
    
    228
    +      then return Nothing
    
    229
    +      else Just <$> case mii of
    
    230
    +        Nothing -> readTQueue  $  requestsQueue am
    
    231
    +        Just ii -> readTQueue =<< getMailbox am ii
    
    232
    +
    
    233
    +  case mbreq of
    
    234
    +    Nothing -> throwIO PipeClosedException
    
    235
    +    Just (uq, bs) -> do
    
    236
    +      let reply p = do
    
    237
    +            writePipe pipe (Reply uq (bimap toSerializableException runPut p))
    
    238
    +            putMVar ack ()
    
    239
    +
    
    240
    +      -- Run the continuation which must reply exactly once with 'writeReply'
    
    241
    +      case runGetOrFail getit bs of
    
    242
    +        Left (_, _, err) -> do
    
    243
    +          let exc = DecodeAsyncReqException err
    
    244
    +          reply (Left (toException exc))
    
    245
    +          throwIO exc
    
    246
    +        Right (_, _, it) ->
    
    247
    +          restore (k (WriteReply reply) it)
    
    248
    +
    
    249
    +            -- If there's an exception before the reply is sent, reply with an error instead.
    
    250
    +            `catch` \(e :: SomeException) -> do
    
    251
    +
    
    252
    +                tryTakeMVar ack >>= \case
    
    253
    +                  Just () ->
    
    254
    +                    -- Already replied
    
    255
    +                    throwIO e
    
    256
    +                  Nothing -> do
    
    257
    +                    -- Reply with an error
    
    258
    +                    reply (Left e)
    
    259
    +                    throwIO e
    
    260
    +
    
    261
    +-- | 'handleSyncReq' failed to decode an incoming request payload using the given @'Get' a@
    
    262
    +newtype DecodeAsyncReqException = DecodeAsyncReqException String deriving Show
    
    263
    +instance Exception DecodeAsyncReqException
    
    264
    +
    
    265
    +
    
    266
    +-- ** Mailboxes ----------------------------------------------------------------
    
    267
    +
    
    268
    +-- | A 'Request' may optionally declare an 'InboxId'. If it does, it can only
    
    269
    +-- be read by a 'handleAsyncReq' for that specific 'InboxId'. That is,
    
    270
    +-- @'handleAsyncReq' p get Nothing@ will not receive messages meant for a
    
    271
    +-- specific inbox.
    
    272
    +--
    
    273
    +-- This is useful to prevent mixing up reverse requests, where we want to send
    
    274
    +-- many reverse requests but have the caller handle them without interleaving
    
    275
    +-- them.
    
    276
    +newtype InboxId = InboxId Int deriving (Show, Binary)
    
    277
    +
    
    278
    +-- | Create a new mailbox from where we can read all requests addressed to this 'InboxId'.
    
    279
    +newMailbox :: AsyncManager -> IO InboxId
    
    280
    +newMailbox am = InboxId <$> freshId am
    
    281
    +  -- `mailboxQueues` is written on demand, no need to do it here.
    
    282
    +
    
    283
    +-- | Clean up a created mailbox by 'InboxId'.
    
    284
    +closeMailbox :: AsyncManager -> InboxId -> IO ()
    
    285
    +closeMailbox AsyncManager{..} (InboxId ii) =
    
    286
    +  atomically (modifyTVar mailboxQueues (IntMap.delete ii))
    
    287
    +
    
    288
    +-- | Get or create a new mailbox for this 'InboxId'
    
    289
    +getMailbox :: AsyncManager -> InboxId -> STM (TQueue (CorrelationId, LB.ByteString))
    
    290
    +getMailbox am (InboxId ii) = do
    
    291
    +  mboxes <- readTVar (mailboxQueues am)
    
    292
    +  case IntMap.lookup ii mboxes of
    
    293
    +    Nothing -> do
    
    294
    +      q <- newTQueue
    
    295
    +      writeTVar (mailboxQueues am) (IntMap.insert ii q mboxes)
    
    296
    +      pure q
    
    297
    +    Just q ->
    
    298
    +      pure q
    
    299
    +
    
    300
    +-- ** Async matching "internals" -----------------------------------------------
    
    301
    +
    
    302
    +data AsyncManager = AsyncManager
    
    303
    +  { pendingReply :: !(TVar (IntMap (TMVar (Either SomeException LB.ByteString))))
    
    304
    +    -- ^ When a request is sent, keep track that we're expecting a reply. The
    
    305
    +    -- TMVar is signaled when the reply arrives. The exception can originate
    
    306
    +    -- from the host process, or from the remote one (with 'RemoteProcessException').
    
    307
    +  , requestsQueue :: !(TQueue (CorrelationId, LB.ByteString))
    
    308
    +    -- ^ A queue of pending received requests. When a request is received
    
    309
    +    -- /without/ a specific 'InboxId', queue it here. A request handler will
    
    310
    +    -- pop the next request from it. (Nothing except the 'drainPipe' thread
    
    311
    +    -- should read from the pipe). Read with @'handleAsyncReq' p get Nothing@.
    
    312
    +  , mailboxQueues :: !(TVar (IntMap (TQueue (CorrelationId, LB.ByteString))))
    
    313
    +    -- ^ Requests for a specific 'InboxId' that can be read using
    
    314
    +    -- @'handleAsyncReq' p get (Just <mailboxid>)@.
    
    315
    +  , asyncDrainTid :: !ThreadId
    
    316
    +  , asyncFreshId  :: !(IORef Int)
    
    317
    +  }
    
    318
    +
    
    319
    +-- | Each 'Request' expects a 'Reply' with a matching 'CorrelationId'.
    
    320
    +type CorrelationId = Int
    
    321
    +
    
    322
    +-- | We have a single type for requests and replies because both the host and
    
    323
    +-- interpreter may send requests *and* replies on the write end *and* on recv
    
    324
    +-- both on the read end.
    
    325
    +--
    
    326
    +-- For instance:
    
    327
    +--  1. The host sends a RunTH request on its Host Write End (HWE)
    
    328
    +--  2. The interpreter sends a NewName TH request on its Interpreter Write End (IWE)
    
    329
    +--  3. The host sends a reply to the TH request on its HWE
    
    330
    +--  4. The interpreter sends the reply to the RunTH request on its IWE
    
    331
    +--
    
    332
    +-- Additionally, we want to keep concurrent TH requests for independent RunTH
    
    333
    +-- calls separated. Thus the interpreter requests additionally specify a
    
    334
    +-- mailbox announced by RunTH:
    
    335
    +--  1. To interpreter:   @Request _ Nothing (RunTH <mailid>)@
    
    336
    +--  2. From interpreter: @Request _ (Just <mailid>) NewName@
    
    337
    +-- And the host will relate the <mailid> reverse requests to that RunTH run
    
    338
    +-- exclusively.
    
    339
    +data IddMsg  = Request !CorrelationId !(Maybe InboxId) LB.ByteString
    
    340
    +             | Reply   !CorrelationId (Either SerializableException LB.ByteString)
    
    341
    +
    
    342
    +instance Binary IddMsg where
    
    343
    +  put (Request i ii bs) = putWord8 0 >> put i >> put ii >> put bs
    
    344
    +  put (Reply i bs)      = putWord8 1 >> put i >> put bs
    
    345
    +  get = do
    
    346
    +    b <- getWord8
    
    347
    +    case b of
    
    348
    +      0 -> Request <$> get <*> get <*> get
    
    349
    +      1 -> Reply   <$> get <*> get
    
    350
    +      _ -> fail $ "impossible: Binary IddMsg - " ++ show b
    
    351
    +
    
    352
    +newAsyncManager :: Pipe -> IO AsyncManager
    
    353
    +newAsyncManager pipe = do
    
    354
    +  asyncFreshId  <- newIORef 0
    
    355
    +  pendingReply  <- newTVarIO IntMap.empty
    
    356
    +  requestsQueue <- newTQueueIO
    
    357
    +  mailboxQueues <- newTVarIO IntMap.empty
    
    358
    +
    
    359
    +  -- Fork thread to drain read end.
    
    360
    +  -- (cleaned up on 'closePipe')
    
    361
    +  asyncDrainTid <- forkIOWithUnmask (drainPipe pipe)
    
    362
    +
    
    363
    +  let am = AsyncManager{..}
    
    364
    +  pure am
    
    365
    +
    
    366
    +-- | Drains the read end of the Pipe and match 'Reply'es against pending 'Request's.
    
    367
    +--
    
    368
    +-- In pratice, we'll have both a thread draining the read end of the host's
    
    369
    +-- 'Pipe' and a thread draining the read end of the interpreter's 'Pipe', since
    
    370
    +-- both processes construct a 'Pipe' and send and receive messages
    
    371
    +drainPipe :: Pipe -> (forall x. IO x -> IO x) -> IO ()
    
    372
    +drainPipe p unmask = catch (unmask loop) replyToPendings where
    
    373
    +  loop = do
    
    374
    +    readPipe p >>= \case
    
    375
    +      -- Receive a reply to a pending notification
    
    376
    +      Just (Reply uq bs) -> do
    
    377
    +        atomically $ do
    
    378
    +          m <- readTVar (pendingReply am)
    
    379
    +          case IntMap.lookup uq m of
    
    380
    +            Nothing ->
    
    381
    +              -- the req was dropped from pending bc there was an exception, and no
    
    382
    +              -- one's listening any more. the reply is ignored
    
    383
    +              pure ()
    
    384
    +            Just wv -> do
    
    385
    +              writeTVar (pendingReply am) (IntMap.delete uq m)
    
    386
    +              -- signal matching request
    
    387
    +              putTMVar wv (bimap (toException . RemoteProcessException) id bs)
    
    388
    +        loop
    
    389
    +      -- Receive a request and queue it
    
    390
    +      Just (Request uq mbi bs) -> do
    
    391
    +        atomically $ case mbi of
    
    392
    +          Nothing ->
    
    393
    +            -- "Global" request
    
    394
    +            writeTQueue (requestsQueue am) (uq,bs)
    
    395
    +          Just ii -> do
    
    396
    +            -- "Specific mailbox" request
    
    397
    +            q <- getMailbox am ii
    
    398
    +            writeTQueue q (uq,bs)
    
    399
    +        loop
    
    400
    +      -- EOF on read handle
    
    401
    +      Nothing -> do
    
    402
    +        -- clean up pipe then exit loop
    
    403
    +        replyToPendings (toException PipeClosedException)
    
    404
    +        pure ()
    
    405
    +
    
    406
    +  replyToPendings e = atomically $ do
    
    407
    +    writeTVar (pipeIsClosed p) True
    
    408
    +    pendings <- swapTVar (pendingReply am) IntMap.empty -- take pending
    
    409
    +    mapM_ (\p -> putTMVar p (Left e {-host exception-})) pendings
    
    410
    +
    
    411
    +  am = pipeAsyncMngr p
    
    412
    +
    
    413
    +-- | An exception that can be thrown on the host process representing an
    
    414
    +-- expection that happened on the remote one.
    
    415
    +newtype RemoteProcessException = RemoteProcessException SerializableException deriving Show
    
    416
    +instance Exception RemoteProcessException
    
    417
    +
    
    418
    +-- | Come up with a fresh id for 'CorrelationId' or 'InboxId'
    
    419
    +freshId :: AsyncManager -> IO Int
    
    420
    +freshId AsyncManager{asyncFreshId} =
    
    421
    +  atomicModifyIORef' asyncFreshId $ \i -> (i+1, i)
    
    422
    +
    
    423
    +--------------------------------------------------------------------------------
    
    424
    +-- * Consume `reply` exactly once
    
    425
    +--------------------------------------------------------------------------------
    
    426
    +
    
    427
    +-- | Every request should get exactly one reply.
    
    428
    +data WriteReply a where
    
    429
    +  WriteReply :: (Either SomeException a -> IO ()) %1 -> WriteReply a
    
    430
    +
    
    431
    +-- | Write a reply exactly once (consuming the ability to write more replies)
    
    432
    +writeReply :: WriteReply a %1 -> a -> IO ()
    
    433
    +writeReply (WriteReply r) = \x -> r (Right x)
    
    434
    +
    
    435
    +-- | Infix, linear, 'unIO'
    
    436
    +(|#>) :: IO a %1 -> State# RealWorld -> (# State# RealWorld, a #)
    
    437
    +(|#>) (IO fs) s = fs s
    
    438
    +
    
    439
    +--------------------------------------------------------------------------------
    
    440
    +-- * Serializable Exceptions
    
    441
    +--------------------------------------------------------------------------------
    
    442
    +
    
    443
    +-- SomeException can't be serialized because it contains dynamic
    
    444
    +-- types.  However, we do very limited things with the exceptions that
    
    445
    +-- are thrown by interpreted computations:
    
    446
    +--
    
    447
    +-- * We print them, e.g. "*** Exception: <something>"
    
    448
    +-- * UserInterrupt has a special meaning
    
    449
    +-- * In ghc -e, exitWith should exit with the appropriate exit code
    
    450
    +--
    
    451
    +-- So all we need to do is distinguish UserInterrupt and ExitCode, and
    
    452
    +-- all other exceptions can be represented by their 'show' string.
    
    453
    +--
    
    454
    +data SerializableException
    
    455
    +  = EUserInterrupt
    
    456
    +  | EExitCode ExitCode
    
    457
    +  | EOtherException String
    
    458
    +  deriving (Generic, Show)
    
    459
    +
    
    460
    +toSerializableException :: SomeException -> SerializableException
    
    461
    +toSerializableException ex
    
    462
    +  | Just UserInterrupt <- fromException ex  = EUserInterrupt
    
    463
    +  | Just (ec::ExitCode) <- fromException ex = (EExitCode ec)
    
    464
    +  | otherwise = EOtherException $
    
    465
    +#if MIN_VERSION_base(4,20,0)
    
    466
    +      -- Exception plus backtrace as seen in `displayExceptionWithInfo`
    
    467
    +      case displayExceptionContext (someExceptionContext ex) of
    
    468
    +        "" -> displayException (ex :: SomeException)
    
    469
    +        cx -> displayException (ex :: SomeException) ++ "\n\n" ++ cx
    
    470
    +#else
    
    471
    +      show (ex :: SomeException)
    
    472
    +#endif
    
    473
    +
    
    474
    +fromSerializableException :: SerializableException -> SomeException
    
    475
    +fromSerializableException EUserInterrupt = toException UserInterrupt
    
    476
    +fromSerializableException (EExitCode c) = toException c
    
    477
    +fromSerializableException (EOtherException str) = toException (ErrorCall str)
    
    478
    +
    
    479
    +instance Binary ExitCode
    
    480
    +instance Binary SerializableException

  • libraries/ghci/ghci.cabal.in
    ... ... @@ -76,6 +76,7 @@ library
    76 76
         exposed-modules:
    
    77 77
             GHCi.BreakArray
    
    78 78
             GHCi.Message
    
    79
    +        GHCi.Message.Async
    
    79 80
             GHCi.ResolvedBCO
    
    80 81
             GHCi.RemoteTypes
    
    81 82
             GHCi.FFI