@@ -1,20 +1,18 @@
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 #-}
2−{-# OPTIONS_GHC -fno-warn-orphans #-}
3{- HLINT ignore "Use lambda-case" -}
2{- HLINT ignore "Use lambda-case" -}
4module Network.RPC.Curryer.Server where
3module Network.RPC.Curryer.Server where
5import qualified Streamly.Prelude as S
5import qualified Streamly.Prelude as S
6import Streamly.Network.Socket
6import Streamly.Network.Socket
7import Streamly.Internal.Network.Socket (handleWithM)
7import Streamly.Internal.Network.Socket (handleWithM)
8import Network.Socket as Socket
8import Network.Socket as Socket
9import Network.Socket.ByteString as Socket
9import Network.Socket.ByteString as Socket
10−import Streamly.Internal.Data.Parser as P hiding (concatMap)
10+import Streamly.Internal.Data.Parser as P
11import Codec.Winery
11import Codec.Winery
12import Codec.Winery.Internal (varInt, decodeVarInt, getBytes)
12import Codec.Winery.Internal (varInt, decodeVarInt, getBytes)
13import Codec.Winery.Class (mkExtractor)
13import Codec.Winery.Class (mkExtractor)
14import GHC.Generics
14import GHC.Generics
15−import GHC.Fingerprint
17−import Control.Concurrent.MVar (MVar, newMVar, withMVar)
15+import Control.Concurrent.MVar (MVar)
18import Control.Exception
16import Control.Exception
19import Data.Function ((&))
17import Data.Function ((&))
20import Data.Word
18import Data.Word
@@ -26,23 +24,21 @@ import qualified Streamly.Internal.Data.Stream.IsStream as S
26import qualified Data.Binary as B
24import qualified Data.Binary as B
27import qualified Data.UUID as UUIDBase
25import qualified Data.UUID as UUIDBase
28import qualified Data.UUID.V4 as UUIDBase
26import qualified Data.UUID.V4 as UUIDBase
32import qualified Network.RPC.Curryer.StreamlyAdditions as SA
28import qualified Network.RPC.Curryer.StreamlyAdditions as SA
33−--import Control.Monad
29+import Control.Monad
34import Data.Hashable
30import Data.Hashable
35import System.Timeout
31import System.Timeout
36import qualified Network.ByteOrder as BO
32import qualified Network.ByteOrder as BO
39-- for toArrayS conversion
35-- for toArrayS conversion
40−import qualified Data.ByteString.Internal as BSI
36+{-import qualified Data.ByteString.Internal as BSI
41−import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as Arr
37+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as SA
38+import Foreign.ForeignPtr (plusForeignPtr)
42import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
39import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
43−import GHC.Ptr (minusPtr)
40+import GHC.Ptr (plusPtr)
45−-- define CURRYER_SHOW_BYTES 1
46#if CURRYER_SHOW_BYTES == 1
42#if CURRYER_SHOW_BYTES == 1
47import Debug.Trace
43import Debug.Trace
@@ -54,72 +50,23 @@ traceBytes msg bs = traceShowM (msg, BS.length bs, bs)
54traceBytes _ _ = pure ()
50traceBytes _ _ = pure ()
57−data Locking a = Locking (MVar ()) a
59−newLock :: a -> IO (Locking a)
62− pure (Locking lock x)
64−withLock :: Locking a -> (a -> IO b) -> IO b
65−withLock (Locking mvar v) m =
66− withMVar mvar $ \_ -> m v
68−lockless :: Locking a -> a
69−lockless (Locking _ a) = a
71−type Timeout = Word32
53+type Timeout = Int
73−type BinaryMessage = BS.ByteString
75−--includes the fingerprint of the incoming data type (held in the BinaryMessage) to determine how to dispatch the message.
55+data Message a = Response UUID a
76−--add another envelope type for unencoded binary messages for any easy optimization for in-process communication
56+ | AsyncRequest UUID a
77−data Envelope = Envelope {
57+ | ResponseExpectedRequest UUID (Maybe Timeout) a
78− envFingerprint :: !Fingerprint,
58+ | TimedOutResponse UUID
79− envMessageType :: !MessageType,
59+ | ExceptionResponse UUID String
81− envPayload :: !BinaryMessage
83− deriving (Generic, Show)
60+ deriving (Generic, Show)
61+ deriving Serialise via WineryVariant (Message a)
85−type TimeoutMicroseconds = Int
63+-- | The response type
64+data HandlerResponse a = NoResponse
66+ | HandlerException String
68+ deriving Serialise via WineryVariant (HandlerResponse a)
87−deriving instance Generic Fingerprint
88−deriving via WineryVariant Fingerprint instance Serialise Fingerprint
90−-- | Internal type used to mark envelope types.
91−data MessageType = RequestMessage TimeoutMicroseconds
93− | TimeoutResponseMessage
94− | ExceptionResponseMessage
95− deriving (Generic, Show)
96− deriving Serialise via WineryVariant MessageType
98−-- | A list of `RequestHandler`s.
99−type RequestHandlers serverState = [RequestHandler serverState]
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
108−-- | Server state sent in via `serve` and passed to `RequestHandler`s.
109−data ConnectionState a = ConnectionState {
110− connectionServerState :: a,
111− connectionSocket :: Locking Socket
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
119− Envelope (fingerprint msg) (RequestMessage timeout') requestID (serialise msg)
121− sendEnvelope env lockSock
123--avoid orphan instance
70--avoid orphan instance
124newtype UUID = UUID { _unUUID :: UUIDBase.UUID }
71newtype UUID = UUID { _unUUID :: UUIDBase.UUID }
125 deriving (Show, Eq, B.Binary, Hashable)
72 deriving (Show, Eq, B.Binary, Hashable)
@@ -138,18 +85,13 @@ instance Serialise UUID where
138 x -> error $ "invalid schema element " <> show x
85 x -> error $ "invalid schema element " <> show x
139 decodeCurrent = B.decode . BSL.fromStrict <$> (decodeVarInt >>= getBytes)
86 decodeCurrent = B.decode . BSL.fromStrict <$> (decodeVarInt >>= getBytes)
141−-- | Errors from remote calls.
88+
142data ConnectionError = CodecError String -- show of WineryException from exception initiator which cannot otherwise be transmitted over a line due to dependencies on TypeReps
89data ConnectionError = CodecError String -- show of WineryException from exception initiator which cannot otherwise be transmitted over a line due to dependencies on TypeReps
143 | TimeoutError
90 | TimeoutError
144 | ExceptionError String
91 | ExceptionError String
145 deriving (Generic, Show, Eq)
92 deriving (Generic, Show, Eq)
146 deriving Serialise via WineryVariant ConnectionError
93 deriving Serialise via WineryVariant ConnectionError
148−data TimeoutException = TimeoutException
151−instance Exception TimeoutException
153type HostAddr = (Word8, Word8, Word8, Word8)
95type HostAddr = (Word8, Word8, Word8, Word8)
155allHostAddrs,localHostAddr :: HostAddr
97allHostAddrs,localHostAddr :: HostAddr
@@ -157,186 +99,129 @@ allHostAddrs = (0,0,0,0)
157localHostAddr = (127,0,0,1)
99localHostAddr = (127,0,0,1)
159-- Each message is length-prefixed by a 32-bit unsigned length.
101-- Each message is length-prefixed by a 32-bit unsigned length.
160−envelopeP :: Parser IO Word8 Envelope
102+messageBoundaryP :: Parser IO Word8 BS.ByteString
161−envelopeP = do
103+messageBoundaryP = 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)
167− lenPrefixedByteStringP = do
168− c <- fromIntegral <$> word32P
169− --streamly can't handle takeEQ 0, so add special handling
173− fromArray <$> P.takeEQ c (Arr.writeN c)
174− Envelope <$> fingerprintP <*> msgTypeP <*> uuidP <*> lenPrefixedByteStringP
176−encodeEnvelope :: Envelope -> BS.ByteString
177−encodeEnvelope (Envelope (Fingerprint fp1 fp2) msgType msgId bs) =
178− fingerprintBs <> msgTypeBs <> msgIdBs <> lenPrefixedBs
180− fingerprintBs = BO.bytestring64 fp1 <> BO.bytestring64 fp2
181− msgTypeBs = case msgType of
182− RequestMessage timeoutms -> BS.singleton 0 <> BO.bytestring32 (fromIntegral timeoutms)
183− ResponseMessage -> BS.singleton 1
184− TimeoutResponseMessage -> BS.singleton 2
185− ExceptionResponseMessage -> BS.singleton 3
187− case UUIDBase.toWords (_unUUID msgId) of
188− (u1, u2, u3, u4) -> foldr ((<>) . BO.bytestring32) BS.empty [u1, u2, u3, u4]
189− msgLen = fromIntegral (BS.length bs)
190− lenPrefixedBs = BO.bytestring32 msgLen <> bs
192−fingerprintP :: Parser IO Word8 Fingerprint
194− Fingerprint <$> word64P <*> word64P
196−word64P :: Parser IO Word8 Word64
198 let s = FL.toList
104 let s = FL.toList
199− b <- P.takeEQ 8 s
105+ w4x8 <- P.take 4 s
200− pure (BO.word64 (BS.pack b))
106+ --traceShowM ("w4x8"::String, w4x8)
202−--parse a 32-bit integer from network byte order
203−word32P :: Parser IO Word8 Word32
206− w4x8 <- P.takeEQ 4 s
207− pure (BO.word32 (BS.pack w4x8))
107+ let c = fromIntegral (BO.word32 (BS.pack w4x8))
208−
108+ --traceShowM ("c"::String, c)
209−-- uuid is encode as 4 32-bit words because of its convenient 32-bit tuple encoding
109+ vals <- P.take c s
210−uuidP :: Parser IO Word8 UUID
110+ let bytes = BS.pack vals
211−uuidP = do
111+ --traceShowM ("parsedBytes"::String, c, BS.length bytes, bytes)
212− u1 <- word32P
112+ pure bytes
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
220type NewConnectionHandler msg = IO (Maybe msg)
114type NewConnectionHandler msg = IO (Maybe msg)
222−type NewMessageHandler req resp = req -> IO resp
116+type NewMessageHandler req resp = req -> IO (HandlerResponse resp)
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.
118+serve :: forall req resp. (Show req, Serialise req, Serialise resp) =>
225−serve ::
119+ NewConnectionHandler resp ->
226− RequestHandlers s->
120+ NewMessageHandler req resp ->
228 HostAddr ->
121 HostAddr ->
229 PortNumber ->
122 PortNumber ->
230 Maybe (MVar SockAddr) ->
123 Maybe (MVar SockAddr) ->
232−serve userMsgHandlers serverState hostaddr port mSockLock = do
125+serve connhandler userMsgHandler hostaddr port mSockLock = do
127+ decoder :: NewMessageHandler req resp -> Decoder (Message req)
129+ case getDecoder (schema (Proxy @(Message req))) of
130+ Left exc -> error (show exc)
234 handleSock sock = do
132 handleSock sock = do
235− lockingSocket <- newLock sock
133+ mResp <- connhandler
236− drainSocketMessages sock (serverEnvelopeHandler lockingSocket userMsgHandlers serverState)
134+ case mResp of
237−
135+ Nothing -> pure ()
137+ requestID <- UUID <$> UUIDBase.nextRandom
138+ sendMessage (AsyncRequest requestID resp) sock
139+ drainSocketMessages sock (decoder userMsgHandler) (serverMessageHandler sock userMsgHandler)
238− S.serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & S.parallely . S.mapM (handleWithM handleSock) & S.drain
140+ serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & parallely . S.mapM (handleWithM handleSock) & S.drain
239 pure True
141 pure True
241−openEnvelope :: forall s. (Serialise s, Typeable s) => Envelope -> Maybe s
143+serverMessageHandler :: forall req resp. (Show req,
242−openEnvelope (Envelope eprint _ _ bytes) =
144+ Serialise req,
243− if eprint == fingerprint (undefined :: s) then
145+ Serialise resp)
244− case deserialise bytes of
245− Left err -> error (show err)
250−matchEnvelope :: forall a b s. (Serialise a, Serialise b, Typeable b) =>
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
257− Just decoded -> Just (dispatchf, decoded)
259−-- | Called by `serve` to process incoming envelope requests. Never returns, so use `async` to spin it off on another thread.
260−serverEnvelopeHandler ::
261− Locking Socket
146+ => Socket
262− -> RequestHandlers s
147+ -> NewMessageHandler req resp
263− -> s
148+ -> Message req
266−serverEnvelopeHandler _ _ _ (Envelope _ TimeoutResponseMessage _ _) = pure ()
150+serverMessageHandler sock requestMessageHandler msg = do
267−serverEnvelopeHandler _ _ _ (Envelope _ ExceptionResponseMessage _ _) = pure ()
151+ --putStrLn $ "GOT MSG " ++ show msg
268−serverEnvelopeHandler _ _ _ (Envelope _ ResponseMessage _ _) = pure ()
269−serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (RequestMessage timeoutms) msgId _) = do
270− --find first matching handler
271− let runTimeout :: IO b -> IO (Maybe b)
152+ let --runTimeout :: IO (HandlerResponse resp) -> IO (Maybe (HandlerResponse resp))
272− runTimeout m =
153+ runTimeout mTimeout m = case mTimeout of
273− if timeoutms == 0 then
154+ Nothing -> Just <$> m
274− (Just <$> m) `catch` timeoutExcHandler
155+ Just timeoutMicroseconds -> timeout timeoutMicroseconds m
275− else
156+ case msg of
276− (timeout (fromIntegral timeoutms) m) `catch` timeoutExcHandler
157+ Response{} -> putStrLn "client sent response"
277− --allow server-side function to throw TimeoutError which is caught here and becomes TimeoutError value
158+ ResponseExpectedRequest requestID mTimeout val -> do
278− timeoutExcHandler :: TimeoutException -> IO (Maybe b)
159+ let normalResponder = do
279− timeoutExcHandler _ = pure Nothing
160+ resp <- runTimeout mTimeout (requestMessageHandler val)
281− sState = ConnectionState {
162+ Just (HandlerResponse responseVal) -> sendMessage (Response requestID responseVal) sock
282− connectionServerState = serverState,
163+ Just NoResponse -> error "attempt to return non-response to expected response message"
283− connectionSocket = sockLock
164+ Just (HandlerException exc) -> sendMessage (ExceptionResponse @(Message resp) requestID exc) sock
286− firstMatcher (RequestHandler msghandler) Nothing =
287− case matchEnvelope envelope msghandler of
288− Nothing -> pure Nothing
165+ Nothing ->
289− Just (dispatchf, decoded) -> do
166+ sendMessage (TimedOutResponse @(Message resp) requestID) sock
290− --TODO add exception handling
167+ excHandler :: SomeException -> IO ()
291− mResponse <- runTimeout (dispatchf sState decoded)
168+ excHandler e = do
292− let envelopeResponse =
169+ --send exception to client
293− case mResponse of
170+ sendMessage (ExceptionResponse @(Message resp) requestID (show e)) sock
294− Just response ->
171+ throwIO e
295− Envelope (fingerprint response) ResponseMessage msgId (serialise response)
172+ catch normalResponder excHandler
296− Nothing ->
173+ AsyncRequest _ val -> do
297− Envelope (fingerprint TimeoutError) TimeoutResponseMessage msgId BS.empty
174+ void $ requestMessageHandler val
298− sendEnvelope envelopeResponse sockLock
175+ --no response necessary
299− pure (Just ())
176+ ExceptionResponse{} -> putStrLn "client sent exception response"
300− firstMatcher (AsyncRequestHandler msghandler) Nothing =
177+ TimedOutResponse{} -> putStrLn "client sent timed out response"
301− case matchEnvelope envelope msghandler of
178+
302− Nothing -> pure Nothing
179+--add callback to allow for responses via socket
303− Just (dispatchf, decoded) -> do
180+type MessageHandler a = Message a -> IO ()
304− _ <- dispatchf sState decoded
181+
305− pure (Just ())
182+type AsyncMessageHandler a = a -> IO ()
306− firstMatcher _ acc = pure acc
183+
307− eExc <- try $ foldM_ (flip firstMatcher) Nothing msgHandlers :: IO (Either SomeException ())
184+
310− let env = Envelope (fingerprint (show exc)) ExceptionResponseMessage msgId (serialise (show exc)) in
311− sendEnvelope env sockLock
315−type EnvelopeHandler = Envelope -> IO ()
317−drainSocketMessages :: Socket -> EnvelopeHandler -> IO ()
186+drainSocketMessages :: Serialise msg => Socket -> Decoder (Message msg) -> MessageHandler msg -> IO ()
318−drainSocketMessages sock envelopeHandler = do
187+drainSocketMessages sock decoder msgHandler = do
319 let sockStream = S.unfold readWithBufferOf (1024 * 4, sock)
188 let sockStream = S.unfold readWithBufferOf (1024 * 4, sock)
190+ let decoded = evalDecoder decoder bs
320− S.drain $ S.serially $ S.parseMany envelopeP sockStream & S.mapM envelopeHandler
192+ S.drain $ serially $ S.parseMany messageBoundaryP sockStream & S.mapM handler
322--send length-tagged bytestring, perhaps should be in network byte order?
194--send length-tagged bytestring, perhaps should be in network byte order?
323−sendEnvelope :: Envelope -> Locking Socket -> IO ()
195+sendMessage :: Serialise a => Message a -> Socket -> IO ()
324−sendEnvelope envelope sockLock = do
196+sendMessage msg socket' = do
325− let envelopebytes = encodeEnvelope envelope
197+ let
326− fullbytes = envelopebytes
198+ msgbytes = serialiseOnly msg
327− --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
199+ fullbytes = lenbytes <> msgbytes
328− withLock sockLock $ \socket' ->
200+ len = BS.length msgbytes
329− Socket.sendAll socket' fullbytes
201+ lenbytes = BO.bytestring32 (fromIntegral len)
330− --traceShowM ("sendEnvelope", envelope)
202+
331− traceBytes "sendEnvelope" fullbytes
203+ byteCount <- Socket.send socket' fullbytes
332−
204+ when (byteCount /= BS.length fullbytes) (error "bytes sent mismatch")
333−fingerprint :: Typeable a => a -> Fingerprint
205+ traceBytes "sent bytes" fullbytes
334−fingerprint = typeRepFingerprint . typeOf
206+
209+--from streamly-bytestring
210+-- | Convert a 'ByteString' to an array of 'Word8'. This function unwraps the
211+-- 'ByteString' and wraps it with 'Array' constructors and hence the operation
212+-- is performed in constant time.
214+{-# INLINE toArrayS #-}
215+toArrayS :: BS.ByteString -> SA.Array Word8
217+--toArrayS = SA.fromList . BS.unpack
218+toArrayS (BSI.PS fp off len) = SA.Array nfp endPtr
220+ nfp = fp `plusForeignPtr` off
221+ endPtr = unsafeForeignPtrToPtr nfp `plusPtr` len `plusPtr` 1 -- ? why +1?
336−fromArray :: Arr.Array Word8 -> BSI.ByteString
223+toArraySlow :: BS.ByteString -> SA.Array Word8
337−fromArray Arr.Array {..}
224+toArraySlow bs = arr
338− | aLen == 0 = mempty
339− | otherwise = BSI.PS aStart 0 aLen
341− aStartPtr = unsafeForeignPtrToPtr aStart
226+ arr = SA.fromList (BS.unpack bs)
342− aLen = aEnd `minusPtr` aStartPtr
227+-}