on master help sign in
add type-specific request handlers using GADTs

update streamly commit add support for async requests add request timeout support compilation checkpoint

commit88a59ff21f864c6ca1b051910c570d29d67862a9 authorAgentM <agentm@themactionfaction.com> dateThu 17 Dec 2020, 00:21 (6 years ago) parent26ff225
5 files changed+239−164
bench/Basic.hs+25−21 cabal.project+1−1 src/Network/RPC/Curryer/Client.hs+54−24 src/Network/RPC/Curryer/Server.hs+82−33 test/Curryer/Test/Basic.hs+77−85
bench/Basic.hs+25−21
@@ -15,10 +15,10 @@ main :: IO ()
1515main = do
1616 --start server shared across benchmarks- gather stats on round-trip requests-responses
1717 portReadyMVar <- newEmptyMVar
18− server <- async (serve (pure Nothing) benchmarkServerMessageHandler localHostAddr 0 (Just portReadyMVar))
18+ server <- async (serve benchmarkServerMessageHandlers localHostAddr 0 (Just portReadyMVar))
1919 --wait for server to be ready
2020 (SockAddrInet port _) <- takeMVar portReadyMVar
21− clientConn <- connect (const (pure ())) localHostAddr port
21+ clientConn <- connect localHostAddr port
2222
2323 _ <- syncWaitRequest clientConn 100
2424 let syncWait ms = nfIO (syncWaitRequest clientConn ms)
@@ -30,10 +30,10 @@ main = do
3030 defaultMain [bgroup "wait sync" [bench "0 ms" (syncWait 0),
3131 bench "10 ms" (syncWait 10),
3232 bench "100 ms" (syncWait 100)],
33− bgroup "bytestring roundtrip" [bench "0 bytes" (syncByteString bs0),
34− bench "100 bytes" (syncByteString bs100),
35− bench "1000 bytes" (syncByteString bs1000),
36− bench "1000000 bytes" (syncByteString bs1M)
33+ bgroup "bytestring roundtrip" [bench "0 bytes" (syncByteString bs0)
34+ ,bench "100 bytes" (syncByteString bs100)
35+ ,bench "1000 bytes" (syncByteString bs1000)
36+ ,bench "1000000 bytes" (syncByteString bs1M)
3737 ]]
3838
3939 --TODO: bench for > PIPE_BUF bytes
@@ -41,34 +41,38 @@ main = do
4141 close clientConn
4242 cancel server
4343
44−data BenchRequest = WaitMillisecondsReq Int --ask the server to respond in X milliseconds
45− | WaitByteStringReq BS.ByteString --ask the server to round trip arbitrary bytes
44+data WaitMillisecondsReq = WaitMillisecondsReq Int --ask the server to respond in X milliseconds
4645 deriving (Generic, Show)
47− deriving Serialise via WineryVariant BenchRequest
46+ deriving Serialise via WineryVariant WaitMillisecondsReq
4847
49−data BenchResponse = WaitMillisecondsResp
50− | WaitByteStringResp BS.ByteString
48+data WaitByteStringReq = WaitByteStringReq BS.ByteString
49+ deriving (Generic, Show)
50+ deriving Serialise via WineryVariant WaitByteStringReq
51+
52+data WaitMillisecondsResp = WaitMillisecondsResp
5153 deriving (Generic, Show, Eq)
52− deriving Serialise via WineryVariant BenchResponse
54+ deriving Serialise via WineryVariant WaitMillisecondsResp
5355
54−benchmarkServerMessageHandler :: BenchRequest -> IO (HandlerResponse BenchResponse)
55−benchmarkServerMessageHandler (WaitMillisecondsReq ms) = do
56− threadDelay (1000 * ms)
57− pure (HandlerResponse WaitMillisecondsResp)
58−benchmarkServerMessageHandler (WaitByteStringReq bs) = pure (HandlerResponse (WaitByteStringResp bs))
56+benchmarkServerMessageHandlers :: MessageHandlers
57+benchmarkServerMessageHandlers =
58+ [RequestHandler $ \(WaitMillisecondsReq ms) -> do
59+ threadDelay (1000 * ms)
60+ pure WaitMillisecondsResp,
61+ RequestHandler $ \(WaitByteStringReq bs) ->
62+ pure bs
63+ ]
5964
6065--perform 10000 small synchronous requests
61−syncWaitRequest :: Connection BenchResponse -> Int -> IO Int
66+syncWaitRequest :: Connection -> Int -> IO Int
6267syncWaitRequest conn ms = do
6368 ret <- call conn (WaitMillisecondsReq ms)
6469 when (ret /= Right WaitMillisecondsResp) (error "ret failed")
6570 pure ms
6671
67−syncByteStringRequest :: Connection BenchResponse -> BS.ByteString -> IO BS.ByteString
72+syncByteStringRequest :: Connection -> BS.ByteString -> IO BS.ByteString
6873syncByteStringRequest conn bs = do
6974 ret <- call conn (WaitByteStringReq bs)
7075 case ret of
71− Right (WaitByteStringResp bs') -> pure bs'
72− Right _ -> error "invalid resp"
76+ Right bs' -> pure bs'
7377 Left err -> error (show err)
7478
cabal.project+1−1
@@ -3,4 +3,4 @@ packages: .
33source-repository-package
44 type: git
55 location: https://github.com/composewell/streamly.git
6− tag: e218fb9d8f4263fd69d32a2ff318bac0e179c642
6+ tag: ac3af8749194f1788704dda8667d0b3807075cc2
src/Network/RPC/Curryer/Client.hs+54−24
@@ -1,4 +1,4 @@
1−{-# LANGUAGE TypeApplications, RankNTypes, ScopedTypeVariables #-}
1+{-# LANGUAGE TypeApplications, RankNTypes, ScopedTypeVariables, GADTs #-}
22module Network.RPC.Curryer.Client where
33import Network.RPC.Curryer.Server
44import Network.Socket as Socket
@@ -11,24 +11,31 @@ import Control.Concurrent.MVar
1111import GHC.Conc
1212import Data.Time.Clock
1313import System.Timeout
14+import Control.Monad
1415
1516type SyncMap = STMMap.Map UUID (MVar (Either ConnectionError BinaryMessage), UTCTime)
1617
1718-- the request map holds
1819data Connection = Connection { _conn_sockLock :: Locking Socket,
19− _conn_asyncThread :: Async (),
20− _conn_syncmap :: SyncMap
21− }
20+ _conn_asyncThread :: Async (),
21+ _conn_syncmap :: SyncMap
22+ }
23+
24+-- function handlers run on the client, triggered by the server- useful for asynchronous callbacks
25+data ClientAsyncRequestHandler where
26+ ClientAsyncRequestHandler :: forall a. Serialise a => (a -> IO ()) -> ClientAsyncRequestHandler
27+
28+type ClientAsyncRequestHandlers = [ClientAsyncRequestHandler]
2229
2330connect ::
24− --AsyncMessageHandler ->
25− HostAddr ->
26− PortNumber ->
27− IO Connection
28−connect hostAddr portNum = do
31+ ClientAsyncRequestHandlers ->
32+ HostAddr ->
33+ PortNumber ->
34+ IO Connection
35+connect asyncHandlers hostAddr portNum = do
2936 sock <- TCP.connect hostAddr portNum
3037 syncmap <- STMMap.newIO
31− asyncThread <- async (clientAsync sock syncmap)
38+ asyncThread <- async (clientAsync sock syncmap asyncHandlers)
3239 sockLock <- newLock sock
3340 pure (Connection {
3441 _conn_sockLock = sockLock,
@@ -46,20 +53,30 @@ close conn = do
4653clientAsync ::
4754 Socket ->
4855 SyncMap ->
49− --AsyncMessageHandler ->
56+ ClientAsyncRequestHandlers ->
5057 IO ()
51−clientAsync sock syncmap = do
58+clientAsync sock syncmap asyncHandlers = do
5259 lsock <- newLock sock
53− drainSocketMessages sock (clientEnvelopeHandler lsock syncmap)
60+ drainSocketMessages sock (clientEnvelopeHandler asyncHandlers lsock syncmap)
5461
55−--handles envelope responses from server
62+--handles envelope responses from server- timeout from server ignored- perhaps that's proper for trusted servers
5663clientEnvelopeHandler ::
57− Locking Socket
64+ ClientAsyncRequestHandlers
65+ -> Locking Socket
5866 -> SyncMap
5967 -> Envelope
6068 -> IO ()
61−clientEnvelopeHandler _ _ (Envelope _ RequestMessage _ _) = error "client received request"
62−clientEnvelopeHandler _ syncMap (Envelope _ ResponseMessage msgId binaryMessage) = do
69+clientEnvelopeHandler handlers _ _ envelope@(Envelope _ (RequestMessage _) _ _) = do
70+ --should this run off on another green thread?
71+ let firstMatcher Nothing (ClientAsyncRequestHandler dispatchf) = do
72+ case matchEnvelope envelope dispatchf of
73+ Nothing -> pure Nothing
74+ Just (dispatchf', decoded) -> do
75+ dispatchf' decoded
76+ pure (Just ())
77+ firstMatcher acc _ = pure acc
78+ foldM_ firstMatcher Nothing handlers
79+clientEnvelopeHandler _ _ syncMap (Envelope _ ResponseMessage msgId binaryMessage) = do
6380 --find a matching response item
6481 match <- atomically $ do
6582 val' <- STMMap.lookup msgId syncMap
@@ -69,7 +86,15 @@ clientEnvelopeHandler _ syncMap (Envelope _ ResponseMessage msgId binaryMessage)
6986 Nothing -> error ("dropping unrequested response " <> show msgId)
7087 Just (mVar, _) ->
7188 putMVar mVar (Right binaryMessage)
72−
89+clientEnvelopeHandler _ _ syncMap (Envelope _ TimeoutResponseMessage msgId _) = do
90+ match <- atomically $ do
91+ val' <- STMMap.lookup msgId syncMap
92+ STMMap.delete msgId syncMap
93+ pure val'
94+ case match of
95+ Nothing -> error ("dropping unrequested timeout response " <> show msgId)
96+ Just (mVar, _) ->
97+ putMVar mVar (Left TimeoutError)
7398
7499call :: (Serialise request, Serialise response) => Connection -> request -> IO (Either ConnectionError response)
75100call = callTimeout Nothing
@@ -79,7 +104,12 @@ callTimeout :: (Serialise request, Serialise response) => Maybe Int -> Connectio
79104callTimeout mTimeout conn msg = do
80105 requestID <- UUID <$> UUIDBase.nextRandom
81106 let mVarMap = _conn_syncmap conn
82− envelope = Envelope fprint RequestMessage requestID (serialise msg)
107+ timeoutms = case mTimeout of
108+ Nothing -> 0
109+ Just tm | tm < 0 -> 0
110+ Just tm -> fromIntegral tm
111+
112+ envelope = Envelope fprint (RequestMessage timeoutms) requestID (serialise msg)
83113 fprint = fingerprint msg
84114 -- setup mvar to wait for response
85115 responseMVar <- newEmptyMVar
@@ -102,11 +132,11 @@ callTimeout mTimeout conn msg = do
102132 case deserialise binmsg of
103133 Left err -> error ("deserialise client error " <> show err)
104134 Right m -> pure (Right m)
105−{-
106−asyncCall :: (Serialise request, Serialise response) => Connection response -> request -> IO (Either ConnectionError ())
135+
136+asyncCall :: Serialise request => Connection -> request -> IO (Either ConnectionError ())
107137asyncCall conn msg = do
108138 requestID <- UUID <$> UUIDBase.nextRandom
109− sendMessage (AsyncRequest requestID msg) (_conn_sockLock conn)
139+ let envelope = Envelope fprint (RequestMessage 0) requestID (serialise msg)
140+ fprint = fingerprint msg
141+ sendEnvelope envelope (_conn_sockLock conn)
110142 pure (Right ())
111−
112−-}
src/Network/RPC/Curryer/Server.hs+82−33
@@ -1,8 +1,7 @@
1−{-# LANGUAGE DerivingVia, DeriveGeneric, RankNTypes, ScopedTypeVariables, MultiParamTypeClasses, OverloadedStrings, GeneralizedNewtypeDeriving, TypeApplications, CPP, ExistentialQuantification, StandaloneDeriving #-}
1+{-# LANGUAGE DerivingVia, DeriveGeneric, RankNTypes, ScopedTypeVariables, MultiParamTypeClasses, OverloadedStrings, GeneralizedNewtypeDeriving, TypeApplications, CPP, ExistentialQuantification, StandaloneDeriving, GADTs #-}
22{-# OPTIONS_GHC -fno-warn-orphans #-}
33{- HLINT ignore "Use lambda-case" -}
44module Network.RPC.Curryer.Server where
5−import Streamly
65import qualified Streamly.Prelude as S
76import Streamly.Network.Socket
87import Streamly.Internal.Network.Socket (handleWithM)
@@ -26,11 +25,12 @@ import Streamly.Data.Fold as FL
2625import qualified Streamly.Internal.Data.Stream.IsStream as S
2726import qualified Data.Binary as B
2827import qualified Data.UUID as UUIDBase
28+import Control.Monad
2929
3030import qualified Network.RPC.Curryer.StreamlyAdditions as SA
3131--import Control.Monad
3232import Data.Hashable
33−--import System.Timeout
33+import System.Timeout
3434import qualified Network.ByteOrder as BO
3535
3636-- for toArrayS conversion
@@ -66,29 +66,47 @@ withLock (Locking mvar v) m =
6666lockless :: Locking a -> a
6767lockless (Locking _ a) = a
6868
69−type Timeout = Int
69+type Timeout = Word32
7070
7171type BinaryMessage = BS.ByteString
7272
7373--includes the fingerprint of the incoming data type (held in the BinaryMessage) to determine how to dispatch the message.
7474--add another envelope type for unencoded binary messages for any easy optimization for in-process communication
75−data Envelope = Envelope !Fingerprint !MessageType !UUID !BinaryMessage
75+data Envelope = Envelope {
76+ envFingerprint :: !Fingerprint,
77+ envMessageType :: !MessageType,
78+ envMsgId :: !UUID,
79+ envPayload :: !BinaryMessage
80+ }
7681 deriving (Generic, Show)
77− deriving Serialise via WineryVariant Envelope
82+
83+type TimeoutMicroseconds = Int
7884
7985deriving instance Generic Fingerprint
8086deriving via WineryVariant Fingerprint instance Serialise Fingerprint
8187
82−data MessageType = RequestMessage
88+data MessageType = RequestMessage TimeoutMicroseconds
8389 | ResponseMessage
90+ | TimeoutResponseMessage
8491 deriving (Generic, Show)
8592 deriving Serialise via WineryVariant MessageType
8693
87−data MessageHandlers =
88− forall a b. (Show a, Serialise a, Serialise b) => MessageHandlers [RequestHandler a b]
94+type MessageHandlers = [RequestHandler]
8995
90−type RequestHandler a b = a -> IO b
96+--newtype RequestHandler = RequestHandler { handler :: forall a b. (Serialise a, Serialise b) => (a -> IO b) }
9197
98+data RequestHandler where
99+ -- | create a request handler with a response
100+ RequestHandler :: forall a b. (Serialise a, Serialise b) => (a -> IO b) -> RequestHandler
101+ -- | create an asynchronous request handler where the client does not expect nor await a response
102+ AsyncRequestHandler :: forall a. Serialise a => (a -> IO ()) -> RequestHandler
103+
104+{-
105+handleCall :: forall a b. (Serialise a, Serialise b)
106+ => (a -> IO b)
107+ -> RequestHandler
108+handleCall f = RequestHandler f
109+-}
92110{-data Message a = Response UUID a
93111 | AsyncRequest UUID a
94112 | ResponseExpectedRequest UUID (Maybe Timeout) a
@@ -136,12 +154,14 @@ allHostAddrs,localHostAddr :: HostAddr
136154allHostAddrs = (0,0,0,0)
137155localHostAddr = (127,0,0,1)
138156
139−-- Each message is length-prefixed by a 32-bit unsigned length.
157+-- Each message is lxggength-prefixed by a 32-bit unsigned length.
140158envelopeP :: Parser IO Word8 Envelope
141159envelopeP = do
142160 let s = FL.toList
143− msgTypeP = (P.satisfy (== 0) *> pure RequestMessage) `P.alt`
144− (P.satisfy (== 1) *> pure ResponseMessage)
161+ msgTypeP = (P.satisfy (== 0) *>
162+ (RequestMessage . fromIntegral <$> word32P)) `P.alt`
163+ (P.satisfy (== 1) *> pure ResponseMessage) `P.alt`
164+ (P.satisfy (== 2) *> pure TimeoutResponseMessage)
145165 lenPrefixedByteStringP = do
146166 c <- fromIntegral <$> word32P
147167 BS.pack <$> P.takeEQ c s
@@ -153,8 +173,9 @@ encodeEnvelope (Envelope (Fingerprint fp1 fp2) msgType msgId bs) =
153173 where
154174 fingerprintBs = BO.bytestring64 fp1 <> BO.bytestring64 fp2
155175 msgTypeBs = case msgType of
156− RequestMessage -> BS.singleton 0
176+ RequestMessage timeoutms -> BS.singleton 0 <> BO.bytestring32 (fromIntegral timeoutms)
157177 ResponseMessage -> BS.singleton 1
178+ TimeoutResponseMessage -> BS.singleton 2
158179 msgIdBs =
159180 case UUIDBase.toWords (_unUUID msgId) of
160181 (u1, u2, u3, u4) -> foldr (<>) BS.empty (map BO.bytestring32 [u1, u2, u3, u4])
@@ -199,13 +220,13 @@ serve ::
199220 PortNumber ->
200221 Maybe (MVar SockAddr) ->
201222 IO Bool
202−serve userMsgHandler hostaddr port mSockLock = do
223+serve userMsgHandlers hostaddr port mSockLock = do
203224 let
204225 handleSock sock = do
205226 lockingSocket <- newLock sock
206− drainSocketMessages sock (serverEnvelopeHandler lockingSocket userMsgHandler)
227+ drainSocketMessages sock (serverEnvelopeHandler lockingSocket userMsgHandlers)
207228
208− serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & parallely . S.mapM (handleWithM handleSock) & S.drain
229+ S.serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & S.parallely . S.mapM (handleWithM handleSock) & S.drain
209230 pure True
210231
211232{-serverEnvelopeHandler :: Locking Socket -> MessageHandlers s -> Envelope -> IO ()
@@ -228,27 +249,54 @@ openEnvelope (Envelope eprint _ _ bytes) = do
228249 else
229250 Nothing
230251
252+matchEnvelope :: forall a b. (Serialise a, Serialise b, Typeable b) =>
253+ Envelope ->
254+ (a -> IO b) ->
255+ Maybe (a -> IO b, a)
256+matchEnvelope envelope dispatchf =
257+ case openEnvelope envelope :: Maybe a of
258+ Nothing -> Nothing
259+ Just decoded -> Just (dispatchf, decoded)
260+
231261serverEnvelopeHandler ::
232262 Locking Socket
233263 -> MessageHandlers
234264 -> Envelope
235265 -> IO ()
236−serverEnvelopeHandler sockLock (MessageHandlers msgHandlers) envelope@(Envelope _ RequestMessage msgId _) = do
266+serverEnvelopeHandler _ _ (Envelope _ TimeoutResponseMessage _ _) = pure ()
267+serverEnvelopeHandler sockLock msgHandlers envelope@(Envelope _ (RequestMessage timeoutms) msgId _) = do
237268 --find first matching handler
238− let firstMatch = foldr firstMatcher Nothing msgHandlers
239− firstMatcher :: forall a b. (Serialise a, Serialise b) => RequestHandler a b -> Maybe (RequestHandler a b, a) -> Maybe (RequestHandler a b, a)
240− firstMatcher msghandler Nothing =
241− case openEnvelope envelope :: Maybe a of
242− Just decoded -> Just (msghandler, decoded)
243− Nothing -> Nothing
244− firstMatcher _ acc = acc
245− case firstMatch of
246− Nothing -> error "failed to handle msg"
247− Just (msghandler, decoded) -> do
248− --TODO add exception handling
249− response <- msghandler decoded
250− sendEnvelope (Envelope (fingerprint response) ResponseMessage msgId (serialise response)) sockLock
251−serverEnvelopeHandler _ _ (Envelope _ ResponseMessage _ _) = error "server received response message"
269+ let runTimeout :: IO b -> IO (Maybe b)
270+ runTimeout m =
271+ if timeoutms == 0 then
272+ Just <$> m
273+ else
274+ timeout (fromIntegral timeoutms) m
275+ firstMatcher (RequestHandler (msghandler :: a -> IO b)) Nothing =
276+ case matchEnvelope envelope msghandler of
277+ Nothing -> pure Nothing
278+ Just (dispatchf, decoded) ->
279+ --TODO add exception handling
280+ do
281+ mResponse <- runTimeout (dispatchf decoded)
282+ let envelopeResponse =
283+ case mResponse of
284+ Just response ->
285+ Envelope (fingerprint response) ResponseMessage msgId (serialise response)
286+ Nothing -> Envelope (fingerprint TimeoutError) (TimeoutResponseMessage) msgId (BS.empty)
287+ sendEnvelope envelopeResponse sockLock
288+ pure (Just ())
289+ firstMatcher (AsyncRequestHandler (msghandler :: a -> IO ())) Nothing =
290+ case matchEnvelope envelope msghandler of
291+ Nothing -> pure Nothing
292+ Just (dispatchf, decoded) -> do
293+ _ <- dispatchf decoded
294+ pure (Just ())
295+ firstMatcher _ acc = pure acc
296+ foldM_ (flip firstMatcher) Nothing msgHandlers
297+serverEnvelopeHandler _ _ (Envelope _ ResponseMessage _ _) = error "server received response message"
298+
299+
252300{-
253301
254302
@@ -291,7 +339,7 @@ type AsyncMessageHandler a = a -> IO ()
291339drainSocketMessages :: Socket -> EnvelopeHandler -> IO ()
292340drainSocketMessages sock envelopeHandler = do
293341 let sockStream = S.unfold readWithBufferOf (1024 * 4, sock)
294− S.drain $ serially $ S.parseMany envelopeP sockStream & S.mapM envelopeHandler
342+ S.drain $ S.serially $ S.parseMany envelopeP sockStream & S.mapM envelopeHandler
295343
296344--send length-tagged bytestring, perhaps should be in network byte order?
297345sendEnvelope :: Envelope -> Locking Socket -> IO ()
@@ -305,3 +353,4 @@ sendEnvelope envelope sockLock = do
305353
306354fingerprint :: Typeable a => a -> Fingerprint
307355fingerprint = typeRepFingerprint . typeOf
356+
test/Curryer/Test/Basic.hs+77−85
@@ -1,4 +1,4 @@
1−{-# LANGUAGE DerivingVia, DeriveGeneric, TypeApplications #-}
1+{-# LANGUAGE DerivingVia, DeriveGeneric, TypeApplications, ExistentialQuantification #-}
22module Curryer.Test.Basic where
33import Test.Tasty
44import Test.Tasty.HUnit
@@ -9,63 +9,72 @@ import Network.Socket (SockAddr(..))
99import Control.Concurrent.Async
1010import Control.Monad
1111import Control.Concurrent
12−import Data.List
1312
1413import Network.RPC.Curryer.Server
1514import Network.RPC.Curryer.Client
1615
17−import Debug.Trace
18−
1916-- TODO: add test for nested calls
2017
2118testTree :: TestTree
2219testTree = testGroup "basic" [testCase "simple" testSimpleCall
23− {-testCase "client async" testAsyncServerCall,
24− testCase "server async" testAsyncClientCall,
25− testCase "client sync timeout" testSyncClientCallTimeout,
26− testCase "server-side exception" testSyncException,
27− testCase "multi-threaded client" testMultithreadedClient-}
20+ --,testCase "client async" testAsyncServerCall
21+ ,testCase "server async" testAsyncClientCall
22+ --,testCase "client sync timeout" testSyncClientCallTimeout
23+ --,testCase "server-side exception" testSyncException
24+ ,testCase "multi-threaded client" testMultithreadedClient
2825 ]
2926
30−--client-to-server request
31−data TestRequest = AddTwoNumbersReq Int Int
32− | TestAsyncReq String
33− | TestCallMeBackReq String
34− | RoundtripStringReq String
35− | DelayMicrosecondsReq Int
36− | ThrowServerSideExceptionReq
27+
28+data AddTwoNumbersReq = AddTwoNumbersReq Int Int
3729 deriving (Generic, Show)
38− deriving Serialise via WineryVariant TestRequest
30+ deriving Serialise via WineryVariant AddTwoNumbersReq
3931
40−--server responds to client
41−data TestResponse = AddTwoNumbersResp Int
42− | AsyncHello String
43− | DelayMicrosecondsResp
44− | RoundtripStringResp String
32+data TestCallMeBackReq = TestCallMeBackReq String
4533 deriving (Generic, Show, Eq)
46− deriving Serialise via WineryVariant TestResponse
34+ deriving Serialise via WineryVariant TestCallMeBackReq
35+
36+data TestAsyncReq = TestAsyncReq String
37+ deriving (Generic, Show)
38+ deriving Serialise via WineryVariant TestAsyncReq
39+
40+data DelayMicrosecondsReq = DelayMicrosecondsReq Int
41+ deriving (Generic, Show)
42+ deriving Serialise via WineryVariant DelayMicrosecondsReq
43+
44+data RoundtripStringReq = RoundtripStringReq String
45+ deriving (Generic, Show)
46+ deriving Serialise via WineryVariant RoundtripStringReq
47+
48+data ThrowServerSideExceptionReq = ThrowServerSideExceptionReq
49+ deriving (Generic, Show)
50+ deriving Serialise via WineryVariant ThrowServerSideExceptionReq
51+
52+--used to server -> client async request
53+data AsyncHelloReq = AsyncHelloReq
54+ deriving (Generic, Show)
55+ deriving Serialise via WineryVariant AsyncHelloReq
4756
4857testServerMessageHandlers :: Maybe (MVar String) -> MessageHandlers
4958testServerMessageHandlers mAsyncMVar =
50− MessageHandlers [\(AddTwoNumbersReq x y) ->
51− pure (AddTwoNumbersResp (x + y))]
52−{- --print msg
53− case msg of
54− --round-trip request to add two Ints
55− AddTwoNumbersReq x y -> pure (AddTwoNumbersResp (x+y))
56− --respond to a request for an async callback
57− TestCallMeBackReq s -> do
58− case mAsyncMVar of
59− Nothing -> pure ()
60− Just mvar -> putMVar mvar s
61− pure NoResponse
62− TestAsyncReq _ -> pure NoResponse
63− RoundtripStringReq s -> pure (RoundtripStringResp s)
64− DelayMicrosecondsReq ms -> do
59+ [ RequestHandler $ \(AddTwoNumbersReq x y) -> pure (x + y)
60+ , RequestHandler $ \(TestCallMeBackReq s) ->
61+ case mAsyncMVar of
62+ Nothing -> pure ()
63+ Just mvar -> putMVar mvar s
64+ , AsyncRequestHandler $ \(TestAsyncReq v) ->
65+ maybe (pure ()) (\mvar -> putMVar mvar v) mAsyncMVar
66+ -- an async hello to the server generates an async hello to the client
67+ --, AsyncRequestHandler $ \(AsyncReq s) -> currently lacking server state to message client
68+
69+ , RequestHandler $ \(RoundtripStringReq s) -> pure s
70+ , RequestHandler $ \(DelayMicrosecondsReq ms) -> do
6571 threadDelay ms
66− pure (HandlerResponse DelayMicrosecondsResp)
67− ThrowServerSideExceptionReq -> error "test server exception"
68−-}
72+ pure ()
73+ , RequestHandler $ \ThrowServerSideExceptionReq -> do
74+ _ <- error "test server exception"
75+ pure ()
76+ ]
77+
6978-- test a simple client-to-server round-trip function execution
7079testSimpleCall :: Assertion
7180testSimpleCall = do
@@ -74,42 +83,32 @@ testSimpleCall = do
7483 server <- async (serve (testServerMessageHandlers Nothing) localHostAddr 0 (Just readyVar))
7584 --wait for server to be ready
7685 (SockAddrInet port _) <- takeMVar readyVar
77− let clientHandler :: AsyncMessageHandler TestResponse
78− clientHandler _ = error "async handler called"
79− mkConn :: IO Connection
80− mkConn = connect localHostAddr port
81− conn <- mkConn
82− let c :: IO (Either ConnectionError TestResponse)
83− c = call conn (AddTwoNumbersReq 1 1)
86+ conn <- connect [] localHostAddr port
8487 replicateM_ 5 $ do --make five AddTwo calls to shake out parallelism bugs
85− x <- c
86− assertEqual "server request+response" (Right (AddTwoNumbersResp 2)) x
88+ x <- call conn (AddTwoNumbersReq 1 1)
89+ assertEqual "server request+response" (Right (2 :: Int)) x
8790 close conn
8891 cancel server
8992
90−{-
93+
9194--test that the client can proces a server-initiated asynchronous callback from the server
95+{-
9296testAsyncServerCall :: Assertion
9397testAsyncServerCall = do
9498 portReadyVar <- newEmptyMVar
9599 receivedAsyncMessageVar <- newEmptyMVar
96−
97− let testServerNewConnHandler :: NewConnectionHandler TestResponse
98− testServerNewConnHandler =
99− pure (Just (AsyncHello "welcome"))
100−
101− server <- async (serve testServerNewConnHandler (testServerMessageHandler Nothing) localHostAddr 0 (Just portReadyVar))
100+ let clientAsyncHandlers =
101+ [ClientAsyncRequestHandler (\AsyncHelloReq ->
102+ putMVar receivedAsyncMessageVar "")]
103+ server <- async (serve clientAsyncHandlers (testServerMessageHandlers receivedAsyncMessageVar) localHostAddr 0 (Just portReadyVar))
102104 (SockAddrInet port _) <- takeMVar portReadyVar
103− let clientHandler :: AsyncMessageHandler TestResponse
104− clientHandler msg = do
105− case msg of
106− AsyncHello str -> putMVar receivedAsyncMessageVar str
107− x -> error ("unexpected message " <> show x)
108− conn <- connect @TestResponse clientHandler localHostAddr port
105+ conn <- connect @TestResponse clientHandler localHostAddr port
106+ callAsync conn TestAsyncReq
109107 asyncMessage <- takeMVar receivedAsyncMessageVar
110108 assertEqual "async message" "welcome" asyncMessage
111109 close conn
112110 cancel server
111+-}
113112
114113--test that the client can make a non-blocking call
115114testAsyncClientCall :: Assertion
@@ -117,10 +116,10 @@ testAsyncClientCall = do
117116 portReadyVar <- newEmptyMVar
118117 receivedAsyncMessageVar <- newEmptyMVar
119118
120− server <- async (serve (pure Nothing) (testServerMessageHandler (Just receivedAsyncMessageVar)) localHostAddr 0 (Just portReadyVar))
119+ server <- async (serve (testServerMessageHandlers (Just receivedAsyncMessageVar)) localHostAddr 0 (Just portReadyVar))
121120 (SockAddrInet port _) <- takeMVar portReadyVar
122−
123− conn <- connect @TestResponse (\_ -> pure ()) localHostAddr port
121+
122+ conn <- connect [] localHostAddr port
124123 --send an async message, wait for an async response to confirm receipt
125124 Right () <- asyncCall conn (TestCallMeBackReq "hi server")
126125 asyncMessage <- takeMVar receivedAsyncMessageVar
@@ -132,21 +131,16 @@ testSyncClientCallTimeout :: Assertion
132131testSyncClientCallTimeout = do
133132 readyVar <- newEmptyMVar
134133
135− server <- async (serve (pure Nothing) (testServerMessageHandler Nothing) localHostAddr 0 (Just readyVar))
134+ server <- async (serve (testServerMessageHandlers Nothing) localHostAddr 0 (Just readyVar))
136135 --wait for server to be ready
137136 (SockAddrInet port _) <- takeMVar readyVar
138− let clientHandler :: AsyncMessageHandler TestResponse
139− clientHandler _ = error "async handler called"
140− mkConn :: IO (Connection TestResponse)
141− mkConn = connect clientHandler localHostAddr port
142− conn <- mkConn
143− let c :: IO (Either ConnectionError TestResponse)
144− c = callTimeout (Just 500) conn (DelayMicrosecondsReq 1000)
145− x <- c
137+ conn <- connect [] localHostAddr port
138+ x <- callTimeout @_ @Int (Just 500) conn (DelayMicrosecondsReq 1000)
146139 assertEqual "client sync timeout" (Left TimeoutError) x
147140 close conn
148141 cancel server
149−
142+
143+{-
150144testSyncException :: Assertion
151145testSyncException = do
152146 readyVar <- newEmptyMVar
@@ -165,25 +159,23 @@ testSyncException = do
165159 Right _ -> assertFailure "missed exception"
166160 close conn
167161 cancel server
168−
162+-}
169163--throw large messages (> PIPE_BUF) at the server from multiple client connections to exercise the socket lock
170164testMultithreadedClient :: Assertion
171165testMultithreadedClient = do
172166 readyVar <- newEmptyMVar
173167
174− server <- async (serve (pure Nothing) (testServerMessageHandler Nothing) localHostAddr 0 (Just readyVar))
168+ server <- async (serve (testServerMessageHandlers Nothing) localHostAddr 0 (Just readyVar))
175169 (SockAddrInet port _) <- takeMVar readyVar
176− let clientHandler :: AsyncMessageHandler TestResponse
177− clientHandler _ = error "async handler called"
178− mkConn :: IO (Connection TestResponse)
179− mkConn = connect clientHandler localHostAddr port
180− conn <- mkConn
170+ {-let clientHandler :: AsyncMessageHandler TestResponse
171+ clientHandler _ = error "async handler called"-}
172+ conn <- connect [] localHostAddr port
181173 let bigString = replicate (1024 * 1000) 'x'
182174 replicateM_ 10 $ do
183175 ret <- call conn (RoundtripStringReq bigString)
184− assertEqual "big string multithread" (Right (RoundtripStringResp bigString)) ret
176+ assertEqual "big string multithread" (Right bigString) ret
185177 putStrLn "plus one"
186178 close conn
187179 cancel server
188180
189−-}
181+