on 26ff225 help sign in
10 files changed+278−544
since splitsnapshots
unifiedsplit
Changelog.markdown−4 README.markdown+2−68 bench/Basic.hs+21−25 cabal.project+1−1 curryer-rpc.cabal → curryer.cabal+7−21 examples/SimpleKeyValueClient.hs−39 examples/SimpleKeyValueServer.hs−30 src/Network/RPC/Curryer/Client.hs+36−71 src/Network/RPC/Curryer/Server.hs+115−150 test/Curryer/Test/Basic.hs+96−135
Changelog.markdowndeleted−4
@@ -1,4 +0,0 @@
1−# v0.1 (2020-12-27)
2−
3−* initial release to support [Project:M36](https://github.com/agentm/project-m36)
4−
README.markdown+2−68
@@ -2,77 +2,11 @@
22
33Curryer (pun intended) is a fast, Haskell-exclusive RPC (remote procedure call) library. By using the latest Haskell serialization and streaming libraries, curryer aims to be the fastest and easiest means of communicating between Haskell-based processes.
44
5−Curryer is inspired by the now unmaintained [distributed-process](https://hackage.haskell.org/package/distributed-process) library, but is lighter-weight and uses a higher-performance serialization package.
5+Curryer is inspired by the now unmaintained [distributed-process](https://hackage.haskell.org/package/distributed-process) library.
66
77## Features
88
99* blocking and non-blocking remote function calls
1010* asynchronous server-to-client callbacks (for server-initiated notifications)
1111* timeouts
12−* leverages [winery](https://hackage.haskell.org/package/winery) for high-performance serialization
13−
14−## Requirements
15−
16−* GHC 8.6+
17−
18−## Code Example
19−
20−[Server](https://github.com/agentm/curryer/examples/SimpleKeyValueServer.hs):
21−
22−```haskell
23−data SetKey = SetKey String String
24− deriving (Generic, Show)
25− deriving Serialise via WineryVariant SetKey
26−
27−data GetKey = GetKey String
28− deriving (Generic, Show)
29− deriving Serialise via WineryVariant GetKey
30−
31−main :: IO ()
32−main = do
33− kvmap <- M.newIO
34− void $ serve kvRequestHandlers kvmap localHostAddr 8765 Nothing
35−
36−kvRequestHandlers :: RequestHandlers (M.Map String String)
37−kvRequestHandlers = [ RequestHandler $ \state (SetKey k v) ->
38− atomically $ M.insert v k (connectionServerState state)
39− , RequestHandler $ \state (GetKey k) ->
40− atomically $ M.lookup k (connectionServerState state)
41− ]
42−```
43−
44−[Client](https://github.com/agentm/curryer/examples/SimpleKeyValueClient.hs):
45−
46−```haskell
47−data SetKey = SetKey String String
48− deriving (Generic, Show)
49− deriving Serialise via WineryVariant SetKey
50−
51−data GetKey = GetKey String
52− deriving (Generic, Show)
53− deriving Serialise via WineryVariant GetKey
54−
55−data CommandOptions = Get {name :: String}
56− | Set {name :: String, value :: String}
57− deriving (Generic, Show)
58−
59−instance ParseRecord CommandOptions
60−
61−main :: IO ()
62−main = do
63− opts <- getRecord "SimpleKeyValueClient"
64− conn <- connect [] localHostAddr 8765
65− case opts of
66− Get k -> do
67− eRet <- call conn (GetKey k)
68− case eRet of
69− Left err -> error (show err)
70− Right (Just val) -> putStrLn val
71− Right Nothing -> error "no such key"
72− Set k v -> do
73− eRet <- call conn (SetKey k v)
74− case eRet of
75− Left err -> error (show err)
76− Right () -> pure ()
77−
78−```
12+* utilizes versionable shared ADT for communication instead of shared function `StaticPtr`s
bench/Basic.hs+21−25
@@ -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 benchmarkServerRequestHandlers () localHostAddr 0 (Just portReadyMVar))
18+ server <- async (serve (pure Nothing) benchmarkServerMessageHandler localHostAddr 0 (Just portReadyMVar))
1919 --wait for server to be ready
2020 (SockAddrInet port _) <- takeMVar portReadyMVar
21− clientConn <- connect [] localHostAddr port
21+ clientConn <- connect (const (pure ())) 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,38 +41,34 @@ main = do
4141 close clientConn
4242 cancel server
4343
44−data WaitMillisecondsReq = WaitMillisecondsReq Int --ask the server to respond in X milliseconds
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
4546 deriving (Generic, Show)
46− deriving Serialise via WineryVariant WaitMillisecondsReq
47+ deriving Serialise via WineryVariant BenchRequest
4748
48−data WaitByteStringReq = WaitByteStringReq BS.ByteString
49− deriving (Generic, Show)
50− deriving Serialise via WineryVariant WaitByteStringReq
51−
52−data WaitMillisecondsResp = WaitMillisecondsResp
49+data BenchResponse = WaitMillisecondsResp
50+ | WaitByteStringResp BS.ByteString
5351 deriving (Generic, Show, Eq)
54− deriving Serialise via WineryVariant WaitMillisecondsResp
52+ deriving Serialise via WineryVariant BenchResponse
5553
56−benchmarkServerRequestHandlers :: RequestHandlers a
57−benchmarkServerRequestHandlers =
58− [RequestHandler $ \_ (WaitMillisecondsReq ms) -> do
59− threadDelay (1000 * ms)
60− pure WaitMillisecondsResp,
61− RequestHandler $ \_ (WaitByteStringReq bs) ->
62− pure bs
63− ]
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))
6459
6560--perform 10000 small synchronous requests
66−syncWaitRequest :: Connection -> Int -> IO Int
61+syncWaitRequest :: Connection BenchResponse -> Int -> IO Int
6762syncWaitRequest conn ms = do
6863 ret <- call conn (WaitMillisecondsReq ms)
6964 when (ret /= Right WaitMillisecondsResp) (error "ret failed")
7065 pure ms
7166
72−syncByteStringRequest :: Connection -> BS.ByteString -> IO BS.ByteString
67+syncByteStringRequest :: Connection BenchResponse -> BS.ByteString -> IO BS.ByteString
7368syncByteStringRequest conn bs = do
7469 ret <- call conn (WaitByteStringReq bs)
7570 case ret of
76− Right bs' -> pure bs'
71+ Right (WaitByteStringResp bs') -> pure bs'
72+ Right _ -> error "invalid resp"
7773 Left err -> error (show err)
7874
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: 4e48b52e8709390a47e99c107ebed2d1c9420076
6+ tag: e218fb9d8f4263fd69d32a2ff318bac0e179c642
curryer-rpc.cabal → curryer.cabalrenamed+7−21
@@ -1,4 +1,4 @@
1−Name: curryer-rpc
1+Name: curryer
22Version: 0.1
33License: PublicDomain
44Build-Type: Simple
@@ -11,14 +11,14 @@ Maintainer: agentm@themactionfaction.com
1111Cabal-Version: >= 1.10
1212Synopsis: Fast, Haskell RPC
1313Description: Haskell-to-Haskell RPC using Winery serialization.
14−Extra-Source-Files: Changelog.markdown README.markdown cabal.project
14+Extra-Source-Files: Changelog.markdown README.markdown
1515
1616Source-Repository head
1717 Type: git
1818 location: https://github.com/agentm/curryer
1919
2020Library
21− Build-Depends: base >= 4.12 && < 4.15
21+ Build-Depends: base >= 4.8
2222 , winery
2323 , bytestring
2424 , streamly >= 0.7.2
@@ -33,7 +33,6 @@ Library
3333 , hashable
3434 , time
3535 , network-byte-order
36− , stm
3736 Hs-Source-Dirs: ./src
3837 Default-Language: Haskell2010
3938 ghc-options: -Wall -fwarn-unused-binds -fwarn-unused-imports
@@ -51,33 +50,20 @@ Test-Suite test
5150 build-depends: tasty
5251 , tasty-hunit
5352 , base
54− , curryer-rpc
53+ , curryer
5554 , winery
5655 , network
5756 , async
58− , stm
5957 other-modules: Curryer.Test.Basic
6058
6159Benchmark perf
6260 Default-Language: Haskell2010
6361 Default-Extensions: OverloadedStrings
64− Build-Depends: base, criterion, curryer-rpc, network, winery, async, bytestring
62+ Build-Depends: base, criterion, curryer, network, winery, async, bytestring
6563
6664 Main-Is: Basic.hs
6765 Type: exitcode-stdio-1.0
68− GHC-Options: -Wall -threaded -rtsopts
69− -- -fprof-auto
66+ GHC-Options: -Wall -threaded -rtsopts -fprof-auto
7067 HS-Source-Dirs: ./bench
7168
72−Executable SimpleKeyValueServer
73− Build-Depends: stm-containers, stm, base, curryer-rpc, winery
74− Main-Is: examples/SimpleKeyValueServer.hs
75− GHC-Options: -Wall -threaded
76− Default-Language: Haskell2010
77−
78−Executable SimpleKeyValueClient
79− Build-Depends: stm, base, curryer-rpc, winery, optparse-generic
80− Main-Is: examples/SimpleKeyValueClient.hs
81− GHC-Options: -Wall -threaded
82− Default-Language: Haskell2010
83−
69+
examples/SimpleKeyValueClient.hsdeleted−39
@@ -1,39 +0,0 @@
1−{-# LANGUAGE DerivingVia, DeriveGeneric, OverloadedStrings #-}
2−import GHC.Generics
3−import Network.RPC.Curryer.Client
4−import Network.RPC.Curryer.Server (localHostAddr)
5−import Options.Generic
6−import Codec.Winery
7−
8−data SetKey = SetKey String String
9− deriving (Generic, Show)
10− deriving Serialise via WineryVariant SetKey
11−
12−data GetKey = GetKey String
13− deriving (Generic, Show)
14− deriving Serialise via WineryVariant GetKey
15−
16−data CommandOptions = Get {name :: String}
17− | Set {name :: String, value :: String}
18− deriving (Generic, Show)
19−
20−instance ParseRecord CommandOptions
21−
22−main :: IO ()
23−main = do
24− opts <- getRecord "SimpleKeyValueClient"
25− -- connect to the remote server (in this case on the localhost address)
26− conn <- connect [] localHostAddr 8765
27− case opts of
28− Get k -> do
29− --call the remote function and validate the result
30− eRet <- call conn (GetKey k)
31− case eRet of
32− Left err -> error (show err)
33− Right (Just val) -> putStrLn val
34− Right Nothing -> error "no such key"
35− Set k v -> do
36− eRet <- call conn (SetKey k v)
37− case eRet of
38− Left err -> error (show err)
39− Right () -> pure ()
examples/SimpleKeyValueServer.hsdeleted−30
@@ -1,30 +0,0 @@
1−{-# LANGUAGE DerivingVia, DeriveGeneric #-}
2−import Network.RPC.Curryer.Server
3−import Control.Concurrent.STM
4−import StmContainers.Map as M
5−import GHC.Generics
6−import Codec.Winery
7−import Data.Functor
8−
9−-- create data structures to represent remote function calls
10−data SetKey = SetKey String String
11− deriving (Generic, Show)
12− deriving Serialise via WineryVariant SetKey
13−
14−data GetKey = GetKey String
15− deriving (Generic, Show)
16− deriving Serialise via WineryVariant GetKey
17−
18−--call the `serve` function to handle incoming requests
19−main :: IO ()
20−main = do
21− kvmap <- M.newIO
22− void $ serve kvRequestHandlers kvmap localHostAddr 8765 Nothing
23−
24−-- setup incoming request handlers to operate on the server's state
25−kvRequestHandlers :: RequestHandlers (M.Map String String)
26−kvRequestHandlers = [ RequestHandler $ \state (SetKey k v) ->
27− atomically $ M.insert v k (connectionServerState state)
28− , RequestHandler $ \state (GetKey k) ->
29− atomically $ M.lookup k (connectionServerState state)
30− ]
src/Network/RPC/Curryer/Client.hs+36−71
@@ -1,4 +1,4 @@
1−{-# LANGUAGE RankNTypes, ScopedTypeVariables, GADTs #-}
1+{-# LANGUAGE TypeApplications, RankNTypes, ScopedTypeVariables #-}
22module Network.RPC.Curryer.Client where
33import Network.RPC.Curryer.Server
44import Network.Socket as Socket
@@ -11,32 +11,24 @@ import Control.Concurrent.MVar
1111import GHC.Conc
1212import Data.Time.Clock
1313import System.Timeout
14−import Control.Monad
1514
1615type SyncMap = STMMap.Map UUID (MVar (Either ConnectionError BinaryMessage), UTCTime)
1716
18−-- | Represents a remote connection to server.
17+-- the request map holds
1918data Connection = Connection { _conn_sockLock :: Locking Socket,
20− _conn_asyncThread :: Async (),
21− _conn_syncmap :: SyncMap
22− }
19+ _conn_asyncThread :: Async (),
20+ _conn_syncmap :: SyncMap
21+ }
2322
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]
29−
30−-- | Connects to a remote server with specific async callbacks registered.
3123connect ::
32− ClientAsyncRequestHandlers ->
33− HostAddr ->
34− PortNumber ->
35− IO Connection
36−connect asyncHandlers hostAddr portNum = do
24+ --AsyncMessageHandler ->
25+ HostAddr ->
26+ PortNumber ->
27+ IO Connection
28+connect hostAddr portNum = do
3729 sock <- TCP.connect hostAddr portNum
3830 syncmap <- STMMap.newIO
39− asyncThread <- async (clientAsync sock syncmap asyncHandlers)
31+ asyncThread <- async (clientAsync sock syncmap)
4032 sockLock <- newLock sock
4133 pure (Connection {
4234 _conn_sockLock = sockLock,
@@ -44,75 +36,50 @@ connect asyncHandlers hostAddr portNum = do
4436 _conn_syncmap = syncmap
4537 })
4638
47−-- | Close the connection and release all connection resources.
4839close :: Connection -> IO ()
4940close conn = do
5041 withLock (_conn_sockLock conn) $ \sock ->
5142 Socket.close sock
5243 cancel (_conn_asyncThread conn)
5344
54−-- | async thread for handling client-side incoming messages- dispatch to proper waiting thread or asynchronous notifications handler
45+-- async thread for handling client-side incoming messages- dispatch to proper waiting thread or handler asynchronous notifications
5546clientAsync ::
5647 Socket ->
5748 SyncMap ->
58− ClientAsyncRequestHandlers ->
49+ --AsyncMessageHandler ->
5950 IO ()
60−clientAsync sock syncmap asyncHandlers = do
51+clientAsync sock syncmap = do
6152 lsock <- newLock sock
62− drainSocketMessages sock (clientEnvelopeHandler asyncHandlers lsock syncmap)
53+ drainSocketMessages sock (clientEnvelopeHandler lsock syncmap)
6354
64−consumeResponse :: UUID -> STMMap.Map UUID (MVar a, b) -> a -> IO ()
65−consumeResponse msgId syncMap val = do
55+--handles envelope responses from server
56+clientEnvelopeHandler ::
57+ Locking Socket
58+ -> SyncMap
59+ -> Envelope
60+ -> IO ()
61+clientEnvelopeHandler _ _ (Envelope _ RequestMessage _ _) = error "client received request"
62+clientEnvelopeHandler _ syncMap (Envelope _ ResponseMessage msgId binaryMessage) = do
63+ --find a matching response item
6664 match <- atomically $ do
6765 val' <- STMMap.lookup msgId syncMap
6866 STMMap.delete msgId syncMap
6967 pure val'
7068 case match of
71− Nothing -> pure () -- drop message
72− Just (mVar, _) -> putMVar mVar val
69+ Nothing -> error ("dropping unrequested response " <> show msgId)
70+ Just (mVar, _) ->
71+ putMVar mVar (Right binaryMessage)
72+
7373
74−-- | handles envelope responses from server- timeout from ths server is ignored, but perhaps that's proper for trusted servers- the server expects the client to process all async requests
75−clientEnvelopeHandler ::
76− ClientAsyncRequestHandlers
77− -> Locking Socket
78− -> SyncMap
79− -> Envelope
80− -> IO ()
81−clientEnvelopeHandler handlers _ _ envelope@(Envelope _ (RequestMessage _) _ _) = do
82− --should this run off on another green thread?
83− let firstMatcher Nothing (ClientAsyncRequestHandler (dispatchf :: a -> IO ())) = do
84− case openEnvelope envelope of
85− Nothing -> pure Nothing
86− Just decoded -> do
87− dispatchf decoded
88− pure (Just ())
89− firstMatcher acc _ = pure acc
90− foldM_ firstMatcher Nothing handlers
91−clientEnvelopeHandler _ _ syncMap (Envelope _ ResponseMessage msgId binaryMessage) =
92− consumeResponse msgId syncMap (Right binaryMessage)
93−clientEnvelopeHandler _ _ syncMap (Envelope _ TimeoutResponseMessage msgId _) =
94− consumeResponse msgId syncMap (Left TimeoutError)
95−clientEnvelopeHandler _ _ syncMap (Envelope _ ExceptionResponseMessage msgId excPayload) =
96− case deserialise excPayload of
97− Left err -> error ("failed to deserialise exception string" <> show err)
98− Right excStr ->
99− consumeResponse msgId syncMap (Left (ExceptionError excStr))
100−
101−-- | Basic remote function call via data type and return value.
10274call :: (Serialise request, Serialise response) => Connection -> request -> IO (Either ConnectionError response)
10375call = callTimeout Nothing
10476
105−-- | Send a request to the remote server and returns a response but with the possibility of a timeout after n microseconds.
77+-- | Send a request to the remote server and returns a response.
10678callTimeout :: (Serialise request, Serialise response) => Maybe Int -> Connection -> request -> IO (Either ConnectionError response)
10779callTimeout mTimeout conn msg = do
10880 requestID <- UUID <$> UUIDBase.nextRandom
10981 let mVarMap = _conn_syncmap conn
110− timeoutms = case mTimeout of
111− Nothing -> 0
112− Just tm | tm < 0 -> 0
113− Just tm -> fromIntegral tm
114−
115− envelope = Envelope fprint (RequestMessage timeoutms) requestID (serialise msg)
82+ envelope = Envelope fprint RequestMessage requestID (serialise msg)
11683 fprint = fingerprint msg
11784 -- setup mvar to wait for response
11885 responseMVar <- newEmptyMVar
@@ -129,19 +96,17 @@ callTimeout mTimeout conn msg = do
12996 --timeout
13097 Nothing ->
13198 pure (Left TimeoutError)
132− Just (Left exc) ->
133− pure (Left exc)
99+ Just (Left exc) -> error ("exception in client from server: " <> show exc)
134100 Just (Right binmsg) ->
135101 --TODO use decoder instead
136102 case deserialise binmsg of
137103 Left err -> error ("deserialise client error " <> show err)
138104 Right m -> pure (Right m)
139−
140−-- | Call a remote function but do not expect a response from the server.
141−asyncCall :: Serialise request => Connection -> request -> IO (Either ConnectionError ())
105+{-
106+asyncCall :: (Serialise request, Serialise response) => Connection response -> request -> IO (Either ConnectionError ())
142107asyncCall conn msg = do
143108 requestID <- UUID <$> UUIDBase.nextRandom
144− let envelope = Envelope fprint (RequestMessage 0) requestID (serialise msg)
145− fprint = fingerprint msg
146− sendEnvelope envelope (_conn_sockLock conn)
109+ sendMessage (AsyncRequest requestID msg) (_conn_sockLock conn)
147110 pure (Right ())
111+
112+-}
src/Network/RPC/Curryer/Server.hs+115−150
@@ -1,7 +1,8 @@
1−{-# LANGUAGE DerivingVia, DeriveGeneric, RankNTypes, ScopedTypeVariables, MultiParamTypeClasses, OverloadedStrings, GeneralizedNewtypeDeriving, CPP, ExistentialQuantification, StandaloneDeriving, GADTs, RecordWildCards #-}
1+{-# LANGUAGE DerivingVia, DeriveGeneric, RankNTypes, ScopedTypeVariables, MultiParamTypeClasses, OverloadedStrings, GeneralizedNewtypeDeriving, TypeApplications, CPP, ExistentialQuantification, StandaloneDeriving #-}
22{-# OPTIONS_GHC -fno-warn-orphans #-}
33{- HLINT ignore "Use lambda-case" -}
44module Network.RPC.Curryer.Server where
5+import Streamly
56import qualified Streamly.Prelude as S
67import Streamly.Network.Socket
78import Streamly.Internal.Network.Socket (handleWithM)
@@ -25,24 +26,21 @@ import Streamly.Data.Fold as FL
2526import qualified Streamly.Internal.Data.Stream.IsStream as S
2627import qualified Data.Binary as B
2728import qualified Data.UUID as UUIDBase
28−import qualified Data.UUID.V4 as UUIDBase
29−import Control.Monad
30−import Data.Functor
3129
3230import qualified Network.RPC.Curryer.StreamlyAdditions as SA
3331--import Control.Monad
3432import Data.Hashable
35−import System.Timeout
33+--import System.Timeout
3634import qualified Network.ByteOrder as BO
3735
38−
3936-- for toArrayS conversion
40−import qualified Data.ByteString.Internal as BSI
41−import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as Arr
37+{-import qualified Data.ByteString.Internal as BSI
38+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as SA
39+import Foreign.ForeignPtr (plusForeignPtr)
4240import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
43−import GHC.Ptr (minusPtr)
44−
45−-- define CURRYER_SHOW_BYTES 1
41+import GHC.Ptr (plusPtr)
42+-}
43+-- #define CURRYER_SHOW_BYTES 1
4644#if CURRYER_SHOW_BYTES == 1
4745import Debug.Trace
4846#endif
@@ -68,58 +66,45 @@ withLock (Locking mvar v) m =
6866lockless :: Locking a -> a
6967lockless (Locking _ a) = a
7068
71−type Timeout = Word32
69+type Timeout = Int
7270
7371type BinaryMessage = BS.ByteString
7472
7573--includes the fingerprint of the incoming data type (held in the BinaryMessage) to determine how to dispatch the message.
7674--add another envelope type for unencoded binary messages for any easy optimization for in-process communication
77−data Envelope = Envelope {
78− envFingerprint :: !Fingerprint,
79− envMessageType :: !MessageType,
80− envMsgId :: !UUID,
81− envPayload :: !BinaryMessage
82− }
75+data Envelope = Envelope !Fingerprint !MessageType !UUID !BinaryMessage
8376 deriving (Generic, Show)
84−
85−type TimeoutMicroseconds = Int
77+ deriving Serialise via WineryVariant Envelope
8678
8779deriving instance Generic Fingerprint
8880deriving via WineryVariant Fingerprint instance Serialise Fingerprint
8981
90−-- | Internal type used to mark envelope types.
91−data MessageType = RequestMessage TimeoutMicroseconds
82+data MessageType = RequestMessage
9283 | ResponseMessage
93− | TimeoutResponseMessage
94− | ExceptionResponseMessage
9584 deriving (Generic, Show)
9685 deriving Serialise via WineryVariant MessageType
9786
98−-- | A list of `RequestHandler`s.
99−type RequestHandlers serverState = [RequestHandler serverState]
100−
101−-- | Data types for server-side request handlers, in synchronous (client waits for return value) and asynchronous (client does not wait for return value) forms.
102−data RequestHandler serverState where
103− -- | create a request handler with a response
104− RequestHandler :: forall a b serverState. (Serialise a, Serialise b) => (ConnectionState serverState -> a -> IO b) -> RequestHandler serverState
105− -- | create an asynchronous request handler where the client does not expect nor await a response
106− AsyncRequestHandler :: forall a serverState. Serialise a => (ConnectionState serverState -> a -> IO ()) -> RequestHandler serverState
107−
108−-- | Server state sent in via `serve` and passed to `RequestHandler`s.
109−data ConnectionState a = ConnectionState {
110− connectionServerState :: a,
111− connectionSocket :: Locking Socket
112− }
113−
114−-- | Used by server-side request handlers to send additional messages to the client. This is useful for sending asynchronous responses to the client outside of the normal request-response flow. The locking socket can be found in the ConnectionState when a request handler is called.
115−sendMessage :: Serialise a => Locking Socket -> a -> IO ()
116−sendMessage lockSock msg = do
117− requestID <- UUID <$> UUIDBase.nextRandom
118− let env =
119− Envelope (fingerprint msg) (RequestMessage timeout') requestID (serialise msg)
120− timeout' = 0
121− sendEnvelope env lockSock
87+data MessageHandlers =
88+ forall a b. (Show a, Serialise a, Serialise b) => MessageHandlers [RequestHandler a b]
12289
90+type RequestHandler a b = a -> IO b
91+
92+{-data Message a = Response UUID a
93+ | AsyncRequest UUID a
94+ | ResponseExpectedRequest UUID (Maybe Timeout) a
95+ | TimedOutResponse UUID
96+ | ExceptionResponse UUID String
97+ deriving (Generic, Show)
98+ deriving Serialise via WineryVariant (Message a)
99+
100+-- | The response type
101+
102+data HandlerResponse a = NoResponse
103+ | HandlerResponse a
104+ | HandlerException String
105+ deriving Generic
106+ deriving Serialise via WineryVariant (HandlerResponse a)
107+-}
123108--avoid orphan instance
124109newtype UUID = UUID { _unUUID :: UUIDBase.UUID }
125110 deriving (Show, Eq, B.Binary, Hashable)
@@ -138,18 +123,13 @@ instance Serialise UUID where
138123 x -> error $ "invalid schema element " <> show x
139124 decodeCurrent = B.decode . BSL.fromStrict <$> (decodeVarInt >>= getBytes)
140125
141−-- | Errors from remote calls.
126+
142127data ConnectionError = CodecError String -- show of WineryException from exception initiator which cannot otherwise be transmitted over a line due to dependencies on TypeReps
143128 | TimeoutError
144129 | ExceptionError String
145130 deriving (Generic, Show, Eq)
146131 deriving Serialise via WineryVariant ConnectionError
147132
148−data TimeoutException = TimeoutException
149− deriving Show
150−
151−instance Exception TimeoutException
152−
153133type HostAddr = (Word8, Word8, Word8, Word8)
154134
155135allHostAddrs,localHostAddr :: HostAddr
@@ -159,18 +139,12 @@ localHostAddr = (127,0,0,1)
159139-- Each message is length-prefixed by a 32-bit unsigned length.
160140envelopeP :: Parser IO Word8 Envelope
161141envelopeP = do
162− let msgTypeP = (P.satisfy (== 0) *>
163− (RequestMessage . fromIntegral <$> word32P)) `P.alt`
164− (P.satisfy (== 1) $> ResponseMessage) `P.alt`
165− (P.satisfy (== 2) $> TimeoutResponseMessage) `P.alt`
166− (P.satisfy (== 3) $> ExceptionResponseMessage)
142+ let s = FL.toList
143+ msgTypeP = (P.satisfy (== 0) *> pure RequestMessage) `P.alt`
144+ (P.satisfy (== 1) *> pure ResponseMessage)
167145 lenPrefixedByteStringP = do
168146 c <- fromIntegral <$> word32P
169− --streamly can't handle takeEQ 0, so add special handling
170− if c == 0 then
171− pure BS.empty
172− else
173− fromArray <$> P.takeEQ c (Arr.writeN c)
147+ BS.pack <$> P.takeEQ c s
174148 Envelope <$> fingerprintP <*> msgTypeP <*> uuidP <*> lenPrefixedByteStringP
175149
176150encodeEnvelope :: Envelope -> BS.ByteString
@@ -179,19 +153,19 @@ encodeEnvelope (Envelope (Fingerprint fp1 fp2) msgType msgId bs) =
179153 where
180154 fingerprintBs = BO.bytestring64 fp1 <> BO.bytestring64 fp2
181155 msgTypeBs = case msgType of
182− RequestMessage timeoutms -> BS.singleton 0 <> BO.bytestring32 (fromIntegral timeoutms)
156+ RequestMessage -> BS.singleton 0
183157 ResponseMessage -> BS.singleton 1
184− TimeoutResponseMessage -> BS.singleton 2
185− ExceptionResponseMessage -> BS.singleton 3
186158 msgIdBs =
187159 case UUIDBase.toWords (_unUUID msgId) of
188− (u1, u2, u3, u4) -> foldr ((<>) . BO.bytestring32) BS.empty [u1, u2, u3, u4]
160+ (u1, u2, u3, u4) -> foldr (<>) BS.empty (map BO.bytestring32 [u1, u2, u3, u4])
189161 msgLen = fromIntegral (BS.length bs)
190162 lenPrefixedBs = BO.bytestring32 msgLen <> bs
191163
192164fingerprintP :: Parser IO Word8 Fingerprint
193−fingerprintP =
194− Fingerprint <$> word64P <*> word64P
165+fingerprintP = do
166+ f1 <- word64P
167+ f2 <- word64P
168+ pure (Fingerprint f1 f2)
195169
196170word64P :: Parser IO Word8 Word64
197171word64P = do
@@ -212,112 +186,112 @@ uuidP = do
212186 u1 <- word32P
213187 u2 <- word32P
214188 u3 <- word32P
215− --u4 <- word32P
216− --pure (UUID (UUIDBase.fromWords u1 u2 u3 u4))-}
217− --(UUID . UUIDBase.fromWords) <$> word32P <*> word32P <*> word32P <*> word32P
218− UUID . UUIDBase.fromWords u1 u2 u3 <$> word32P
189+ u4 <- word32P
190+ pure (UUID (UUIDBase.fromWords u1 u2 u3 u4))
219191
220192type NewConnectionHandler msg = IO (Maybe msg)
221193
222194type NewMessageHandler req resp = req -> IO resp
223−
224−-- | Listen for new connections and handle requests which are passed the server state 's'. The MVar SockAddr can be be optionally used to know when the server is ready for processing requests.
195+
225196serve ::
226− RequestHandlers s->
227− s ->
197+ MessageHandlers ->
228198 HostAddr ->
229199 PortNumber ->
230200 Maybe (MVar SockAddr) ->
231201 IO Bool
232−serve userMsgHandlers serverState hostaddr port mSockLock = do
202+serve userMsgHandler hostaddr port mSockLock = do
233203 let
234204 handleSock sock = do
235205 lockingSocket <- newLock sock
236− drainSocketMessages sock (serverEnvelopeHandler lockingSocket userMsgHandlers serverState)
206+ drainSocketMessages sock (serverEnvelopeHandler lockingSocket userMsgHandler)
237207
238− S.serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & S.parallely . S.mapM (handleWithM handleSock) & S.drain
208+ serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & parallely . S.mapM (handleWithM handleSock) & S.drain
239209 pure True
240210
211+{-serverEnvelopeHandler :: Locking Socket -> MessageHandlers s -> Envelope -> IO ()
212+serverEnvelopeHandler sockLock (Envelope fprint RequestMessage msgId messagebs) = do
213+ error "spam"
214+serverEnvelopeHandler sockLock (Envelope fprint ResponseMessage msgId messagebs) = do
215+ error "spam2"
216+-}
217+
241218openEnvelope :: forall s. (Serialise s, Typeable s) => Envelope -> Maybe s
242−openEnvelope (Envelope eprint _ _ bytes) =
219+openEnvelope (Envelope eprint _ _ bytes) = do
220+ {-let decoder = case getDecoder (schema (Proxy :: Proxy s)) of
221+ Left err -> error (show err)
222+ Right dec -> dec-}
243223 if eprint == fingerprint (undefined :: s) then
244224 case deserialise bytes of
245225 Left err -> error (show err)
246226 Right v -> Just v
227+ --Just (evalDecoder decoder bytes)
247228 else
248229 Nothing
249230
250−matchEnvelope :: forall a b s. (Serialise a, Serialise b, Typeable b) =>
251− Envelope ->
252− (ConnectionState s -> a -> IO b) ->
253− Maybe (ConnectionState s -> a -> IO b, a)
254−matchEnvelope envelope dispatchf =
255− case openEnvelope envelope :: Maybe a of
256− Nothing -> Nothing
257− Just decoded -> Just (dispatchf, decoded)
258−
259−-- | Called by `serve` to process incoming envelope requests. Never returns, so use `async` to spin it off on another thread.
260231serverEnvelopeHandler ::
261232 Locking Socket
262− -> RequestHandlers s
263− -> s
233+ -> MessageHandlers
264234 -> Envelope
265235 -> IO ()
266−serverEnvelopeHandler _ _ _ (Envelope _ TimeoutResponseMessage _ _) = pure ()
267−serverEnvelopeHandler _ _ _ (Envelope _ ExceptionResponseMessage _ _) = pure ()
268−serverEnvelopeHandler _ _ _ (Envelope _ ResponseMessage _ _) = pure ()
269−serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (RequestMessage timeoutms) msgId _) = do
236+serverEnvelopeHandler sockLock (MessageHandlers msgHandlers) envelope@(Envelope _ RequestMessage msgId _) = do
270237 --find first matching handler
271− let runTimeout :: IO b -> IO (Maybe b)
272− runTimeout m =
273− if timeoutms == 0 then
274− (Just <$> m) `catch` timeoutExcHandler
275− else
276− (timeout (fromIntegral timeoutms) m) `catch` timeoutExcHandler
277− --allow server-side function to throw TimeoutError which is caught here and becomes TimeoutError value
278− timeoutExcHandler :: TimeoutException -> IO (Maybe b)
279− timeoutExcHandler _ = pure Nothing
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"
252+{-
280253
281− sState = ConnectionState {
282− connectionServerState = serverState,
283− connectionSocket = sockLock
284− }
285−
286− firstMatcher (RequestHandler msghandler) Nothing =
287− case matchEnvelope envelope msghandler of
288− Nothing -> pure Nothing
289− Just (dispatchf, decoded) -> do
290− --TODO add exception handling
291− mResponse <- runTimeout (dispatchf sState decoded)
292− let envelopeResponse =
293− case mResponse of
294− Just response ->
295− Envelope (fingerprint response) ResponseMessage msgId (serialise response)
296− Nothing ->
297− Envelope (fingerprint TimeoutError) TimeoutResponseMessage msgId BS.empty
298− sendEnvelope envelopeResponse sockLock
299− pure (Just ())
300− firstMatcher (AsyncRequestHandler msghandler) Nothing =
301− case matchEnvelope envelope msghandler of
302− Nothing -> pure Nothing
303− Just (dispatchf, decoded) -> do
304− _ <- dispatchf sState decoded
305− pure (Just ())
306− firstMatcher _ acc = pure acc
307− eExc <- try $ foldM_ (flip firstMatcher) Nothing msgHandlers :: IO (Either SomeException ())
308− case eExc of
309− Left exc ->
310− let env = Envelope (fingerprint (show exc)) ExceptionResponseMessage msgId (serialise (show exc)) in
311− sendEnvelope env sockLock
312− Right () -> pure ()
313−
254+
255+ --putStrLn $ "GOT MSG " ++ show msg
256+ let --runTimeout :: IO (HandlerResponse resp) -> IO (Maybe (HandlerResponse resp))
257+ runTimeout mTimeout m = case mTimeout of
258+ Nothing -> Just <$> m
259+ Just timeoutMicroseconds -> timeout timeoutMicroseconds m
260+ case msg of
261+ Response{} -> putStrLn "client sent response"
262+ ResponseExpectedRequest requestID mTimeout val -> do
263+ let normalResponder = do
264+ resp <- runTimeout mTimeout (requestMessageHandler val)
265+ case resp of
266+ Just (HandlerResponse responseVal) -> sendMessage (Response requestID responseVal) sockLock
267+ Just NoResponse -> error "attempt to return non-response to expected response message"
268+ Just (HandlerException exc) -> sendMessage (ExceptionResponse @(Message resp) requestID exc) sockLock
269+ Nothing ->
270+ sendMessage (TimedOutResponse @(Message resp) requestID) sockLock
271+ excHandler :: SomeException -> IO ()
272+ excHandler e = do
273+ --send exception to client
274+ sendMessage (ExceptionResponse @(Message resp) requestID (show e)) sockLock
275+ throwIO e
276+ catch normalResponder excHandler
277+ AsyncRequest _ val ->
278+ void $ requestMessageHandler val
279+ --no response necessary
280+ ExceptionResponse{} -> putStrLn "client sent exception response"
281+ TimedOutResponse{} -> putStrLn "client sent timed out response"
282+-}
283+
284+--add callback to allow for responses via socket
285+--type MessageHandler a = Message a -> IO ()
314286
315287type EnvelopeHandler = Envelope -> IO ()
316288
289+type AsyncMessageHandler a = a -> IO ()
290+
317291drainSocketMessages :: Socket -> EnvelopeHandler -> IO ()
318292drainSocketMessages sock envelopeHandler = do
319293 let sockStream = S.unfold readWithBufferOf (1024 * 4, sock)
320− S.drain $ S.serially $ S.parseMany envelopeP sockStream & S.mapM envelopeHandler
294+ S.drain $ serially $ S.parseMany envelopeP sockStream & S.mapM envelopeHandler
321295
322296--send length-tagged bytestring, perhaps should be in network byte order?
323297sendEnvelope :: Envelope -> Locking Socket -> IO ()
@@ -327,16 +301,7 @@ sendEnvelope envelope sockLock = do
327301 --Socket.sendAll syscalls send() on a loop until all the bytes are sent, so we need socket locking here to account for serialized messages of size > PIPE_BUF
328302 withLock sockLock $ \socket' ->
329303 Socket.sendAll socket' fullbytes
330− --traceShowM ("sendEnvelope", envelope)
331304 traceBytes "sendEnvelope" fullbytes
332305
333306fingerprint :: Typeable a => a -> Fingerprint
334307fingerprint = typeRepFingerprint . typeOf
335−
336−fromArray :: Arr.Array Word8 -> BSI.ByteString
337−fromArray Arr.Array {..}
338− | aLen == 0 = mempty
339− | otherwise = BSI.PS aStart 0 aLen
340− where
341− aStartPtr = unsafeForeignPtrToPtr aStart
342− aLen = aEnd `minusPtr` aStartPtr
test/Curryer/Test/Basic.hs+96−135
@@ -1,4 +1,4 @@
1−{-# LANGUAGE DerivingVia, DeriveGeneric, TypeApplications, ExistentialQuantification #-}
1+{-# LANGUAGE DerivingVia, DeriveGeneric, TypeApplications #-}
22module Curryer.Test.Basic where
33import Test.Tasty
44import Test.Tasty.HUnit
@@ -9,133 +9,118 @@ import Network.Socket (SockAddr(..))
99import Control.Concurrent.Async
1010import Control.Monad
1111import Control.Concurrent
12−import Control.Concurrent.STM
13−import Control.Exception
1412import Data.List
1513
1614import Network.RPC.Curryer.Server
1715import Network.RPC.Curryer.Client
1816
17+import Debug.Trace
18+
1919-- TODO: add test for nested calls
2020
2121testTree :: TestTree
2222testTree = 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
28− ,testCase "server state" testServerState
29− ,testCase "request handler throws timeout" testRequestHandlerThrowTimeout
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-}
3028 ]
3129
32−
33−data AddTwoNumbersReq = AddTwoNumbersReq Int Int
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
3437 deriving (Generic, Show)
35− deriving Serialise via WineryVariant AddTwoNumbersReq
38+ deriving Serialise via WineryVariant TestRequest
3639
37−data TestCallMeBackReq = TestCallMeBackReq String
40+--server responds to client
41+data TestResponse = AddTwoNumbersResp Int
42+ | AsyncHello String
43+ | DelayMicrosecondsResp
44+ | RoundtripStringResp String
3845 deriving (Generic, Show, Eq)
39− deriving Serialise via WineryVariant TestCallMeBackReq
40−
41−data TestAsyncReq = TestAsyncReq String
42− deriving (Generic, Show)
43− deriving Serialise via WineryVariant TestAsyncReq
44−
45−data DelayMicrosecondsReq = DelayMicrosecondsReq Int
46− deriving (Generic, Show)
47− deriving Serialise via WineryVariant DelayMicrosecondsReq
48−
49−data RoundtripStringReq = RoundtripStringReq String
50− deriving (Generic, Show)
51− deriving Serialise via WineryVariant RoundtripStringReq
52−
53−data ThrowServerSideExceptionReq = ThrowServerSideExceptionReq
54− deriving (Generic, Show)
55− deriving Serialise via WineryVariant ThrowServerSideExceptionReq
56−
57−data ChangeServerState = ChangeServerState
58− deriving (Generic, Show)
59− deriving Serialise via WineryVariant ChangeServerState
60−
61−--used to server -> client async request
62−data AsyncHelloReq = AsyncHelloReq String
63− deriving (Generic, Show)
64− deriving Serialise via WineryVariant AsyncHelloReq
65−
66−data ThrowTimeout = ThrowTimeout
67− deriving (Generic, Show)
68− deriving Serialise via WineryVariant ThrowTimeout
69−
70−testServerRequestHandlers :: Maybe (MVar String) -> RequestHandlers ()
71−testServerRequestHandlers mAsyncMVar =
72− [ RequestHandler $ \_ (AddTwoNumbersReq x y) -> pure (x + y)
73− , RequestHandler $ \_ (TestCallMeBackReq s) ->
74− case mAsyncMVar of
75− Nothing -> pure ()
76− Just mvar -> putMVar mvar s
77− , AsyncRequestHandler $ \_ (TestAsyncReq v) ->
78− maybe (pure ()) (\mvar -> putMVar mvar v) mAsyncMVar
79− -- an async hello to the server generates an async hello to the client
80− , AsyncRequestHandler $ \sState (AsyncHelloReq s) -> do
81− sendMessage (connectionSocket sState) (AsyncHelloReq s)
82−
83− , RequestHandler $ \_ (RoundtripStringReq s) -> pure s
84− , RequestHandler $ \_ (DelayMicrosecondsReq ms) -> do
46+ deriving Serialise via WineryVariant TestResponse
47+
48+testServerMessageHandlers :: Maybe (MVar String) -> MessageHandlers
49+testServerMessageHandlers 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
8565 threadDelay ms
86− pure ()
87− , RequestHandler $ \_ ThrowServerSideExceptionReq -> do
88− _ <- error "test server exception"
89− pure ()
90− ]
91−
66+ pure (HandlerResponse DelayMicrosecondsResp)
67+ ThrowServerSideExceptionReq -> error "test server exception"
68+-}
9269-- test a simple client-to-server round-trip function execution
9370testSimpleCall :: Assertion
9471testSimpleCall = do
9572 readyVar <- newEmptyMVar
9673
97− server <- async (serve (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))
74+ server <- async (serve (testServerMessageHandlers Nothing) localHostAddr 0 (Just readyVar))
9875 --wait for server to be ready
9976 (SockAddrInet port _) <- takeMVar readyVar
100− conn <- connect [] localHostAddr port
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)
10184 replicateM_ 5 $ do --make five AddTwo calls to shake out parallelism bugs
102− x <- call conn (AddTwoNumbersReq 1 1)
103− assertEqual "server request+response" (Right (2 :: Int)) x
85+ x <- c
86+ assertEqual "server request+response" (Right (AddTwoNumbersResp 2)) x
10487 close conn
10588 cancel server
10689
107−
90+{-
10891--test that the client can proces a server-initiated asynchronous callback from the server
10992testAsyncServerCall :: Assertion
11093testAsyncServerCall = do
11194 portReadyVar <- newEmptyMVar
11295 receivedAsyncMessageVar <- newEmptyMVar
113− let clientAsyncHandlers =
114− [ClientAsyncRequestHandler (\(AsyncHelloReq s) ->
115− putMVar receivedAsyncMessageVar s)]
116− server <- async (serve (testServerRequestHandlers (Just receivedAsyncMessageVar)) emptyServerState localHostAddr 0 (Just portReadyVar))
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))
117102 (SockAddrInet port _) <- takeMVar portReadyVar
118− conn <- connect clientAsyncHandlers localHostAddr port
119− Right () <- asyncCall conn (AsyncHelloReq "welcome")
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
120109 asyncMessage <- takeMVar receivedAsyncMessageVar
121110 assertEqual "async message" "welcome" asyncMessage
122111 close conn
123112 cancel server
124113
125−
126−emptyServerState :: ()
127−emptyServerState = ()
128−
129114--test that the client can make a non-blocking call
130115testAsyncClientCall :: Assertion
131116testAsyncClientCall = do
132117 portReadyVar <- newEmptyMVar
133118 receivedAsyncMessageVar <- newEmptyMVar
134119
135− server <- async (serve (testServerRequestHandlers (Just receivedAsyncMessageVar)) emptyServerState localHostAddr 0 (Just portReadyVar))
120+ server <- async (serve (pure Nothing) (testServerMessageHandler (Just receivedAsyncMessageVar)) localHostAddr 0 (Just portReadyVar))
136121 (SockAddrInet port _) <- takeMVar portReadyVar
137−
138− conn <- connect [] localHostAddr port
122+
123+ conn <- connect @TestResponse (\_ -> pure ()) localHostAddr port
139124 --send an async message, wait for an async response to confirm receipt
140125 Right () <- asyncCall conn (TestCallMeBackReq "hi server")
141126 asyncMessage <- takeMVar receivedAsyncMessageVar
@@ -147,30 +132,37 @@ testSyncClientCallTimeout :: Assertion
147132testSyncClientCallTimeout = do
148133 readyVar <- newEmptyMVar
149134
150− server <- async (serve (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))
135+ server <- async (serve (pure Nothing) (testServerMessageHandler Nothing) localHostAddr 0 (Just readyVar))
151136 --wait for server to be ready
152137 (SockAddrInet port _) <- takeMVar readyVar
153− conn <- connect [] localHostAddr port
154− x <- callTimeout @_ @Int (Just 500) conn (DelayMicrosecondsReq 1000)
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
155146 assertEqual "client sync timeout" (Left TimeoutError) x
156147 close conn
157148 cancel server
158−
149+
159150testSyncException :: Assertion
160151testSyncException = do
161152 readyVar <- newEmptyMVar
162153
163− server <- async (serve (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))
154+ server <- async (serve (pure Nothing) (testServerMessageHandler Nothing) localHostAddr 0 (Just readyVar))
164155 (SockAddrInet port _) <- takeMVar readyVar
165−
166− conn <- connect [] localHostAddr port
156+ let clientHandler :: AsyncMessageHandler TestResponse
157+ clientHandler _ = error "async handler called"
158+ mkConn :: IO (Connection TestResponse)
159+ mkConn = connect clientHandler localHostAddr port
160+ conn <- mkConn
167161 ret <- call conn ThrowServerSideExceptionReq
168162 case ret of
169163 Left (ExceptionError actualExc) ->
170164 assertBool "server-side exception" ("test server exception" `isPrefixOf` actualExc)
171− Left otherErr ->
172− assertFailure ("server-side error " <> show otherErr)
173− Right () -> assertFailure "missed exception"
165+ Right _ -> assertFailure "missed exception"
174166 close conn
175167 cancel server
176168
@@ -179,50 +171,19 @@ testMultithreadedClient :: Assertion
179171testMultithreadedClient = do
180172 readyVar <- newEmptyMVar
181173
182− server <- async (serve (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))
174+ server <- async (serve (pure Nothing) (testServerMessageHandler Nothing) localHostAddr 0 (Just readyVar))
183175 (SockAddrInet port _) <- takeMVar readyVar
184− conn <- connect [] localHostAddr port
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
185181 let bigString = replicate (1024 * 1000) 'x'
186182 replicateM_ 10 $ do
187183 ret <- call conn (RoundtripStringReq bigString)
188− assertEqual "big string multithread" (Right bigString) ret
189− --putStrLn "plus one"
190− close conn
191− cancel server
192−
193−testServerState :: Assertion
194−testServerState = do
195− readyVar <- newEmptyMVar
196−
197− let serverHandlers = [RequestHandler (\sState ChangeServerState ->
198− atomically $
199− modifyTVar (connectionServerState sState) (+ 1))
200− ]
201− serverState <- newTVarIO @Int 1
202− server <- async (serve serverHandlers serverState localHostAddr 0 (Just readyVar))
203− (SockAddrInet port _) <- takeMVar readyVar
204− conn <- connect [] localHostAddr port
205− ret <- call conn ChangeServerState
206− assertEqual "server ret" (Right ()) ret
207− serverState' <- readTVarIO serverState
208− assertEqual "server state" 2 serverState'
209− close conn
210− cancel server
211−
212−--test that the request handler can throw a TimeoutError exception which is converted into a TimeoutError response
213−testRequestHandlerThrowTimeout :: Assertion
214−testRequestHandlerThrowTimeout = do
215− readyVar <- newEmptyMVar
216−
217− let serverHandlers = [RequestHandler (\_ ThrowTimeout ->
218− throw TimeoutException >> pure (1 :: Int)
219− )
220− ]
221− server <- async (serve serverHandlers () localHostAddr 0 (Just readyVar))
222− (SockAddrInet port _) <- takeMVar readyVar
223− conn <- connect [] localHostAddr port
224− ret <- call @_ @Int conn ThrowTimeout
225− assertEqual "handler timeout exception" (Left TimeoutError) ret
184+ assertEqual "big string multithread" (Right (RoundtripStringResp bigString)) ret
185+ putStrLn "plus one"
226186 close conn
227187 cancel server
228−
188+
189+-}