grapesy-1.0.0: test-stress/Test/Stress/Client.hs
module Test.Stress.Client
( client
) where
import Control.Concurrent
import Control.Concurrent.Async
import Control.Exception
import Control.Monad
import Data.ByteString.Lazy.Char8 qualified as BS.Char8
import GHC.IO.Exception
import Network.HTTP2.Client (HTTP2Error(..))
import Network.Socket
import Network.TLS (TLSException(..))
import Network.GRPC.Client hiding (Call)
import Network.GRPC.Common
import Network.GRPC.Common.Compression (Compression)
import Network.GRPC.Common.Compression qualified as Compr
import Proto.API.Trivial
import Test.Stress.Cmdline
import Test.Stress.Common
-------------------------------------------------------------------------------
-- Top-level
-------------------------------------------------------------------------------
client ::
Bool
-> Maybe ServerValidation
-> PortNumber
-> Maybe Compression
-> [Connect]
-> IO ()
client v mServerValidation serverPort compr =
mapM_ $ runConnect v mServerValidation serverPort compr
runConnect ::
Bool
-> Maybe ServerValidation
-> PortNumber
-> Maybe Compression
-> Connect
-> IO ()
runConnect v mServerValidation serverPort compr Connect{..} = do
say' v serverPort $
"running calls " ++ show connectCalls ++
case connectExec of
Sequential -> " in sequence"
Concurrent -> " concurrently"
mapF (runCalls v mServerValidation serverPort compr callNum) $
zip [1..] $ replicate connectNum connectCalls
where
mapF =
case connectExec of
Sequential -> mapM_
Concurrent -> mapConcurrently_
runCalls ::
Bool
-> Maybe ServerValidation
-> PortNumber
-> Maybe Compression
-> Int
-> (Int, [Call])
-> IO ()
runCalls v mServerValidation serverPort compr callNum (connNum, calls) = do
say' v serverPort msg
let connParams = def {
connCompression = maybe Compr.none Compr.insist compr
, connHTTP2Settings = def {
http2TcpAbortiveClose = True
}
, connReconnectPolicy =
exponentialBackoff
(\d -> do
say' v serverPort $ "Reconnecting after " ++ show d ++ "μs"
threadDelay d
)
1
(0.1, 0.1)
maxBound
}
allowCertainFailures $
withConnection connParams server $ \conn ->
replicateM_ callNum $ mapM_ (runCall v serverPort conn) calls
where
addr :: Address
addr = Address {
addressHost = "127.0.0.1"
, addressPort = serverPort
, addressAuthority = Nothing
}
server :: Server
server =
case mServerValidation of
Just serverValidation ->
ServerSecure serverValidation SslKeyLogNone addr
Nothing ->
ServerInsecure addr
allowCertainFailures :: IO () -> IO ()
allowCertainFailures =
handle $ \case
e | Just ServerDisconnected{} <- fromException e ->
say' v serverPort $ "server disconnected: " ++ show e
| Just IOError{} <- fromException e ->
say' v serverPort "failed to connect"
| Just ConnectionIsTimeout <- fromException e ->
say' v serverPort "got ConnectionIsTimeout"
| Just BadThingHappen{} <- fromException e ->
say' v serverPort "got BadThingHappen"
| Just HandshakeFailed{} <- fromException e ->
say' v serverPort "got HandshakeFailed"
| Just BlockedIndefinitelyOnSTM{} <- fromException e ->
say' v serverPort "got BlockedIndefinitelyOnSTM"
| otherwise -> do
say' v serverPort $ "got exception: " ++ displayException e
throwIO e
msg :: String
msg =
"opening connection " ++ show connNum ++ " to "
++ case mServerValidation of
Just _ -> "secure "
Nothing -> "insecure "
++ "server at port " ++ show serverPort ++ " with compression "
++ maybe "none" (show . Compr.compressionId) compr
runCall :: Bool -> PortNumber -> Connection -> Call -> IO ()
runCall v p conn =
\case
NonStreaming ->
nonStreaming v p conn
ClientStreaming n ->
clientStreaming v p conn n
ServerStreaming n ->
serverStreaming v p conn n
BiDiStreaming n ->
bidiStreaming v p conn n
-------------------------------------------------------------------------------
-- Specific RPCs
-------------------------------------------------------------------------------
-- | One non-streaming, round-trip call
nonStreaming :: Bool -> PortNumber -> Connection -> IO ()
nonStreaming v p conn = do
say' v p "initiating non-streaming call"
withRPC conn def (Proxy @(Trivial' "non-streaming")) $ \call -> do
sendFinalInput call =<< randomMsg
void $ recvFinalOutput call
say' v p "received final output for non-streaming call"
-- | Client streaming
--
-- Client sends the server @N@, followed by @N@ messages.
clientStreaming :: Bool -> PortNumber -> Connection -> Int -> IO ()
clientStreaming v p conn n = do
say' v p "initiating client streaming call"
withRPC conn def (Proxy @(Trivial' "client-streaming")) $ \call -> do
say' v p $ "sending " ++ show n ++ " messages"
sendNextInput call $ BS.Char8.pack (show n)
msg <- randomMsg
forM_ [1 .. n-1] $ \_ ->
void $ sendNextInput call msg
sendFinalInput call msg
void $ recvFinalOutput call
say' v p "received final output for client streaming call"
-- | Server streaming
--
-- Client sends the server @N@, then receives @N@ messages from server.
serverStreaming :: Bool -> PortNumber -> Connection -> Int -> IO ()
serverStreaming v p conn n = do
say' v p "initiating server streaming call"
withRPC conn def (Proxy @(Trivial' "server-streaming")) $ \call -> do
say' v p $ "receiving " ++ show n ++ " messages"
sendFinalInput call $ BS.Char8.pack (show n)
forM_ [1 .. n-1] $ \_ -> void $ recvNextOutput call
void $ recvFinalOutput call
say' v p "received final output for server streaming call"
-- | Bidirectional streaming
--
-- Client sends the server @N@, then alternates sending and receiving @N*2@
-- total messages.
bidiStreaming :: Bool -> PortNumber -> Connection -> Int -> IO ()
bidiStreaming v p conn n = do
say' v p "initiating bidi streaming call"
withRPC conn def (Proxy @(Trivial' "bidi-streaming")) $ \call -> do
say' v p $ "sending and receiving " ++ show n ++ " messages"
sendNextInput call $ BS.Char8.pack (show n)
msg <- randomMsg
forM_ [1 .. n-1] $ \_ -> do
sendNextInput call msg
void $ recvNextOutput call
sendFinalInput call msg
void $ recvFinalOutput call
say' v p "sent and received final messages for bidi streaming call"
-------------------------------------------------------------------------------
-- Utils
-------------------------------------------------------------------------------
say' :: Bool -> PortNumber -> String -> IO ()
say' v p msg =
say v $ "(client " ++ show p ++ ") " ++ msg