on master help sign in
support passing exceptions as strings from server handlers to client

compilation checkpoint

commit9b7209e3e09b0a39b0984cec7010b711521a4548 authorAgentM <agentm@themactionfaction.com> dateFri 18 Dec 2020, 12:48 (6 years ago) parent88a59ff
4 files changed+123−142
curryer.cabal+2 src/Network/RPC/Curryer/Client.hs+19−5 src/Network/RPC/Curryer/Server.hs+65−103 test/Curryer/Test/Basic.hs+37−34
curryer.cabal+2
@@ -33,6 +33,7 @@ Library
3333 , hashable
3434 , time
3535 , network-byte-order
36+ , stm
3637 Hs-Source-Dirs: ./src
3738 Default-Language: Haskell2010
3839 ghc-options: -Wall -fwarn-unused-binds -fwarn-unused-imports
@@ -54,6 +55,7 @@ Test-Suite test
5455 , winery
5556 , network
5657 , async
58+ , stm
5759 other-modules: Curryer.Test.Basic
5860
5961Benchmark perf
src/Network/RPC/Curryer/Client.hs+19−5
@@ -68,11 +68,11 @@ clientEnvelopeHandler ::
6868 -> IO ()
6969clientEnvelopeHandler handlers _ _ envelope@(Envelope _ (RequestMessage _) _ _) = do
7070 --should this run off on another green thread?
71− let firstMatcher Nothing (ClientAsyncRequestHandler dispatchf) = do
72− case matchEnvelope envelope dispatchf of
71+ let firstMatcher Nothing (ClientAsyncRequestHandler (dispatchf :: a -> IO ())) = do
72+ case openEnvelope envelope of
7373 Nothing -> pure Nothing
74− Just (dispatchf', decoded) -> do
75− dispatchf' decoded
74+ Just decoded -> do
75+ dispatchf decoded
7676 pure (Just ())
7777 firstMatcher acc _ = pure acc
7878 foldM_ firstMatcher Nothing handlers
@@ -95,6 +95,19 @@ clientEnvelopeHandler _ _ syncMap (Envelope _ TimeoutResponseMessage msgId _) =
9595 Nothing -> error ("dropping unrequested timeout response " <> show msgId)
9696 Just (mVar, _) ->
9797 putMVar mVar (Left TimeoutError)
98+clientEnvelopeHandler _ _ syncMap (Envelope _ ExceptionResponseMessage msgId excPayload) = do
99+ match <- atomically $ do
100+ val' <- STMMap.lookup msgId syncMap
101+ STMMap.delete msgId syncMap
102+ pure val'
103+ case match of
104+ Nothing -> error ("dropping unrequested timeout response " <> show msgId)
105+ Just (mVar, _) ->
106+ case deserialise excPayload of
107+ Left err -> error ("failed to deserialise exception string" <> show err)
108+ Right excStr ->
109+ putMVar mVar (Left (ExceptionError excStr))
110+
98111
99112call :: (Serialise request, Serialise response) => Connection -> request -> IO (Either ConnectionError response)
100113call = callTimeout Nothing
@@ -126,7 +139,8 @@ callTimeout mTimeout conn msg = do
126139 --timeout
127140 Nothing ->
128141 pure (Left TimeoutError)
129− Just (Left exc) -> error ("exception in client from server: " <> show exc)
142+ Just (Left exc) ->
143+ pure (Left exc)
130144 Just (Right binmsg) ->
131145 --TODO use decoder instead
132146 case deserialise binmsg of
src/Network/RPC/Curryer/Server.hs+65−103
@@ -25,7 +25,9 @@ import Streamly.Data.Fold as FL
2525import qualified Streamly.Internal.Data.Stream.IsStream as S
2626import qualified Data.Binary as B
2727import qualified Data.UUID as UUIDBase
28+import qualified Data.UUID.V4 as UUIDBase
2829import Control.Monad
30+import Control.Concurrent.STM
2931
3032import qualified Network.RPC.Curryer.StreamlyAdditions as SA
3133--import Control.Monad
@@ -88,41 +90,32 @@ deriving via WineryVariant Fingerprint instance Serialise Fingerprint
8890data MessageType = RequestMessage TimeoutMicroseconds
8991 | ResponseMessage
9092 | TimeoutResponseMessage
93+ | ExceptionResponseMessage
9194 deriving (Generic, Show)
9295 deriving Serialise via WineryVariant MessageType
9396
94−type MessageHandlers = [RequestHandler]
97+type MessageHandlers serverState = [RequestHandler serverState]
9598
96−--newtype RequestHandler = RequestHandler { handler :: forall a b. (Serialise a, Serialise b) => (a -> IO b) }
97−
98−data RequestHandler where
99+data RequestHandler serverState where
99100 -- | create a request handler with a response
100− RequestHandler :: forall a b. (Serialise a, Serialise b) => (a -> IO b) -> RequestHandler
101+ RequestHandler :: forall a b serverState. (Serialise a, Serialise b) => (ConnectionState serverState -> a -> IO b) -> RequestHandler serverState
101102 -- | 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+ AsyncRequestHandler :: forall a serverState. Serialise a => (ConnectionState serverState -> a -> IO ()) -> RequestHandler serverState
104+
105+-- | Passed to
106+data ConnectionState a = ConnectionState {
107+ connectionServerState :: STM a,
108+ connectionSocket :: Locking Socket
109+ }
110+
111+-- | 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.
112+sendMessage :: Serialise a => ConnectionState s -> a -> IO ()
113+sendMessage sState msg = do
114+ requestID <- UUID <$> UUIDBase.nextRandom
115+ let env =
116+ Envelope (fingerprint msg) (RequestMessage 0) requestID (serialise msg)
117+ sendEnvelope env (connectionSocket sState)
103118
104−{-
105−handleCall :: forall a b. (Serialise a, Serialise b)
106− => (a -> IO b)
107− -> RequestHandler
108−handleCall f = RequestHandler f
109−-}
110−{-data Message a = Response UUID a
111− | AsyncRequest UUID a
112− | ResponseExpectedRequest UUID (Maybe Timeout) a
113− | TimedOutResponse UUID
114− | ExceptionResponse UUID String
115− deriving (Generic, Show)
116− deriving Serialise via WineryVariant (Message a)
117−
118−-- | The response type
119−
120−data HandlerResponse a = NoResponse
121− | HandlerResponse a
122− | HandlerException String
123− deriving Generic
124− deriving Serialise via WineryVariant (HandlerResponse a)
125−-}
126119--avoid orphan instance
127120newtype UUID = UUID { _unUUID :: UUIDBase.UUID }
128121 deriving (Show, Eq, B.Binary, Hashable)
@@ -161,7 +154,8 @@ envelopeP = do
161154 msgTypeP = (P.satisfy (== 0) *>
162155 (RequestMessage . fromIntegral <$> word32P)) `P.alt`
163156 (P.satisfy (== 1) *> pure ResponseMessage) `P.alt`
164− (P.satisfy (== 2) *> pure TimeoutResponseMessage)
157+ (P.satisfy (== 2) *> pure TimeoutResponseMessage) `P.alt`
158+ (P.satisfy (== 3) *> pure ExceptionResponseMessage)
165159 lenPrefixedByteStringP = do
166160 c <- fromIntegral <$> word32P
167161 BS.pack <$> P.takeEQ c s
@@ -176,6 +170,7 @@ encodeEnvelope (Envelope (Fingerprint fp1 fp2) msgType msgId bs) =
176170 RequestMessage timeoutms -> BS.singleton 0 <> BO.bytestring32 (fromIntegral timeoutms)
177171 ResponseMessage -> BS.singleton 1
178172 TimeoutResponseMessage -> BS.singleton 2
173+ ExceptionResponseMessage -> BS.singleton 3
179174 msgIdBs =
180175 case UUIDBase.toWords (_unUUID msgId) of
181176 (u1, u2, u3, u4) -> foldr (<>) BS.empty (map BO.bytestring32 [u1, u2, u3, u4])
@@ -215,44 +210,34 @@ type NewConnectionHandler msg = IO (Maybe msg)
215210type NewMessageHandler req resp = req -> IO resp
216211
217212serve ::
218− MessageHandlers ->
213+ MessageHandlers s->
214+ STM s ->
219215 HostAddr ->
220216 PortNumber ->
221217 Maybe (MVar SockAddr) ->
222218 IO Bool
223−serve userMsgHandlers hostaddr port mSockLock = do
219+serve userMsgHandlers serverState hostaddr port mSockLock = do
224220 let
225221 handleSock sock = do
226222 lockingSocket <- newLock sock
227− drainSocketMessages sock (serverEnvelopeHandler lockingSocket userMsgHandlers)
223+ drainSocketMessages sock (serverEnvelopeHandler lockingSocket userMsgHandlers serverState)
228224
229225 S.serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & S.parallely . S.mapM (handleWithM handleSock) & S.drain
230226 pure True
231227
232−{-serverEnvelopeHandler :: Locking Socket -> MessageHandlers s -> Envelope -> IO ()
233−serverEnvelopeHandler sockLock (Envelope fprint RequestMessage msgId messagebs) = do
234− error "spam"
235−serverEnvelopeHandler sockLock (Envelope fprint ResponseMessage msgId messagebs) = do
236− error "spam2"
237−-}
238−
239228openEnvelope :: forall s. (Serialise s, Typeable s) => Envelope -> Maybe s
240229openEnvelope (Envelope eprint _ _ bytes) = do
241− {-let decoder = case getDecoder (schema (Proxy :: Proxy s)) of
242− Left err -> error (show err)
243− Right dec -> dec-}
244230 if eprint == fingerprint (undefined :: s) then
245231 case deserialise bytes of
246232 Left err -> error (show err)
247233 Right v -> Just v
248− --Just (evalDecoder decoder bytes)
249234 else
250235 Nothing
251236
252−matchEnvelope :: forall a b. (Serialise a, Serialise b, Typeable b) =>
237+matchEnvelope :: forall a b s. (Serialise a, Serialise b, Typeable b) =>
253238 Envelope ->
254− (a -> IO b) ->
255− Maybe (a -> IO b, a)
239+ (ConnectionState s -> a -> IO b) ->
240+ Maybe (ConnectionState s -> a -> IO b, a)
256241matchEnvelope envelope dispatchf =
257242 case openEnvelope envelope :: Maybe a of
258243 Nothing -> Nothing
@@ -260,77 +245,54 @@ matchEnvelope envelope dispatchf =
260245
261246serverEnvelopeHandler ::
262247 Locking Socket
263− -> MessageHandlers
248+ -> MessageHandlers s
249+ -> STM s
264250 -> Envelope
265251 -> IO ()
266−serverEnvelopeHandler _ _ (Envelope _ TimeoutResponseMessage _ _) = pure ()
267−serverEnvelopeHandler sockLock msgHandlers envelope@(Envelope _ (RequestMessage timeoutms) msgId _) = do
252+serverEnvelopeHandler _ _ _ (Envelope _ TimeoutResponseMessage _ _) = pure ()
253+serverEnvelopeHandler _ _ _ (Envelope _ ExceptionResponseMessage _ _) = pure ()
254+serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (RequestMessage timeoutms) msgId _) = do
268255 --find first matching handler
269256 let runTimeout :: IO b -> IO (Maybe b)
270− runTimeout m =
257+ runTimeout m =
271258 if timeoutms == 0 then
272259 Just <$> m
273− else
274− timeout (fromIntegral timeoutms) m
275− firstMatcher (RequestHandler (msghandler :: a -> IO b)) Nothing =
260+ else
261+ timeout (fromIntegral timeoutms) m
262+
263+ sState = ConnectionState {
264+ connectionServerState = serverState,
265+ connectionSocket = sockLock
266+ }
267+
268+ firstMatcher (RequestHandler msghandler) Nothing =
276269 case matchEnvelope envelope msghandler of
277270 Nothing -> pure Nothing
278− Just (dispatchf, decoded) ->
271+ Just (dispatchf, decoded) -> do
279272 --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 =
273+ mResponse <- runTimeout (dispatchf sState decoded)
274+ let envelopeResponse =
275+ case mResponse of
276+ Just response ->
277+ Envelope (fingerprint response) ResponseMessage msgId (serialise response)
278+ Nothing ->
279+ Envelope (fingerprint TimeoutError) (TimeoutResponseMessage) msgId (BS.empty)
280+ sendEnvelope envelopeResponse sockLock
281+ pure (Just ())
282+ firstMatcher (AsyncRequestHandler msghandler) Nothing =
290283 case matchEnvelope envelope msghandler of
291284 Nothing -> pure Nothing
292285 Just (dispatchf, decoded) -> do
293− _ <- dispatchf decoded
286+ _ <- dispatchf sState decoded
294287 pure (Just ())
295288 firstMatcher _ acc = pure acc
296− foldM_ (flip firstMatcher) Nothing msgHandlers
297−serverEnvelopeHandler _ _ (Envelope _ ResponseMessage _ _) = error "server received response message"
298−
299−
300−{-
301−
302−
303− --putStrLn $ "GOT MSG " ++ show msg
304− let --runTimeout :: IO (HandlerResponse resp) -> IO (Maybe (HandlerResponse resp))
305− runTimeout mTimeout m = case mTimeout of
306− Nothing -> Just <$> m
307− Just timeoutMicroseconds -> timeout timeoutMicroseconds m
308− case msg of
309− Response{} -> putStrLn "client sent response"
310− ResponseExpectedRequest requestID mTimeout val -> do
311− let normalResponder = do
312− resp <- runTimeout mTimeout (requestMessageHandler val)
313− case resp of
314− Just (HandlerResponse responseVal) -> sendMessage (Response requestID responseVal) sockLock
315− Just NoResponse -> error "attempt to return non-response to expected response message"
316− Just (HandlerException exc) -> sendMessage (ExceptionResponse @(Message resp) requestID exc) sockLock
317− Nothing ->
318− sendMessage (TimedOutResponse @(Message resp) requestID) sockLock
319− excHandler :: SomeException -> IO ()
320− excHandler e = do
321− --send exception to client
322− sendMessage (ExceptionResponse @(Message resp) requestID (show e)) sockLock
323− throwIO e
324− catch normalResponder excHandler
325− AsyncRequest _ val ->
326− void $ requestMessageHandler val
327− --no response necessary
328− ExceptionResponse{} -> putStrLn "client sent exception response"
329− TimedOutResponse{} -> putStrLn "client sent timed out response"
330−-}
331−
332−--add callback to allow for responses via socket
333−--type MessageHandler a = Message a -> IO ()
289+ eExc <- try $ foldM_ (flip firstMatcher) Nothing msgHandlers :: IO (Either SomeException ())
290+ case eExc of
291+ Left exc ->
292+ let env = Envelope (fingerprint (show exc)) ExceptionResponseMessage msgId (serialise (show exc)) in
293+ sendEnvelope env sockLock
294+ Right () -> pure ()
295+serverEnvelopeHandler _ _ _ (Envelope _ ResponseMessage _ _) = error "server received response message"
334296
335297type EnvelopeHandler = Envelope -> IO ()
336298
test/Curryer/Test/Basic.hs+37−34
@@ -9,6 +9,8 @@ import Network.Socket (SockAddr(..))
99import Control.Concurrent.Async
1010import Control.Monad
1111import Control.Concurrent
12+import Control.Concurrent.STM
13+import Data.List
1214
1315import Network.RPC.Curryer.Server
1416import Network.RPC.Curryer.Client
@@ -17,11 +19,11 @@ import Network.RPC.Curryer.Client
1719
1820testTree :: TestTree
1921testTree = testGroup "basic" [testCase "simple" testSimpleCall
20− --,testCase "client async" testAsyncServerCall
22+ ,testCase "client async" testAsyncServerCall
2123 ,testCase "server async" testAsyncClientCall
22− --,testCase "client sync timeout" testSyncClientCallTimeout
23− --,testCase "server-side exception" testSyncException
24− ,testCase "multi-threaded client" testMultithreadedClient
24+ ,testCase "client sync timeout" testSyncClientCallTimeout
25+ ,testCase "server-side exception" testSyncException
26+ ,testCase "multi-threaded client" testMultithreadedClient
2527 ]
2628
2729
@@ -50,27 +52,28 @@ data ThrowServerSideExceptionReq = ThrowServerSideExceptionReq
5052 deriving Serialise via WineryVariant ThrowServerSideExceptionReq
5153
5254--used to server -> client async request
53−data AsyncHelloReq = AsyncHelloReq
55+data AsyncHelloReq = AsyncHelloReq String
5456 deriving (Generic, Show)
5557 deriving Serialise via WineryVariant AsyncHelloReq
5658
57−testServerMessageHandlers :: Maybe (MVar String) -> MessageHandlers
59+testServerMessageHandlers :: Maybe (MVar String) -> MessageHandlers ()
5860testServerMessageHandlers mAsyncMVar =
59− [ RequestHandler $ \(AddTwoNumbersReq x y) -> pure (x + y)
60− , RequestHandler $ \(TestCallMeBackReq s) ->
61+ [ RequestHandler $ \_ (AddTwoNumbersReq x y) -> pure (x + y)
62+ , RequestHandler $ \_ (TestCallMeBackReq s) ->
6163 case mAsyncMVar of
6264 Nothing -> pure ()
6365 Just mvar -> putMVar mvar s
64− , AsyncRequestHandler $ \(TestAsyncReq v) ->
66+ , AsyncRequestHandler $ \_ (TestAsyncReq v) ->
6567 maybe (pure ()) (\mvar -> putMVar mvar v) mAsyncMVar
6668 -- an async hello to the server generates an async hello to the client
67− --, AsyncRequestHandler $ \(AsyncReq s) -> currently lacking server state to message client
69+ , AsyncRequestHandler $ \sState (AsyncHelloReq s) -> do
70+ sendMessage sState (AsyncHelloReq s)
6871
69− , RequestHandler $ \(RoundtripStringReq s) -> pure s
70− , RequestHandler $ \(DelayMicrosecondsReq ms) -> do
72+ , RequestHandler $ \_ (RoundtripStringReq s) -> pure s
73+ , RequestHandler $ \_ (DelayMicrosecondsReq ms) -> do
7174 threadDelay ms
7275 pure ()
73− , RequestHandler $ \ThrowServerSideExceptionReq -> do
76+ , RequestHandler $ \_ ThrowServerSideExceptionReq -> do
7477 _ <- error "test server exception"
7578 pure ()
7679 ]
@@ -80,7 +83,7 @@ testSimpleCall :: Assertion
8083testSimpleCall = do
8184 readyVar <- newEmptyMVar
8285
83− server <- async (serve (testServerMessageHandlers Nothing) localHostAddr 0 (Just readyVar))
86+ server <- async (serve (testServerMessageHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))
8487 --wait for server to be ready
8588 (SockAddrInet port _) <- takeMVar readyVar
8689 conn <- connect [] localHostAddr port
@@ -92,23 +95,25 @@ testSimpleCall = do
9295
9396
9497--test that the client can proces a server-initiated asynchronous callback from the server
95−{-
9698testAsyncServerCall :: Assertion
9799testAsyncServerCall = do
98100 portReadyVar <- newEmptyMVar
99101 receivedAsyncMessageVar <- newEmptyMVar
100102 let clientAsyncHandlers =
101− [ClientAsyncRequestHandler (\AsyncHelloReq ->
102− putMVar receivedAsyncMessageVar "")]
103− server <- async (serve clientAsyncHandlers (testServerMessageHandlers receivedAsyncMessageVar) localHostAddr 0 (Just portReadyVar))
103+ [ClientAsyncRequestHandler (\(AsyncHelloReq s) ->
104+ putMVar receivedAsyncMessageVar s)]
105+ server <- async (serve (testServerMessageHandlers (Just receivedAsyncMessageVar)) emptyServerState localHostAddr 0 (Just portReadyVar))
104106 (SockAddrInet port _) <- takeMVar portReadyVar
105− conn <- connect @TestResponse clientHandler localHostAddr port
106− callAsync conn TestAsyncReq
107+ conn <- connect clientAsyncHandlers localHostAddr port
108+ Right () <- asyncCall conn (AsyncHelloReq "welcome")
107109 asyncMessage <- takeMVar receivedAsyncMessageVar
108110 assertEqual "async message" "welcome" asyncMessage
109111 close conn
110112 cancel server
111−-}
113+
114+
115+emptyServerState :: STM ()
116+emptyServerState = pure ()
112117
113118--test that the client can make a non-blocking call
114119testAsyncClientCall :: Assertion
@@ -116,7 +121,7 @@ testAsyncClientCall = do
116121 portReadyVar <- newEmptyMVar
117122 receivedAsyncMessageVar <- newEmptyMVar
118123
119− server <- async (serve (testServerMessageHandlers (Just receivedAsyncMessageVar)) localHostAddr 0 (Just portReadyVar))
124+ server <- async (serve (testServerMessageHandlers (Just receivedAsyncMessageVar)) emptyServerState localHostAddr 0 (Just portReadyVar))
120125 (SockAddrInet port _) <- takeMVar portReadyVar
121126
122127 conn <- connect [] localHostAddr port
@@ -131,7 +136,7 @@ testSyncClientCallTimeout :: Assertion
131136testSyncClientCallTimeout = do
132137 readyVar <- newEmptyMVar
133138
134− server <- async (serve (testServerMessageHandlers Nothing) localHostAddr 0 (Just readyVar))
139+ server <- async (serve (testServerMessageHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))
135140 --wait for server to be ready
136141 (SockAddrInet port _) <- takeMVar readyVar
137142 conn <- connect [] localHostAddr port
@@ -140,32 +145,30 @@ testSyncClientCallTimeout = do
140145 close conn
141146 cancel server
142147
143−{-
144148testSyncException :: Assertion
145149testSyncException = do
146150 readyVar <- newEmptyMVar
147151
148− server <- async (serve (pure Nothing) (testServerMessageHandler Nothing) localHostAddr 0 (Just readyVar))
152+ server <- async (serve (testServerMessageHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))
149153 (SockAddrInet port _) <- takeMVar readyVar
150− let clientHandler :: AsyncMessageHandler TestResponse
151− clientHandler _ = error "async handler called"
152− mkConn :: IO (Connection TestResponse)
153− mkConn = connect clientHandler localHostAddr port
154− conn <- mkConn
154+
155+ conn <- connect [] localHostAddr port
155156 ret <- call conn ThrowServerSideExceptionReq
156157 case ret of
157158 Left (ExceptionError actualExc) ->
158159 assertBool "server-side exception" ("test server exception" `isPrefixOf` actualExc)
159− Right _ -> assertFailure "missed exception"
160+ Left otherErr ->
161+ assertFailure ("server-side error " <> show otherErr)
162+ Right () -> assertFailure "missed exception"
160163 close conn
161164 cancel server
162−-}
165+
163166--throw large messages (> PIPE_BUF) at the server from multiple client connections to exercise the socket lock
164167testMultithreadedClient :: Assertion
165168testMultithreadedClient = do
166169 readyVar <- newEmptyMVar
167170
168− server <- async (serve (testServerMessageHandlers Nothing) localHostAddr 0 (Just readyVar))
171+ server <- async (serve (testServerMessageHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))
169172 (SockAddrInet port _) <- takeMVar readyVar
170173 {-let clientHandler :: AsyncMessageHandler TestResponse
171174 clientHandler _ = error "async handler called"-}
@@ -174,7 +177,7 @@ testMultithreadedClient = do
174177 replicateM_ 10 $ do
175178 ret <- call conn (RoundtripStringReq bigString)
176179 assertEqual "big string multithread" (Right bigString) ret
177− putStrLn "plus one"
180+ --putStrLn "plus one"
178181 close conn
179182 cancel server
180183