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

Commits:

7 changed files:

Changes:

  • compiler/GHC/Runtime/Interpreter.hs
    ... ... @@ -105,7 +105,7 @@ import GHCi.Run
    105 105
     import Control.Concurrent
    
    106 106
     import Control.Monad
    
    107 107
     import Control.Monad.IO.Class
    
    108
    -import Control.Monad.Catch as MC (mask)
    
    108
    +import Control.Monad.Catch as MC (mask, handle, MonadThrow (..))
    
    109 109
     import Data.Binary
    
    110 110
     import Data.ByteString (ByteString)
    
    111 111
     import qualified Data.ByteString.Short as SBS
    
    ... ... @@ -196,14 +196,16 @@ interpCmd interp msg = case interpInstance interp of
    196 196
     #if defined(HAVE_INTERNAL_INTERPRETER)
    
    197 197
       InternalInterp     -> run msg -- Just run it directly
    
    198 198
     #endif
    
    199
    -  ExternalInterp ext -> withExtInterp ext $ \inst ->
    
    199
    +  ExternalInterp ext -> withExtInterp interp ext $ \inst ->
    
    200 200
         sendMessage inst msg
    
    201 201
     
    
    202
    -withExtInterp :: ExceptionMonad m => ExtInterp -> (forall d. ExtInterpInstance d -> m a) -> m a
    
    203
    -withExtInterp ext action = case ext of
    
    202
    +withExtInterp :: ExceptionMonad m => Interp -> ExtInterp -> (forall d. ExtInterpInstance d -> m a) -> m a
    
    203
    +withExtInterp interp ext action = stopInterpOnPipeClosed $ case ext of
    
    204 204
       ExtJS    i -> withJSInterp i action
    
    205 205
       ExtWasm  i -> withWasmInterp i action
    
    206 206
       ExtIServ i -> withIServ    i action
    
    207
    +  where
    
    208
    +    stopInterpOnPipeClosed = MC.handle $ \ (e :: PipeClosedException) -> liftIO (stopInterp interp) >> MC.throwM e
    
    207 209
     
    
    208 210
     withExtInterpStatus :: ExtInterp -> (forall d. ExtInterpStatusVar d -> m a) -> m a
    
    209 211
     withExtInterpStatus ext action = case ext of
    
    ... ... @@ -642,6 +644,9 @@ stopInterp interp = case interpInstance interp of
    642 644
                    else sendMessage i Shutdown
    
    643 645
                 -- We asked the server to shutdown, so we will also shutdown any communication.
    
    644 646
                 closePipe (interpPipe (instProcess i))
    
    647
    +            modifyMVar_ (loader_state (interpLoader interp)) (const $ pure Nothing)
    
    648
    +            purgeInterpSymbolCache (interpSymbolCache interp)
    
    649
    +            modifyMVar_ (interpStringCache interp) (const $ pure emptyFsEnv)
    
    645 650
                 pure InterpPending
    
    646 651
     
    
    647 652
     -- -----------------------------------------------------------------------------
    

  • compiler/GHC/Runtime/Interpreter/Process.hs
    ... ... @@ -12,13 +12,10 @@ import GHC.Prelude
    12 12
     import GHC.Runtime.Interpreter.Types
    
    13 13
     import GHCi.Message
    
    14 14
     
    
    15
    -import GHC.IO (catchException)
    
    16 15
     import GHC.Utils.Panic
    
    17 16
     import GHC.Utils.Exception as Ex
    
    18 17
     
    
    19 18
     import Data.Binary
    
    20
    -import System.Exit
    
    21
    -import System.Process
    
    22 19
     import Control.Concurrent
    
    23 20
     import Data.IORef
    
    24 21
     import Control.Concurrent.STM
    
    ... ... @@ -34,7 +31,6 @@ import Control.Concurrent.STM
    34 31
     sendMessage :: Binary a => ExtInterpInstance d -> Message a -> IO a
    
    35 32
     sendMessage i msg =
    
    36 33
       remoteCall (interpPipe proc) msg
    
    37
    -    `catchException` \(e :: SomeException) -> handleInterpProcessFailure proc e
    
    38 34
       where
    
    39 35
         proc = instProcess i
    
    40 36
     
    
    ... ... @@ -64,7 +60,7 @@ sendTHComputation i msg reqHandler = mask $ \restore -> do
    64 60
       where
    
    65 61
         proc = instProcess i
    
    66 62
         handleOne mbox doneRef = do
    
    67
    -      handleRemoteTHCall (interpPipe proc) mbox $ \req -> do
    
    63
    +      handleRemoteTHCall (interpPipe proc) mbox $ \_setCancel req -> do
    
    68 64
             (stop, res) <- reqHandler req
    
    69 65
             writeIORef doneRef stop
    
    70 66
             return res
    
    ... ... @@ -77,18 +73,6 @@ sendTHComputation i msg reqHandler = mask $ \restore -> do
    77 73
                           else loop
    
    78 74
            in loop
    
    79 75
     
    
    80
    -handleInterpProcessFailure :: InterpProcess -> SomeException -> IO a
    
    81
    -handleInterpProcessFailure i e = do
    
    82
    -  let hdl = interpHandle i
    
    83
    -  ex <- getProcessExitCode hdl
    
    84
    -  case ex of
    
    85
    -    Just (ExitFailure n) ->
    
    86
    -      throwIO (InstallationError ("External interpreter terminated (" ++ show n ++ ")"))
    
    87
    -    _ -> do
    
    88
    -      terminateProcess hdl
    
    89
    -      _ <- waitForProcess hdl
    
    90
    -      throw e
    
    91
    -
    
    92 76
     data CancelTHHandler = CancelTHHandler deriving Show
    
    93 77
     instance Exception CancelTHHandler where
    
    94 78
       fromException = asyncExceptionFromException
    

  • compiler/GHC/Tc/Gen/Splice.hs
    ... ... @@ -1160,7 +1160,7 @@ runRemoteModFinalizers (ThModFinalizers finRefs) = do
    1160 1160
           runQinTcM $ sequence_ qs
    
    1161 1161
     #endif
    
    1162 1162
     
    
    1163
    -    ExternalInterp ext -> withExtInterp ext $ \inst -> do
    
    1163
    +    ExternalInterp ext -> withExtInterp interp ext $ \inst -> do
    
    1164 1164
           tcg <- getGblEnv
    
    1165 1165
           th_state <- readTcRef (tcg_th_remote_state tcg)
    
    1166 1166
           case th_state of
    
    ... ... @@ -1817,7 +1817,7 @@ runTH ty fhv = do
    1817 1817
           return r
    
    1818 1818
     #endif
    
    1819 1819
     
    
    1820
    -    ExternalInterp ext -> withExtInterp ext $ \inst -> do
    
    1820
    +    ExternalInterp ext -> withExtInterp interp ext $ \inst -> do
    
    1821 1821
           -- Run it on the server.  For an overview of how TH works with
    
    1822 1822
           -- Remote GHCi, see Note [Remote Template Haskell] in
    
    1823 1823
           -- libraries/ghci/GHCi/TH.hs.
    

  • libraries/ghci/GHCi/Message.hs
    ... ... @@ -27,7 +27,7 @@ module GHCi.Message
    27 27
       , Pipe(..), mkPipeFromHandles, mkPipeFromContinuations, closePipe
    
    28 28
       , remoteCall, remoteTHCall
    
    29 29
       , handleRemoteCall, handleRemoteTHCall, writeReply
    
    30
    -  , RemoteProcessException(..), PipeReadClosedException(..)
    
    30
    +  , RemoteProcessException(..), PipeClosedException(..)
    
    31 31
     
    
    32 32
       , InboxId(..)
    
    33 33
       , newMailbox
    
    ... ... @@ -666,12 +666,12 @@ remoteCall pipe msg = either throwIO (pure . runGet get)
    666 666
     -- | Handle a single 'Message' request on the interpreter and reply to the host
    
    667 667
     handleRemoteCall :: Pipe
    
    668 668
                      -> (Msg -> IO Msg) {-^ Msg hook -}
    
    669
    -                 -> (forall a. (Show a, Binary a) => WriteReply a %1 -> Message a -> IO r)
    
    669
    +                 -> (forall a. (Show a, Binary a) => WriteReply a %1 -> (IO () -> IO ()) -> Message a -> IO r)
    
    670 670
                      -> IO r
    
    671 671
     handleRemoteCall pipe hook k =
    
    672
    -  handleAsyncReq pipe getMessage Nothing \(WriteReply writer) msg0 -> IO \s0 ->
    
    672
    +  handleAsyncReq pipe getMessage Nothing \(WriteReply writer) sa msg0 -> IO \s0 ->
    
    673 673
         case hook msg0 |#> s0 of -- unIO for linearity checking
    
    674
    -      (# s1, Msg msg #) -> k (WriteReply \x -> writer (fmap put x)) msg |#> s1
    
    674
    +      (# s1, Msg msg #) -> k (WriteReply \x -> writer (fmap put x)) sa msg |#> s1
    
    675 675
     
    
    676 676
     -- | Send a 'THMessage' request to the host and wait for a reply from the host
    
    677 677
     -- to the interpreter.
    
    ... ... @@ -687,9 +687,9 @@ remoteTHCall pipe ii msg = either throwIO (pure . runGet get)
    687 687
     -- Symmterically to 'remoteTHCall', we always want to specify the 'InboxId' for
    
    688 688
     -- RunTH "reverse requests"
    
    689 689
     handleRemoteTHCall
    
    690
    -  :: Pipe -> InboxId -> (forall a. (Show a, Binary a) => THMessage a -> IO a) -> IO ()
    
    690
    +  :: Pipe -> InboxId -> (forall a. (Show a, Binary a) => (IO () -> IO ()) -> THMessage a -> IO a) -> IO ()
    
    691 691
     handleRemoteTHCall pipe ii k =
    
    692
    -  handleAsyncReq pipe getTHMessage (Just ii) \writer (THMsg msg) -> IO \s0 ->
    
    693
    -    case k msg |#> s0 of -- unIO for linearity checking
    
    692
    +  handleAsyncReq pipe getTHMessage (Just ii) \writer sa (THMsg msg) -> IO \s0 ->
    
    693
    +    case k sa msg |#> s0 of -- unIO for linearity checking
    
    694 694
           (# s1, a #) -> writeReply writer (put a) |#> s1
    
    695 695
     

  • libraries/ghci/GHCi/Message/Async.hs
    ... ... @@ -7,7 +7,7 @@ module GHCi.Message.Async
    7 7
       (
    
    8 8
         -- * Pipes
    
    9 9
         Pipe(..), mkPipeFromHandles, mkPipeFromContinuations
    
    10
    -  , closePipe, PipeReadClosedException(..)
    
    10
    +  , closePipe, PipeClosedException(..)
    
    11 11
     
    
    12 12
         -- * Interface for async bidirectional RPC
    
    13 13
       , remoteAsyncReq, handleAsyncReq
    
    ... ... @@ -145,13 +145,13 @@ closePipe p@Pipe{..} = do
    145 145
           unless writeAlreadyClosed
    
    146 146
             (throwTo (asyncFillTid pipeAsyncMngr) PipeWriteClosedException)
    
    147 147
     
    
    148
    --- | We've closed the pipe read end on this process
    
    149
    -data PipeReadClosedException = PipeReadClosedException deriving Show
    
    150
    -instance Exception PipeReadClosedException
    
    148
    +data PipeClosedException
    
    149
    +  = PipeReadClosedException -- ^ We've closed the pipe read end on this process
    
    150
    +
    
    151
    +  | PipeWriteClosedException -- ^ We've closed the pipe write end on this process
    
    152
    +  deriving Show
    
    153
    +instance Exception PipeClosedException
    
    151 154
     
    
    152
    --- | We've closed the pipe write end on this process
    
    153
    -data PipeWriteClosedException = PipeWriteClosedException deriving Show
    
    154
    -instance Exception PipeWriteClosedException
    
    155 155
     
    
    156 156
     --------------------------------------------------------------------------------
    
    157 157
     -- ** Pipe thread-safe read/write
    
    ... ... @@ -160,14 +160,19 @@ instance Exception PipeWriteClosedException
    160 160
     -- and identify whether they are requests or replies.
    
    161 161
     
    
    162 162
     writePipe :: Pipe -> IddMsg -> IO ()
    
    163
    -writePipe Pipe{..} msg = do
    
    164
    -  var <- newEmptyTMVarIO
    
    165
    -  atomically $ do
    
    163
    +writePipe p msg = do
    
    164
    +  var <- atomically $ writePipeAsync p msg
    
    165
    +  either throwIO pure =<< atomically (takeTMVar var)
    
    166
    +
    
    167
    +writePipeAsync :: Pipe -> IddMsg -> STM (TMVar (Either SomeException ()))
    
    168
    +writePipeAsync Pipe{..} msg = do
    
    169
    +    var <- newEmptyTMVar
    
    166 170
         readTVar pipeWriteClosed >>= \case
    
    167 171
           True -> throwSTM PipeWriteClosedException
    
    168 172
           False -> pure ()
    
    169 173
         writeTQueue (writeQueue pipeAsyncMngr) (msg, var)
    
    170
    -  either throwIO pure =<< atomically (takeTMVar var)
    
    174
    +    pure var
    
    175
    +
    
    171 176
     
    
    172 177
     -- | Only the 'drainPipe' thread should read from the pipe directly. Other
    
    173 178
     -- functions should wait for the matching reply to be delivered asynchronously.
    
    ... ... @@ -239,14 +244,18 @@ remoteAsyncReq pipe msg ii = do
    239 244
       -- Write request
    
    240 245
       writePipe pipe (Request uq ii (runPut msg))
    
    241 246
         `onException`
    
    242
    -      -- the req wasn't done, or no one will be reading the reply:
    
    243
    -      -- in either case, drop it from the pending map.
    
    244
    -      atomically (modifyTVar pendingReplies (IntMap.delete uq))
    
    247
    +        -- the req wasn't done, or no one will be reading the reply:
    
    248
    +        -- in either case, drop it from the pending map.
    
    249
    +        atomically (modifyTVar pendingReplies (IntMap.delete uq))
    
    245 250
     
    
    246 251
       -- Block waiting for reply
    
    247 252
       atomically (takeTMVar wv)
    
    248 253
         `onException`
    
    249
    -      atomically (modifyTVar pendingReplies (IntMap.delete uq))
    
    254
    +       atomically (do
    
    255
    +         modifyTVar pendingReplies (IntMap.delete uq)
    
    256
    +         -- we don't wait for Cancel to be written on the pipe,
    
    257
    +         -- and won't get a reply: nowhere to report it anyway.
    
    258
    +         writePipeAsync pipe (Cancel uq))
    
    250 259
     
    
    251 260
     -- | Receive a single asynchronous request and send a matching reply
    
    252 261
     -- asynchronously (the handler may fork a computation that eventually replies
    
    ... ... @@ -258,7 +267,7 @@ remoteAsyncReq pipe msg ii = do
    258 267
     -- branch, and if the handler otherwise throws, it is handled by 'handleAsyncReq'.
    
    259 268
     --
    
    260 269
     -- See Note [Exceptions during serialization] about exceptions the 'Put' argument might raise.
    
    261
    -handleAsyncReq :: Pipe -> Get a -> (Maybe InboxId) -> (WriteReply Put %1 -> a -> IO r) -> IO r
    
    270
    +handleAsyncReq :: Pipe -> Get a -> (Maybe InboxId) -> (WriteReply Put %1 -> (IO () -> IO ()) -> a -> IO r) -> IO r
    
    262 271
     handleAsyncReq pipe getit mii k = mask $ \restore -> do
    
    263 272
       let am = pipeAsyncMngr pipe
    
    264 273
       ack <- newEmptyMVar
    
    ... ... @@ -274,7 +283,7 @@ handleAsyncReq pipe getit mii k = mask $ \restore -> do
    274 283
           Nothing -> tryReadTQueue  $  requestsQueue am
    
    275 284
           Just ii -> tryReadTQueue =<< getMailbox am ii
    
    276 285
         case taken of
    
    277
    -      Just req -> pure (Just req)
    
    286
    +      Just req@(uq,_) -> fetchedRequest am uq >> pure (Just req)
    
    278 287
           Nothing  | isClosed  -> pure Nothing -- empty and closed
    
    279 288
                    | otherwise -> retry -- wait for msg
    
    280 289
     
    
    ... ... @@ -282,9 +291,9 @@ handleAsyncReq pipe getit mii k = mask $ \restore -> do
    282 291
         Nothing -> throwIO PipeReadClosedException -- can't expect reqs
    
    283 292
         Just (uq, bs) -> do
    
    284 293
           let reply p = do
    
    285
    -            writePipe pipe (Reply uq (bimap toSerializableException runPut p))
    
    294
    +            b <- atomically $ doneHandling am uq
    
    295
    +            when b $ writePipe pipe (Reply uq (bimap toSerializableException runPut p))
    
    286 296
                 putMVar ack ()
    
    287
    -
    
    288 297
           -- Run the continuation which must reply exactly once with 'writeReply'
    
    289 298
           case runGetOrFail getit bs of
    
    290 299
             Left (_, _, err) -> do
    
    ... ... @@ -292,7 +301,7 @@ handleAsyncReq pipe getit mii k = mask $ \restore -> do
    292 301
               reply (Left (toException exc))
    
    293 302
               throwIO exc
    
    294 303
             Right (_, _, it) ->
    
    295
    -          restore (k (WriteReply reply) it)
    
    304
    +          restore (k (WriteReply reply) (setActive am uq) it)
    
    296 305
     
    
    297 306
                 -- If there's an exception before the reply is sent, reply with an error instead.
    
    298 307
                 `catch` \(e :: SomeException) -> do
    
    ... ... @@ -306,6 +315,7 @@ handleAsyncReq pipe getit mii k = mask $ \restore -> do
    306 315
                         reply (Left e)
    
    307 316
                         throwIO e
    
    308 317
     
    
    318
    +
    
    309 319
     -- | 'handleSyncReq' failed to decode an incoming request payload using the given @'Get' a@
    
    310 320
     newtype DecodeAsyncReqException = DecodeAsyncReqException String deriving Show
    
    311 321
     instance Exception DecodeAsyncReqException
    
    ... ... @@ -347,6 +357,28 @@ getMailbox am (InboxId ii) = do
    347 357
     
    
    348 358
     -- ** Async matching "internals" -----------------------------------------------
    
    349 359
     
    
    360
    +-- | Tracks handling status of a received 'Request'. The values are states of a
    
    361
    +-- tiny state machine used to coordinate cancelations.
    
    362
    +--
    
    363
    +-- 'handleAsyncReq' will pop a request from the appropriate queue, setting the
    
    364
    +-- status to `Fetched`. Then one of two things can happen first:
    
    365
    +--  - we receive a 'Cancel', then status is set to `Canceling` so the cancel
    
    366
    +--    action will run when set.
    
    367
    +--  - handler sets a cancel action, then reception of a `Cancel` runs that
    
    368
    +--    action and deletes the RequestStatus.
    
    369
    +--
    
    370
    +-- If RequestStatus is `Canceling` or absent, then a `Reply` won't be sent.
    
    371
    +--
    
    372
    +-- The two possible transition paths:
    
    373
    +-- requestsQueue -> Fetched -> Active cancel -> gone
    
    374
    +-- requestsQueue -> Fetched -> Canceling     -> gone
    
    375
    +
    
    376
    +data RequestStatus
    
    377
    +  = Fetched        -- ^ 'Fetched' from a request queue, to pass to handler.
    
    378
    +  | Active (IO ()) -- ^ Handler set a cancel action, and has not sent a reply
    
    379
    +                   -- yet. The cancel action must not block.
    
    380
    +  | Canceling      -- ^ `Cancel` received while status is still `Fetched`.
    
    381
    +
    
    350 382
     data AsyncManager = AsyncManager
    
    351 383
       { pendingReply :: !(TVar (IntMap (TMVar (Either SomeException LB.ByteString))))
    
    352 384
         -- ^ When a request is sent, keep track that we're expecting a reply. The
    
    ... ... @@ -357,6 +389,9 @@ data AsyncManager = AsyncManager
    357 389
         -- /without/ a specific 'InboxId', queue it here. A request handler will
    
    358 390
         -- pop the next request from it. (Nothing except the 'drainPipe' thread
    
    359 391
         -- should read from the pipe). Read with @'handleAsyncReq' p get Nothing@.
    
    392
    +  , cancelStatus :: !(TVar (IntMap RequestStatus))
    
    393
    +    -- ^ When a `Cancel uq` is received, we lookup `uq` in this map and call the
    
    394
    +    -- callback if present.
    
    360 395
       , mailboxQueues :: !(TVar (IntMap (TQueue (CorrelationId, LB.ByteString))))
    
    361 396
         -- ^ Requests for a specific 'InboxId' that can be read using
    
    362 397
         -- @'handleAsyncReq' p get (Just <mailboxid>)@.
    
    ... ... @@ -389,15 +424,21 @@ type CorrelationId = Int
    389 424
     -- exclusively.
    
    390 425
     data IddMsg  = Request !CorrelationId !(Maybe InboxId) LB.ByteString
    
    391 426
                  | Reply   !CorrelationId (Either SerializableException LB.ByteString)
    
    427
    +             | Cancel  !CorrelationId -- ^ Technically a Request, but no Reply
    
    428
    +                                      -- is expected, the CorrelationId is the
    
    429
    +                                      -- one of the Request being canceled.
    
    392 430
     
    
    393 431
     instance Binary IddMsg where
    
    394 432
       put (Request i ii bs) = putWord8 0 >> put i >> put ii >> put bs
    
    395 433
       put (Reply i bs)      = putWord8 1 >> put i >> put bs
    
    434
    +  put (Cancel i)        = putWord8 2 >> put i
    
    435
    +
    
    396 436
       get = do
    
    397 437
         b <- getWord8
    
    398 438
         case b of
    
    399 439
           0 -> Request <$> get <*> get <*> get
    
    400 440
           1 -> Reply   <$> get <*> get
    
    441
    +      2 -> Cancel  <$> get
    
    401 442
           _ -> fail $ "impossible: Binary IddMsg - " ++ show b
    
    402 443
     
    
    403 444
     newAsyncManager :: Pipe -> IO AsyncManager
    
    ... ... @@ -405,6 +446,7 @@ newAsyncManager pipe = do
    405 446
       asyncFreshId  <- newIORef 0
    
    406 447
       pendingReply  <- newTVarIO IntMap.empty
    
    407 448
       requestsQueue <- newTQueueIO
    
    449
    +  cancelStatus  <- newTVarIO IntMap.empty
    
    408 450
       mailboxQueues <- newTVarIO IntMap.empty
    
    409 451
       writeQueue <- newTQueueIO
    
    410 452
     
    
    ... ... @@ -430,41 +472,85 @@ drainPipe :: Pipe -> (forall x. IO x -> IO x) -> IO ()
    430 472
     drainPipe p unmask = catch (unmask $ loop `finally` pipeCloseRead p) replyToPendings  where
    
    431 473
       loop = do
    
    432 474
         readPipe p >>= \case
    
    433
    -      -- Receive a reply to a pending notification
    
    434
    -      Just (Reply uq bs) -> do
    
    435
    -        atomically $ do
    
    436
    -          m <- readTVar (pendingReply am)
    
    437
    -          case IntMap.lookup uq m of
    
    438
    -            Nothing ->
    
    439
    -              -- the req was dropped from pending bc there was an exception, and no
    
    440
    -              -- one's listening any more. the reply is ignored
    
    441
    -              pure ()
    
    442
    -            Just wv -> do
    
    443
    -              writeTVar (pendingReply am) (IntMap.delete uq m)
    
    444
    -              -- signal matching request
    
    445
    -              putTMVar wv (bimap (toException . RemoteProcessException) id bs)
    
    446
    -        loop
    
    447
    -      -- Receive a request and queue it
    
    448
    -      Just (Request uq mbi bs) -> do
    
    449
    -        atomically $ case mbi of
    
    450
    -          Nothing ->
    
    451
    -            -- "Global" request
    
    452
    -            writeTQueue (requestsQueue am) (uq,bs)
    
    453
    -          Just ii -> do
    
    454
    -            -- "Specific mailbox" request
    
    455
    -            q <- getMailbox am ii
    
    456
    -            writeTQueue q (uq,bs)
    
    475
    +      Just msg -> do
    
    476
    +        handleMsg msg
    
    457 477
             loop
    
    458 478
           -- EOF on read handle
    
    459 479
           Nothing -> do
    
    460 480
             -- clean up pipe then exit loop
    
    461 481
             replyToPendings (toException PipeReadClosedException)
    
    462 482
             pure ()
    
    483
    +  handleMsg = \case
    
    484
    +    -- Receive a reply to a pending notification
    
    485
    +    Reply uq bs -> do
    
    486
    +      atomically $ do
    
    487
    +        m <- readTVar (pendingReply am)
    
    488
    +        case IntMap.lookup uq m of
    
    489
    +          Nothing ->
    
    490
    +            -- the req was dropped from pending bc there was an exception, and no
    
    491
    +            -- one's listening any more. the reply is ignored
    
    492
    +            pure ()
    
    493
    +          Just wv -> do
    
    494
    +            writeTVar (pendingReply am) (IntMap.delete uq m)
    
    495
    +            -- signal matching request
    
    496
    +            putTMVar wv (bimap (toException . RemoteProcessException) id bs)
    
    497
    +    -- Receive a request and queue it
    
    498
    +    Request uq mbi bs -> do
    
    499
    +      atomically $ case mbi of
    
    500
    +        Nothing ->
    
    501
    +          -- "Global" request
    
    502
    +          writeTQueue (requestsQueue am) (uq,bs)
    
    503
    +        Just ii -> do
    
    504
    +          -- "Specific mailbox" request
    
    505
    +          q <- getMailbox am ii
    
    506
    +          writeTQueue q (uq,bs)
    
    507
    +    Cancel uq -> cancelRequest am uq
    
    463 508
     
    
    464 509
       replyToPendings e = atomically $ failPendingReplies p e
    
    465 510
     
    
    466 511
       am = pipeAsyncMngr p
    
    467 512
     
    
    513
    +cancelRequest :: AsyncManager -> CorrelationId -> IO ()
    
    514
    +cancelRequest am uq = join . atomically $ do
    
    515
    +  cs <- readTVar (cancelStatus am)
    
    516
    +  case IntMap.lookup uq cs of
    
    517
    +    Nothing -> pure (pure ())
    
    518
    +    Just Fetched -> writeTVar (cancelStatus am) (IntMap.insert uq Canceling cs) >> pure (pure ())
    
    519
    +    Just (Active cancel) -> writeTVar (cancelStatus am) (IntMap.delete uq cs) >> pure cancel
    
    520
    +    Just Canceling -> pure (pure ())
    
    521
    +
    
    522
    +-- | Returns @True@ when the handler should write its 'Reply'.
    
    523
    +doneHandling :: AsyncManager -> CorrelationId -> STM Bool
    
    524
    +doneHandling am uq = do
    
    525
    +  cs <- readTVar (cancelStatus am)
    
    526
    +  case IntMap.lookup uq cs of
    
    527
    +    Nothing -> pure False
    
    528
    +    Just s -> do
    
    529
    +      writeTVar (cancelStatus am) $ IntMap.delete uq cs
    
    530
    +      case s of
    
    531
    +        Canceling -> pure False
    
    532
    +        _         -> pure True
    
    533
    +
    
    534
    +setActive :: AsyncManager -> CorrelationId -> IO () -> IO ()
    
    535
    +setActive am uq cancel = join . atomically $ do
    
    536
    +  cs <- readTVar (cancelStatus am)
    
    537
    +  case IntMap.lookup uq cs of
    
    538
    +    Nothing -> pure (pure ()) -- reply already received.
    
    539
    +    Just Fetched -> writeTVar (cancelStatus am) (IntMap.insert uq (Active cancel) cs) >> pure (pure ())
    
    540
    +    Just (Active _) -> writeTVar (cancelStatus am) (IntMap.insert uq (Active cancel) cs) >> pure (pure ())
    
    541
    +    Just Canceling -> pure cancel
    
    542
    +
    
    543
    +fetchedRequest :: AsyncManager -> CorrelationId -> STM ()
    
    544
    +fetchedRequest am uq = do
    
    545
    +  cs <- readTVar (cancelStatus am)
    
    546
    +  case IntMap.lookup uq cs of
    
    547
    +    Nothing -> writeTVar (cancelStatus am) (IntMap.insert uq Fetched cs)
    
    548
    +    Just Fetched -> error "impossible"
    
    549
    +    Just (Active _) -> error "impossible"
    
    550
    +    Just Canceling -> error "impossible"
    
    551
    +
    
    552
    +
    
    553
    +
    
    468 554
     {-
    
    469 555
     Note [Exceptions during serialization]
    
    470 556
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    ... ... @@ -553,7 +639,7 @@ runWriteReply (WriteReply r) = r
    553 639
     (|#>) (IO fs) s = fs s
    
    554 640
     
    
    555 641
     (%>>=) :: (IO a) -> (a -> IO b) %1 -> IO b
    
    556
    -(%>>=) = \ m f -> IO (\s0 ->
    
    642
    +(%>>=) = \ m -> \ f -> IO (\s0 ->
    
    557 643
             case m |#> s0 of -- unIO for linearity checking
    
    558 644
               (# s1, x #) -> f x |#> s1)
    
    559 645
     infixl 1 %>>=
    

  • libraries/ghci/GHCi/Server.hs
    ... ... @@ -66,13 +66,13 @@ servWithCustom
    66 66
       -> IO ()
    
    67 67
     servWithCustom verbose hook pipe restore customHandler = do
    
    68 68
       ref <- newIORef False
    
    69
    -  withThreadManager $ \mngr ->
    
    69
    +  withThreadManager $ \mngr -> do
    
    70 70
         loop ref mngr `finally` closePipe pipe
    
    71 71
      where
    
    72 72
       loop doneRef mngr = do
    
    73 73
         when verbose $ trace "reading pipe..."
    
    74 74
     
    
    75
    -    handleRemoteCall pipe hook $ \wr (msg :: Message a) ->
    
    75
    +    handleRemoteCall pipe hook $ \wr setCancel (msg :: Message a) ->
    
    76 76
     
    
    77 77
           discardCtrlC                                 %>>= \_ ->
    
    78 78
           when verbose (trace ("msg: " ++ (show msg))) %>>= \_ ->
    
    ... ... @@ -81,10 +81,14 @@ servWithCustom verbose hook pipe restore customHandler = do
    81 81
             CustomMessage tag payload -> handleCustom tag payload %>>= writeReply wr
    
    82 82
             Shutdown                  -> writeIORef doneRef True >>
    
    83 83
                                          reply ()                 %>>= writeReply wr
    
    84
    -        RunTH ii st q ty loc      -> spawnFinally mngr         (runWriteReply wr)
    
    84
    +        RunTH ii st q ty loc      -> unsafe_linear restore
    
    85
    +                                     (spawnFinally mngr         (runWriteReply wr)
    
    85 86
                                          (wrapRunTH ii (runTH pipe ii st q ty loc))
    
    86
    -        RunModFinalizers ii st qs -> spawnFinally mngr         (runWriteReply wr)
    
    87
    +                                     (setCancel . void . forkIO . killThread))
    
    88
    +        RunModFinalizers ii st qs -> unsafe_linear restore
    
    89
    +                                     (spawnFinally mngr         (runWriteReply wr)
    
    87 90
                                          (wrapRunTH ii (runModFinalizerRefs pipe ii st qs))
    
    91
    +                                     (setCancel . void . forkIO . killThread))
    
    88 92
             _other                    -> run msg >>= reply        %>>= writeReply wr
    
    89 93
     
    
    90 94
         isDone <- readIORef doneRef
    

  • libraries/ghci/GHCi/Server/Threads.hs
    ... ... @@ -41,8 +41,8 @@ withThreadManager k = do
    41 41
     
    
    42 42
     -- | Linear and resource-safe because 'ThreadManager' is guaranteed to wait or
    
    43 43
     -- cancel all threads.
    
    44
    -spawnFinally :: ThreadManager -> (Either SomeException a -> IO ()) %1 -> IO a -> IO ()
    
    45
    -spawnFinally ThreadManager{..} = unsafe_linear \fin -> \action -> void $
    
    44
    +spawnFinally :: ThreadManager -> (Either SomeException a -> IO ()) %1 -> IO a -> (ThreadId -> IO b) -> IO b
    
    45
    +spawnFinally ThreadManager{..} = unsafe_linear \fin -> \action -> \k ->
    
    46 46
       forkFinally
    
    47 47
         ( mask $ \unmask -> do
    
    48 48
             tid <- myThreadId
    
    ... ... @@ -57,6 +57,7 @@ spawnFinally ThreadManager{..} = unsafe_linear \fin -> \action -> void $
    57 57
               else throwIO ThreadKilled
    
    58 58
         )
    
    59 59
         ( \ea -> fin ea )
    
    60
    +    >>= k
    
    60 61
     
    
    61 62
     unsafe_linear :: (a -> b) %1 -> (a %1 -> b)
    
    62 63
     unsafe_linear = Unsafe.unsafeCoerce (Unsafe.unsafeCoerce :: (a -> b) -> (a %1 -> b))