Andrea Vezzosi pushed to branch wip/romes/async-rpc at Glasgow Haskell Compiler / GHC

Commits:

3 changed files:

Changes:

  • compiler/GHC/Runtime/Interpreter/Process.hs
    ... ... @@ -4,6 +4,7 @@ module GHC.Runtime.Interpreter.Process
    4 4
         Message(..)
    
    5 5
       , sendMessage
    
    6 6
       , sendTHComputation
    
    7
    +  , sendMessageWithResponse
    
    7 8
       )
    
    8 9
     where
    
    9 10
     
    
    ... ... @@ -16,9 +17,7 @@ import GHC.Utils.Panic
    16 17
     import GHC.Utils.Exception as Ex
    
    17 18
     
    
    18 19
     import Data.Binary
    
    19
    -import Control.Concurrent
    
    20 20
     import Data.IORef
    
    21
    -import Control.Concurrent.STM
    
    22 21
     
    
    23 22
     -- -----------------------------------------------------------------------------
    
    24 23
     -- Top-level Message API
    
    ... ... @@ -34,29 +33,27 @@ sendMessage i msg =
    34 33
       where
    
    35 34
         proc = instProcess i
    
    36 35
     
    
    36
    +sendMessageWithResponse :: Binary a => ExtInterpInstance c -> Message a -> (IO a -> IO b) -> IO b
    
    37
    +sendMessageWithResponse i msg k =
    
    38
    +  remoteCallWithResponse (interpPipe proc) msg k
    
    39
    +  where
    
    40
    +    proc = instProcess i
    
    41
    +
    
    37 42
     -- | Send a message with a TH computation to the ext. interpreter (like
    
    38 43
     -- 'RunTH', or 'RunModFinalizers') and handle 'THMessage' requests from the
    
    39
    --- ext. interpreter to us in the meantime.
    
    40
    ---
    
    41
    --- This function blocks until the TH computation returns a value, but spawns in
    
    42
    --- the background a thread to handle the 'THMessage's until the TH computation
    
    43
    --- is fully done.
    
    44
    +-- ext. interpreter to us until done, then wait for the response.
    
    44 45
     sendTHComputation
    
    45 46
       :: Binary a
    
    46 47
       => ExtInterpInstance d
    
    47 48
       -> (InboxId -> Message a)
    
    48 49
       -> (forall b. Binary b => THMessage b -> IO (Bool, b))
    
    49 50
       -- ^ Handle one message. Return True to end the loop.
    
    50
    -  -- This handler must never uninterrutibly mask an expression which may get
    
    51
    -  -- stuck. If it does, then 'sendTHComputation' will also get uninterruptibly stuck.
    
    52 51
       -> IO a
    
    53
    -sendTHComputation i msg reqHandler = mask $ \restore -> do
    
    54
    -  -- inline "withAsync/wait"
    
    55
    -  var  <- newEmptyTMVarIO
    
    56
    -  mbox <- newMailbox (pipeAsyncMngr (interpPipe proc))
    
    57
    -  ht   <- forkFinally (restore (handlerLoop mbox)) (atomically . putTMVar var)
    
    58
    -  restore (sendMessage i (msg mbox) <* atomically (readTMVar var))
    
    59
    -    `onException` uninterruptibleMask_ (throwTo ht CancelTHHandler)
    
    52
    +sendTHComputation i msg reqHandler =
    
    53
    +  withNewMailbox (pipeAsyncMngr (interpPipe proc)) $ \ mbox ->
    
    54
    +    sendMessageWithResponse i (msg mbox) $ \ waitRes -> do
    
    55
    +      handlerLoop mbox
    
    56
    +      waitRes
    
    60 57
       where
    
    61 58
         proc = instProcess i
    
    62 59
         handleOne mbox doneRef = do
    

  • libraries/ghci/GHCi/Message.hs
    ... ... @@ -25,13 +25,12 @@ module GHCi.Message
    25 25
       , getMessage, putMessage, getTHMessage, putTHMessage
    
    26 26
     
    
    27 27
       , Pipe(..), mkPipeFromHandles, mkPipeFromContinuations, closePipe
    
    28
    -  , remoteCall, remoteTHCall
    
    28
    +  , remoteCall, remoteCallWithResponse, remoteTHCall
    
    29 29
       , handleRemoteCall, handleRemoteTHCall, writeReply
    
    30 30
       , RemoteProcessException(..), PipeClosedException(..)
    
    31 31
     
    
    32 32
       , InboxId(..)
    
    33
    -  , newMailbox
    
    34
    -  , closeMailbox
    
    33
    +  , withNewMailbox
    
    35 34
     
    
    36 35
       , BreakModule
    
    37 36
       , BreakUnitId
    
    ... ... @@ -660,8 +659,12 @@ serializeBCOs rbcos = parMap doChunk (chunkList 100 rbcos)
    660 659
     -- | Send a 'Message' request to and wait for a reply from the interpreter
    
    661 660
     -- (blocks on the calling side, doesn't necessarily block the interpreter)
    
    662 661
     remoteCall :: Binary a => Pipe -> Message a -> IO a
    
    663
    -remoteCall pipe msg = either throwIO (pure . runGet get)
    
    664
    -                  =<< remoteAsyncReq pipe (putMessage msg) Nothing
    
    662
    +remoteCall pipe msg = remoteCallWithResponse pipe msg id
    
    663
    +
    
    664
    +remoteCallWithResponse :: Binary a => Pipe -> Message a -> (IO a -> IO b) -> IO b
    
    665
    +remoteCallWithResponse pipe msg k =
    
    666
    +  remoteAsyncReqWithResponse pipe (putMessage msg) Nothing $ \ m ->
    
    667
    +    k $ either throwIO (pure . runGet get) =<< m
    
    665 668
     
    
    666 669
     -- | Handle a single 'Message' request on the interpreter and reply to the host
    
    667 670
     handleRemoteCall :: Pipe
    

  • libraries/ghci/GHCi/Message/Async.hs
    ... ... @@ -11,12 +11,14 @@ module GHCi.Message.Async
    11 11
     
    
    12 12
         -- * Interface for async bidirectional RPC
    
    13 13
       , remoteAsyncReq, handleAsyncReq
    
    14
    +  , remoteAsyncReqWithResponse
    
    14 15
       , RemoteProcessException(..), DecodeAsyncReqException(..)
    
    15 16
     
    
    16 17
         -- ** Mailboxes
    
    17 18
       , InboxId(..)
    
    18 19
       , newMailbox
    
    19 20
       , closeMailbox
    
    21
    +  , withNewMailbox
    
    20 22
     
    
    21 23
         -- ** Async matching internals
    
    22 24
       , AsyncManager(..), CorrelationId
    
    ... ... @@ -247,24 +249,44 @@ newPendingReply pipe k = do
    247 249
     --
    
    248 250
     -- See Note [Exceptions during serialization] about exceptions the 'Put' argument might raise.
    
    249 251
     remoteAsyncReq :: Pipe -> Put -> (Maybe InboxId) -> IO (Either SomeException LB.ByteString)
    
    250
    -remoteAsyncReq pipe msg ii = newPendingReply pipe $ \ uq wv ->
    
    251
    -  -- We might send a Cancel even if the request never got sent, but it will just
    
    252
    -  -- get ignored in that case.
    
    253
    -  handle (\e -> sendCancelMaybe uq e >> throwIO e) $ do
    
    252
    +remoteAsyncReq pipe msg ii = remoteAsyncReqWithResponse pipe msg ii id
    
    254 253
     
    
    255
    -  -- Write request
    
    256
    -  writePipe pipe (Request uq ii (runPut msg))
    
    257 254
     
    
    258
    -  -- Block waiting for reply
    
    259
    -  atomically (takeTMVar wv)
    
    255
    +remoteAsyncReqWithResponse :: Pipe
    
    256
    +                           -> Put
    
    257
    +                           -> Maybe InboxId
    
    258
    +                           -> (IO (Either SomeException LB.ByteString) -> IO b)
    
    259
    +                           -> IO b
    
    260
    +remoteAsyncReqWithResponse pipe msg ii k = newPendingReply pipe $ \ uq wv -> do
    
    261
    +  -- We use an MVar to only Cancel the request if the response did not get read by `k`.
    
    262
    +  readRes <- newEmptyMVar
    
    263
    +  -- We might send a Cancel even if the request never got sent, but it will just
    
    264
    +  -- get ignored in that case.
    
    265
    +  --
    
    266
    +  -- Waiting for confirmation that the request got sent before "arming" the
    
    267
    +  -- cancelation requires more collaboration from `fillPipe`, because
    
    268
    +  -- writePipe's takeMVar is interruptible. Could be done if we generalize the
    
    269
    +  -- type of `writeQueue` to `TQueue (IddMsg, (Either SomeException ()) -> STM ())`.
    
    270
    +  cancelOnErrorMaybe uq readRes $ do
    
    271
    +    -- Write request
    
    272
    +    writePipe pipe (Request uq ii (runPut msg))
    
    273
    +
    
    274
    +    k $ do
    
    275
    +      -- Block waiting for reply
    
    276
    +      atomically (takeTMVar wv) <* putMVar readRes ()
    
    260 277
       where
    
    261
    -    sendCancelMaybe uq e
    
    262
    -      | Just PipeWriteClosedException <- fromException e = pure ()
    
    263
    -      | otherwise = sendCancel uq
    
    264
    -    sendCancel uq = void . atomically $ do
    
    265
    -      -- we don't wait for Cancel to be written on the pipe,
    
    266
    -      -- and won't get a reply: nowhere to report it anyway.
    
    267
    -      writePipeAsync pipe (Cancel uq)
    
    278
    +    cancelOnErrorMaybe :: CorrelationId -> MVar () -> IO a -> IO a
    
    279
    +    cancelOnErrorMaybe uq readRes = handle (\ e -> sendCancelMaybe e >> throwIO e) where
    
    280
    +      sendCancelMaybe = \case
    
    281
    +        e | Just PipeWriteClosedException <- fromException e ->
    
    282
    +          pure ()
    
    283
    +          | otherwise ->
    
    284
    +          tryTakeMVar readRes >>= \case
    
    285
    +            Just () -> pure ()
    
    286
    +            Nothing -> void . atomically $ do
    
    287
    +              -- we don't wait for Cancel to be written on the pipe,
    
    288
    +              -- and won't get a reply: nowhere to report it anyway.
    
    289
    +              writePipeAsync pipe (Cancel uq)
    
    268 290
     
    
    269 291
     -- | Receive a single asynchronous request and send a matching reply
    
    270 292
     -- asynchronously (the handler may fork a computation that eventually replies
    
    ... ... @@ -352,6 +374,9 @@ closeMailbox :: AsyncManager -> InboxId -> IO ()
    352 374
     closeMailbox AsyncManager{..} (InboxId ii) =
    
    353 375
       atomically (modifyTVar mailboxQueues (IntMap.delete ii))
    
    354 376
     
    
    377
    +withNewMailbox :: AsyncManager -> (InboxId -> IO a) -> IO a
    
    378
    +withNewMailbox am = bracket (newMailbox am) (closeMailbox am)
    
    379
    +
    
    355 380
     -- | Get or create a new mailbox for this 'InboxId'
    
    356 381
     getMailbox :: AsyncManager -> InboxId -> STM (TQueue (CorrelationId, LB.ByteString))
    
    357 382
     getMailbox am (InboxId ii) = do