@@ -1,18 +1,20 @@
1−{-# LANGUAGE DerivingVia, DeriveGeneric, RankNTypes, ScopedTypeVariables, MultiParamTypeClasses, OverloadedStrings, GeneralizedNewtypeDeriving, TypeApplications #-}
1+{-# LANGUAGE DerivingVia, DeriveGeneric, RankNTypes, ScopedTypeVariables, MultiParamTypeClasses, OverloadedStrings, GeneralizedNewtypeDeriving, CPP, ExistentialQuantification, StandaloneDeriving, GADTs, RecordWildCards #-}
2+{-# OPTIONS_GHC -fno-warn-orphans #-}
2{- HLINT ignore "Use lambda-case" -}
3{- HLINT ignore "Use lambda-case" -}
3module Network.RPC.Curryer.Server where
4module 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
10+import Streamly.Internal.Data.Parser as P hiding (concatMap)
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
15−import Control.Concurrent.MVar (MVar)
17+import Control.Concurrent.MVar (MVar, newMVar, withMVar)
16import Control.Exception
18import Control.Exception
17import Data.Function ((&))
19import Data.Function ((&))
18import Data.Word
20import Data.Word
@@ -24,53 +26,100 @@ import qualified Streamly.Internal.Data.Stream.IsStream as S
24import qualified Data.Binary as B
26import qualified Data.Binary as B
25import qualified Data.UUID as UUIDBase
27import qualified Data.UUID as UUIDBase
26import qualified Data.UUID.V4 as UUIDBase
28import qualified Data.UUID.V4 as UUIDBase
27−import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as SA
28−import qualified Network.RPC.Curryer.StreamlyAdditions as SA
29import Control.Monad
29import Control.Monad
32+import qualified Network.RPC.Curryer.StreamlyAdditions as SA
33+--import Control.Monad
30import Data.Hashable
34import Data.Hashable
31import System.Timeout
35import System.Timeout
32import qualified Network.ByteOrder as BO
36import qualified Network.ByteOrder as BO
34-- for toArrayS conversion
39-- for toArrayS conversion
35import qualified Data.ByteString.Internal as BSI
40import qualified Data.ByteString.Internal as BSI
36−import Foreign.ForeignPtr (plusForeignPtr)
41+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as Arr
37import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
42import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
38−import GHC.Ptr (plusPtr)
43+import GHC.Ptr (minusPtr)
45+-- define CURRYER_SHOW_BYTES 1
46+#if CURRYER_SHOW_BYTES == 1
40−--import Debug.Trace
47+import Debug.Trace
42−data ClientHelloMessage = ClientHelloMessage Int Int
49+
43− deriving Generic
50+traceBytes :: Applicative f => String -> BS.ByteString -> f ()
44− deriving Serialise via WineryVariant ClientHelloMessage
51+#if CURRYER_SHOW_BYTES == 1
45−
52+traceBytes msg bs = traceShowM (msg, BS.length bs, bs)
46−data ServerHelloMessage = ServerHelloMessage { serverServices :: [String],
53+#else
47− serverInfo :: String
54+traceBytes _ _ = pure ()
50− deriving Serialise via WineryRecord ServerHelloMessage
57+data Locking a = Locking (MVar ()) a
52−--request-response token
59+newLock :: a -> IO (Locking a)
53−--client-side timeout
60+newLock x = do
54−--server-to-client and client-to-server async request (no response requested)
61+ lock <- newMVar ()
55−
62+ pure (Locking lock x)
57−
64+withLock :: Locking a -> (a -> IO b) -> IO b
58−data Message a = Response UUID a
65+withLock (Locking mvar v) m =
59− | AsyncRequest UUID a
66+ withMVar mvar $ \_ -> m v
60− | ResponseExpectedRequest UUID (Maybe Timeout) a
67+
61− | TimedOutResponse UUID
68+lockless :: Locking a -> a
62− -- | Services
69+lockless (Locking _ a) = a
63− | ExceptionResponse String
64− deriving (Generic, Show)
65− deriving Serialise via WineryVariant (Message a)
67−-- | The response type
68−data HandlerResponse a = NoResponse
72− deriving Serialise via WineryVariant (HandlerResponse a)
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.
76+--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,
81+ envPayload :: !BinaryMessage
83+ deriving (Generic, Show)
85+type TimeoutMicroseconds = Int
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
74--avoid orphan instance
123--avoid orphan instance
75newtype UUID = UUID { _unUUID :: UUIDBase.UUID }
124newtype UUID = UUID { _unUUID :: UUIDBase.UUID }
76 deriving (Show, Eq, B.Binary, Hashable)
125 deriving (Show, Eq, B.Binary, Hashable)
@@ -89,12 +138,18 @@ instance Serialise UUID where
89 x -> error $ "invalid schema element " <> show x
138 x -> error $ "invalid schema element " <> show x
90 decodeCurrent = B.decode . BSL.fromStrict <$> (decodeVarInt >>= getBytes)
139 decodeCurrent = B.decode . BSL.fromStrict <$> (decodeVarInt >>= getBytes)
92−
141+-- | Errors from remote calls.
93data ConnectionError = CodecError String -- show of WineryException from exception initiator which cannot otherwise be transmitted over a line due to dependencies on TypeReps
142data ConnectionError = CodecError String -- show of WineryException from exception initiator which cannot otherwise be transmitted over a line due to dependencies on TypeReps
94 | TimeoutError
143 | TimeoutError
144+ | ExceptionError String
95 deriving (Generic, Show, Eq)
145 deriving (Generic, Show, Eq)
96 deriving Serialise via WineryVariant ConnectionError
146 deriving Serialise via WineryVariant ConnectionError
148+data TimeoutException = TimeoutException
151+instance Exception TimeoutException
98type HostAddr = (Word8, Word8, Word8, Word8)
153type HostAddr = (Word8, Word8, Word8, Word8)
100allHostAddrs,localHostAddr :: HostAddr
155allHostAddrs,localHostAddr :: HostAddr
@@ -102,121 +157,186 @@ allHostAddrs = (0,0,0,0)
102localHostAddr = (127,0,0,1)
157localHostAddr = (127,0,0,1)
104-- Each message is length-prefixed by a 32-bit unsigned length.
159-- Each message is length-prefixed by a 32-bit unsigned length.
105−messageBoundaryP :: Parser IO Word8 BS.ByteString
160+envelopeP :: Parser IO Word8 Envelope
106−messageBoundaryP = do
161+envelopeP = 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
107− let x = FL.toList
198+ let s = FL.toList
108− w4x8 <- P.take 4 x
199+ b <- P.takeEQ 8 s
109− --traceShowM ("w4x8"::String, w4x8)
200+ pure (BO.word64 (BS.pack b))
202+--parse a 32-bit integer from network byte order
203+word32P :: Parser IO Word8 Word32
206+ w4x8 <- P.takeEQ 4 s
110− let c = fromIntegral (BO.word32 (BS.pack w4x8))
207+ pure (BO.word32 (BS.pack w4x8))
111− --traceShowM ("c"::String, c)
208+
112− vals <- P.take c x
209+-- uuid is encode as 4 32-bit words because of its convenient 32-bit tuple encoding
113− let bytes = BS.pack vals
210+uuidP :: Parser IO Word8 UUID
114− --traceShowM ("parsedBytes"::String, c, BS.length bytes, bytes)
211+uuidP = do
115− pure bytes
212+ u1 <- 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
117type NewConnectionHandler msg = IO (Maybe msg)
220type NewConnectionHandler msg = IO (Maybe msg)
119−type NewMessageHandler msg resp = msg -> IO (HandlerResponse resp)
222+type NewMessageHandler req resp = req -> IO resp
121−serve :: (Show msg, Serialise msg, Serialise 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.
122− NewConnectionHandler resp ->
225+serve ::
123− NewMessageHandler msg resp ->
226+ RequestHandlers s->
124 HostAddr ->
228 HostAddr ->
125 PortNumber ->
229 PortNumber ->
126 Maybe (MVar SockAddr) ->
230 Maybe (MVar SockAddr) ->
128−serve connhandler userMsgHandler hostaddr port mSockLock = do
232+serve userMsgHandlers serverState hostaddr port mSockLock = do
129− let handleSock sock = do
234+ handleSock sock = do
130− putStrLn "handleSock"
235+ lockingSocket <- newLock sock
131− -- allow the server to send an async welcome message to the new client, if necessary
236+ drainSocketMessages sock (serverEnvelopeHandler lockingSocket userMsgHandlers serverState)
132− mResp <- connhandler
237+
136− requestID <- UUID <$> UUIDBase.nextRandom
137− sendMessage (AsyncRequest requestID resp) sock
138− drainSocketMessages sock (serverMessageHandler sock userMsgHandler)
139− serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & serially . S.mapM (handleWithM handleSock) & S.drain
238+ S.serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & S.parallely . S.mapM (handleWithM handleSock) & S.drain
140 pure True
239 pure True
142−serverMessageHandler :: forall req resp. (Show req,
241+openEnvelope :: forall s. (Serialise s, Typeable s) => Envelope -> Maybe s
143− Serialise req,
242+openEnvelope (Envelope eprint _ _ bytes) =
144− Serialise resp)
243+ if eprint == fingerprint (undefined :: s) then
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 ::
145− => Socket
261+ Locking Socket
146− -> NewMessageHandler req resp
262+ -> RequestHandlers s
147− -> Message req
263+ -> s
149−serverMessageHandler sock requestMessageHandler msg = do
266+serverEnvelopeHandler _ _ _ (Envelope _ TimeoutResponseMessage _ _) = pure ()
150− putStrLn $ "GOT MSG " ++ show msg
267+serverEnvelopeHandler _ _ _ (Envelope _ ExceptionResponseMessage _ _) = pure ()
268+serverEnvelopeHandler _ _ _ (Envelope _ ResponseMessage _ _) = pure ()
269+serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (RequestMessage timeoutms) msgId _) = do
270+ --find first matching handler
151− let --runTimeout :: IO (HandlerResponse resp) -> IO (Maybe (HandlerResponse resp))
271+ let runTimeout :: IO b -> IO (Maybe b)
152− runTimeout mTimeout m = case mTimeout of
272+ runTimeout m =
153− Nothing -> Just <$> m
273+ if timeoutms == 0 then
154− Just timeoutMicroseconds -> timeout timeoutMicroseconds m
274+ (Just <$> m) `catch` timeoutExcHandler
155− case msg of
275+ else
156− Response{} -> putStrLn "client sent response"
276+ (timeout (fromIntegral timeoutms) m) `catch` timeoutExcHandler
157− ResponseExpectedRequest requestID mTimeout val -> do
277+ --allow server-side function to throw TimeoutError which is caught here and becomes TimeoutError value
158− putStrLn "server received ResponseExpectedRequest"
278+ timeoutExcHandler :: TimeoutException -> IO (Maybe b)
159− resp <- runTimeout mTimeout (requestMessageHandler val)
279+ timeoutExcHandler _ = pure Nothing
161− Just (HandlerResponse responseVal) -> sendMessage (Response requestID responseVal) sock
281+ sState = ConnectionState {
162− Just NoResponse -> error "attempt to return non-response to expected response message"
282+ connectionServerState = serverState,
163− Just (HandlerException _) -> error "TODO HandlerException"
283+ connectionSocket = sockLock
165− sendMessage (TimedOutResponse @(Message resp) requestID) sock
285+
166− AsyncRequest _ val -> do
286+ firstMatcher (RequestHandler msghandler) Nothing =
167− putStrLn "AsyncReq"
287+ case matchEnvelope envelope msghandler of
168− void $ requestMessageHandler val
288+ Nothing -> pure Nothing
169− --no response necessary
289+ Just (dispatchf, decoded) -> do
170− ExceptionResponse{} -> putStrLn "client sent exception response"
290+ --TODO add exception handling
171− TimedOutResponse{} -> putStrLn "client sent timed out response"
291+ mResponse <- runTimeout (dispatchf sState decoded)
172−
292+ let envelopeResponse =
173−--add callback to allow for responses via socket
293+ case mResponse of
174−type MessageHandler a = Message a -> IO ()
294+ Just response ->
175−
295+ Envelope (fingerprint response) ResponseMessage msgId (serialise response)
176−type AsyncMessageHandler a = a -> IO ()
296+ Nothing ->
177−
297+ Envelope (fingerprint TimeoutError) TimeoutResponseMessage msgId BS.empty
178−
298+ sendEnvelope envelopeResponse sockLock
180−drainSocketMessages :: Serialise msg => Socket -> MessageHandler msg -> IO ()
300+ firstMatcher (AsyncRequestHandler msghandler) Nothing =
181−drainSocketMessages sock msgHandler = do
301+ case matchEnvelope envelope msghandler of
182− let sockStream = S.unfold readWithBufferOf (1024 * 4, sock)
302+ Nothing -> pure Nothing
183− handler bs = do
303+ Just (dispatchf, decoded) -> do
304+ _ <- dispatchf sState decoded
306+ firstMatcher _ acc = pure acc
307+ eExc <- try $ foldM_ (flip firstMatcher) Nothing msgHandlers :: IO (Either SomeException ())
184− case deserialise bs of
308+ case eExc of
185− Left err ->
309+ Left exc ->
186− print err
310+ let env = Envelope (fingerprint (show exc)) ExceptionResponseMessage msgId (serialise (show exc)) in
311+ sendEnvelope env sockLock
187− Right val ->
312+ Right () -> pure ()
188− msgHandler val --add response function
189− S.drain $ serially $ S.parseMany messageBoundaryP sockStream & S.mapM handler
191−--send length-tagged bytestring, perhaps should be in network byte order?
192−sendMessage :: Serialise a => Message a -> Socket -> IO ()
193−sendMessage msg socket' = do
195− msgbytes = serialise msg
196− fullbytes = lenbytes <> msgbytes
197− len = BS.length msgbytes
198− lenbytes = BO.bytestring32 (fromIntegral len)
200− byteCount <- Socket.send socket' fullbytes
201− when (byteCount /= BS.length fullbytes) (error "bytes sent mismatch")
202− --traceShowM ("sent bytes:" :: String, byteCount, fullbytes)
315+type EnvelopeHandler = Envelope -> IO ()
317+drainSocketMessages :: Socket -> EnvelopeHandler -> IO ()
318+drainSocketMessages sock envelopeHandler = do
319+ let sockStream = S.unfold readWithBufferOf (1024 * 4, sock)
320+ S.drain $ S.serially $ S.parseMany envelopeP sockStream & S.mapM envelopeHandler
206−--from streamly-bytestring
322+--send length-tagged bytestring, perhaps should be in network byte order?
207−-- | Convert a 'ByteString' to an array of 'Word8'. This function unwraps the
323+sendEnvelope :: Envelope -> Locking Socket -> IO ()
208−-- 'ByteString' and wraps it with 'Array' constructors and hence the operation
324+sendEnvelope envelope sockLock = do
209−-- is performed in constant time.
325+ let envelopebytes = encodeEnvelope envelope
210−{-# INLINE toArrayS #-}
326+ fullbytes = envelopebytes
211−toArrayS :: BS.ByteString -> SA.Array Word8
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
212−--slow path
328+ withLock sockLock $ \socket' ->
213−--toArrayS = SA.fromList . BS.unpack
329+ Socket.sendAll socket' fullbytes
214−toArrayS (BSI.PS fp off len) = SA.Array nfp endPtr
330+ --traceShowM ("sendEnvelope", envelope)
215− where
331+ traceBytes "sendEnvelope" fullbytes
216− nfp = fp `plusForeignPtr` off
332+
217− endPtr = unsafeForeignPtrToPtr nfp `plusPtr` len `plusPtr` 1 -- ? why +1?
333+fingerprint :: Typeable a => a -> Fingerprint
334+fingerprint = typeRepFingerprint . typeOf
219−toArraySlow :: BS.ByteString -> SA.Array Word8
336+fromArray :: Arr.Array Word8 -> BSI.ByteString
220−toArraySlow bs = arr
337+fromArray Arr.Array {..}
338+ | aLen == 0 = mempty
339+ | otherwise = BSI.PS aStart 0 aLen
222− arr = SA.fromList (BS.unpack bs)
341+ aStartPtr = unsafeForeignPtrToPtr aStart
342+ aLen = aEnd `minusPtr` aStartPtr