packages feed

grapesy-1.0.0: test-stress/Test/Stress/Server.hs

module Test.Stress.Server
  ( server
  ) where

import Control.Exception
import Control.Monad
import Data.ByteString.Lazy.Char8 qualified as BS.Char8
import Data.IORef
import Data.Text qualified as Text
import System.Exit (exitFailure)

import Network.GRPC.Common
import Network.GRPC.Server
import Network.GRPC.Server.Run
import Proto.API.Trivial

import Test.Stress.Common

{-------------------------------------------------------------------------------
  Top-level
-------------------------------------------------------------------------------}

server :: Bool -> ServerConfig -> IO ()
server v config = handle swallowInterruptOrKilled $ do
    idRef <- newIORef "unknown"
    s <- mkGrpcServer params (handlers v idRef)
    forkServer def config s $ \runningServer -> do
      p <- getServerPort runningServer
      writeIORef idRef $ show p
      say v $ "server running on port " ++ show p
      waitServer runningServer
  where
    swallowInterruptOrKilled :: SomeException -> IO ()
    swallowInterruptOrKilled e
        | Just UserInterrupt <- asyncExceptionFromException e
        = say v "server received user interrupt, exiting gracefully"
        | Just ThreadKilled <- asyncExceptionFromException e
        = say v "server thread killed, exiting gracefully"
        | otherwise
        = do
          putStrLn $ "got unexpected server exception: " ++ show e
          exitFailure

    params :: ServerParams
    params = def {
          -- Show exception including backtrace
          serverExceptionToClient = \e ->
            return $ Just (Text.pack $ displayException e)
        }

{-------------------------------------------------------------------------------
  Handlers
-------------------------------------------------------------------------------}

handlers :: Bool -> IORef String -> [SomeRpcHandler IO]
handlers v idRef = [
      someRpcHandler @(Trivial' "non-streaming") $
        mkRpcHandler $ clientDisconnectOkay . nonStreaming
    , someRpcHandler @(Trivial' "server-streaming") $
        mkRpcHandler $ clientDisconnectOkay . serverStreaming
    , someRpcHandler @(Trivial' "client-streaming") $
        mkRpcHandler $ clientDisconnectOkay . clientStreaming
    , someRpcHandler @(Trivial' "bidi-streaming") $
        mkRpcHandler $ clientDisconnectOkay . bidiStreaming
    ]
  where
    -- Single message from client, single message from server
    nonStreaming :: Call (Trivial' "non-streaming") -> IO ()
    nonStreaming call = do
        say' "handling non-streaming call"
        msg <- recvFinalInput call
        sendFinalOutput call $ (msg, NoMetadata)
        say' "sent final output for non-streaming call"

    -- Client sends message containing number N, then client streams N messages
    -- to server
    clientStreaming :: Call (Trivial' "client-streaming") -> IO ()
    clientStreaming call = do
        say' "handling client streaming call"
        inp <- read @Int . BS.Char8.unpack <$> recvNextInput call
        say' $ "receiving " ++ show inp ++ " messages"
        forM_ [1 .. inp-1] $ \_ -> void $ recvNextInput call
        msg <- recvFinalInput call
        sendFinalOutput call (msg, NoMetadata)
        say' $ "sent final output for client streaming call"

    -- Client sends message containing number N, then server streams N messages
    -- to client
    serverStreaming :: Call (Trivial' "server-streaming") -> IO ()
    serverStreaming call = do
        say' "handling server streaming call"
        inp <- read @Int . BS.Char8.unpack <$> recvNextInput call
        say' $ "sending " ++ show inp ++ " messages"
        msg <- randomMsg
        forM_ [1 .. inp-1] $ \_ -> sendNextOutput call msg
        sendFinalOutput call (msg, NoMetadata)
        say' $ "sent final output for server streaming call"

    -- Client sends message containing number N, then client and server send N*2
    -- total messages back and forth.
    bidiStreaming :: Call (Trivial' "bidi-streaming") -> IO ()
    bidiStreaming call = do
        say' "handling bidi streaming call"
        inp <- read @Int . BS.Char8.unpack <$> recvNextInput call
        say' $ "sending and receiving " ++ show inp ++ " messages"
        msg <- randomMsg
        forM_ [1 .. inp-1] $ \_ -> do
          void $ recvNextInput call
          sendNextOutput call msg
        void $ recvFinalInput call
        sendFinalOutput call (msg, NoMetadata)
        say' $ "sent and received final messages for bidi streaming call"

    clientDisconnectOkay :: IO () -> IO ()
    clientDisconnectOkay =
        handle $ \ClientDisconnected{} ->
          say' "client disconnected"

    say' :: String -> IO ()
    say' msg = do
        sid <- readIORef idRef
        say v $ "(server " ++ sid ++ ") " ++ msg