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

Commits:

3 changed files:

Changes:

  • compiler/GHC/Runtime/Interpreter.hs
    ... ... @@ -654,6 +654,8 @@ stopInterp interp = case interpInstance interp of
    654 654
                 if isJust ex
    
    655 655
                    then pure ()
    
    656 656
                    else sendMessage i Shutdown
    
    657
    +            -- We asked the server to shutdown, so we will also shutdown any communication.
    
    658
    +            closePipe (interpPipe (instProcess i))
    
    657 659
                 pure InterpPending
    
    658 660
     
    
    659 661
     -- -----------------------------------------------------------------------------
    

  • libraries/ghci/GHCi/Message/Async.hs
    ... ... @@ -70,8 +70,13 @@ data Pipe = Pipe
    70 70
       , pipeLeftovers   :: !(IORef (Maybe ByteString))
    
    71 71
       , pipeLock        :: !(MVar ()) -- ^ Lock to prevent concurrent writes to the stream
    
    72 72
       , pipeAsyncMngr   :: !AsyncManager
    
    73
    -  , pipeIsClosed    :: !(TVar Bool)
    
    74
    -    -- ^ After the pipe is closed, requests can no longer be received nor sent
    
    73
    +  , pipeCloseRead   :: !(IO ())
    
    74
    +  -- ^ closes read end of the pipe
    
    75
    +  , pipeCloseWrite  :: !(IO ())
    
    76
    +  -- ^ closes write end of the pipe
    
    77
    +  , pipeReadClosed  :: !(TVar Bool)
    
    78
    +    -- ^ After the pipe is closed, messages can no longer be received from the
    
    79
    +    -- other end, but the 'AsyncManager' queues might not be empty yet.
    
    75 80
       }
    
    76 81
     
    
    77 82
     -- | Make a 'Pipe' from a 'Handle' to read and a 'Handle' to write.
    
    ... ... @@ -82,43 +87,49 @@ mkPipeFromHandles pipeRead pipeWrite = do
    82 87
           putAll b = do
    
    83 88
             B.hPutBuilder pipeWrite b
    
    84 89
             hFlush pipeWrite
    
    85
    -  mkPipeFromContinuations getSome putAll
    
    90
    +  mkPipeFromContinuations getSome putAll (hClose pipeRead) (hClose pipeWrite)
    
    86 91
     
    
    87 92
     -- | 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
    
    93
    +-- The pipe should be closed with 'closePipe', which will call 'pipeCloseRead' and 'pipeCloseWrite'.
    
    94
    +mkPipeFromContinuations :: IO ByteString -> (B.Builder -> IO ())
    
    95
    +                        -> IO ()
    
    96
    +                        -- ^ pipeCloseRead
    
    97
    +                        -> IO ()
    
    98
    +                        -- ^ pipeCloseWrite
    
    99
    +                        -> IO Pipe
    
    100
    +mkPipeFromContinuations getSome putAll pipeCloseRead pipeCloseWrite = mdo
    
    91 101
       pipeLeftovers   <- newIORef Nothing
    
    92 102
       pipeLock        <- newMVar ()
    
    93
    -  pipeIsClosed    <- newTVarIO False
    
    103
    +  pipeReadClosed  <- newTVarIO False
    
    94 104
       mdo
    
    95 105
         pipeAsyncMngr <- newAsyncManager p
    
    96 106
         let p = Pipe { .. }
    
    97 107
         pure p
    
    98 108
     
    
    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.
    
    109
    +-- | Cleanly close our ends of the Pipe, terminating the async manager's
    
    110
    +-- drain-read-end thread. Also calls 'pipeCloseRead' and 'pipeCloseWrite', in that order.
    
    101 111
     --
    
    102
    --- If the pipe was created using 'mkPipeFromHandles', the handles must only by
    
    103
    --- closed after calling 'closePipe'.
    
    112
    +-- If the pipe was created using 'mkPipeFromHandles', the handles are also closed.
    
    104 113
     --
    
    105 114
     -- The requests we've sent that are pending are all canceled with
    
    106 115
     -- 'PipeClosedException' by ourselves.
    
    107 116
     --
    
    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.
    
    117
    +-- No special notification is sent to the remote process, a message to that
    
    118
    +-- effect should be sent ahead of time if one is needed (see 'Shutdown').
    
    111 119
     closePipe :: Pipe -> IO ()
    
    112 120
     closePipe Pipe{..} = do
    
    113 121
     
    
    114
    -  isAlreadyClosed <- atomically $ swapTVar pipeIsClosed True
    
    122
    +  readAlreadyClosed <- atomically $ swapTVar pipeReadClosed True
    
    115 123
     
    
    116
    -  unless isAlreadyClosed $ do
    
    124
    +  -- this triggers pipeCloseRead
    
    125
    +  unless readAlreadyClosed $ do
    
    117 126
     
    
    118 127
         -- releases handle lock taken by reading, and cleans up by replying to every
    
    119 128
         -- pending request with 'PipeClosedException'.
    
    120 129
         throwTo (asyncDrainTid pipeAsyncMngr) PipeClosedException
    
    121 130
     
    
    131
    +  pipeCloseWrite
    
    132
    +
    
    122 133
     -- | We've closed the pipe on our end and we terminate all pending requests with this.
    
    123 134
     data PipeClosedException = PipeClosedException deriving Show
    
    124 135
     instance Exception PipeClosedException
    
    ... ... @@ -186,7 +197,8 @@ remoteAsyncReq pipe msg ii = do
    186 197
       wv <- newEmptyTMVarIO
    
    187 198
     
    
    188 199
       isClosed <- atomically $ do
    
    189
    -    isClosed <- readTVar (pipeIsClosed pipe)
    
    200
    +    isClosed <- readTVar (pipeReadClosed pipe)
    
    201
    +                -- don't send the req if we won't get a reply.
    
    190 202
         if isClosed
    
    191 203
           then pure True
    
    192 204
           else do
    
    ... ... @@ -223,15 +235,17 @@ handleAsyncReq pipe getit mii k = mask $ \restore -> do
    223 235
     
    
    224 236
       -- Block waiting for request
    
    225 237
       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
    
    238
    +    isClosed <- readTVar (pipeReadClosed pipe)
    
    239
    +    taken <- case mii of
    
    240
    +      Nothing -> tryReadTQueue  $  requestsQueue am
    
    241
    +      Just ii -> tryReadTQueue =<< getMailbox am ii
    
    242
    +    case taken of
    
    243
    +      Just req -> pure (Just req)
    
    244
    +      Nothing  | isClosed  -> pure Nothing -- empty and closed
    
    245
    +               | otherwise -> retry -- wait for msg
    
    232 246
     
    
    233 247
       case mbreq of
    
    234
    -    Nothing -> throwIO PipeClosedException
    
    248
    +    Nothing -> throwIO PipeClosedException -- can't expect reqs
    
    235 249
         Just (uq, bs) -> do
    
    236 250
           let reply p = do
    
    237 251
                 writePipe pipe (Reply uq (bimap toSerializableException runPut p))
    
    ... ... @@ -368,8 +382,10 @@ newAsyncManager pipe = do
    368 382
     -- In pratice, we'll have both a thread draining the read end of the host's
    
    369 383
     -- 'Pipe' and a thread draining the read end of the interpreter's 'Pipe', since
    
    370 384
     -- both processes construct a 'Pipe' and send and receive messages
    
    385
    +--
    
    386
    +-- Closes the Pipe read end when done reading
    
    371 387
     drainPipe :: Pipe -> (forall x. IO x -> IO x) -> IO ()
    
    372
    -drainPipe p unmask = catch (unmask loop) replyToPendings where
    
    388
    +drainPipe p unmask = catch (unmask $ loop `finally` pipeCloseRead p) replyToPendings  where
    
    373 389
       loop = do
    
    374 390
         readPipe p >>= \case
    
    375 391
           -- Receive a reply to a pending notification
    
    ... ... @@ -404,7 +420,9 @@ drainPipe p unmask = catch (unmask loop) replyToPendings where
    404 420
             pure ()
    
    405 421
     
    
    406 422
       replyToPendings e = atomically $ do
    
    407
    -    writeTVar (pipeIsClosed p) True
    
    423
    +    -- reqs are only added if pipe not closed;
    
    424
    +    -- thus we close and take all pending atomically.
    
    425
    +    writeTVar (pipeReadClosed p) True
    
    408 426
         pendings <- swapTVar (pendingReply am) IntMap.empty -- take pending
    
    409 427
         mapM_ (\p -> putTMVar p (Left e {-host exception-})) pendings
    
    410 428
     
    

  • libraries/ghci/GHCi/Server.hs
    ... ... @@ -78,6 +78,9 @@ servWithCustom verbose hook pipe restore customHandler = newIORef False >>= loop
    78 78
             RunModFinalizers ii st qrefs -> wrapRunTH ii $ runModFinalizerRefs pipe ii st qrefs
    
    79 79
             _other -> run msg >>= reply
    
    80 80
     
    
    81
    +    -- closePipe here will wait until every active pipeWrite pipe exits, but
    
    82
    +    -- they should never block for long, as the host process is either reading
    
    83
    +    -- or has called pipeCloseRead.
    
    81 84
         isDone <- readIORef doneRef
    
    82 85
         if isDone then closePipe pipe
    
    83 86
                   else loop doneRef
    
    ... ... @@ -184,7 +187,7 @@ defaultServerWithCustom customHandler = do
    184 187
     #if defined(wasm32_HOST_ARCH)
    
    185 188
       -- See Note [wasm ghci signal handlers] for details
    
    186 189
       installSignalHandlers $ js_register_signal_handler cb_sig
    
    187
    -  pipe <- mkPipeFromContinuations (recv_buf cb_recv) (send_buf cb_send)
    
    190
    +  pipe <- mkPipeFromContinuations (recv_buf cb_recv) (send_buf cb_send) (pure ()) (pure ())
    
    188 191
     #else
    
    189 192
       when verbose $
    
    190 193
         printf "GHC iserv starting (in: %s; out: %s)\n" (show inh) (show outh)