@@ -1,4 +1,4 @@
1−{-# LANGUAGE DerivingVia, DeriveGeneric, RankNTypes, ScopedTypeVariables, MultiParamTypeClasses, OverloadedStrings, GeneralizedNewtypeDeriving, TypeApplications, CPP, ExistentialQuantification, StandaloneDeriving, GADTs #-}
1+{-# LANGUAGE DerivingVia, DeriveGeneric, RankNTypes, ScopedTypeVariables, MultiParamTypeClasses, OverloadedStrings, GeneralizedNewtypeDeriving, CPP, ExistentialQuantification, StandaloneDeriving, GADTs, RecordWildCards #-}
22{-# OPTIONS_GHC -fno-warn-orphans #-}
33{- HLINT ignore "Use lambda-case" -}
44module Network.RPC.Curryer.Server where
@@ -27,6 +27,7 @@ import qualified Data.Binary as B
2727import qualified Data.UUID as UUIDBase
2828import qualified Data.UUID.V4 as UUIDBase
2929import Control.Monad
30+import Data.Functor
3031
3132import qualified Network.RPC.Curryer.StreamlyAdditions as SA
3233--import Control.Monad
@@ -34,14 +35,14 @@ import Data.Hashable
3435import System.Timeout
3536import qualified Network.ByteOrder as BO
3637
38+
3739-- for toArrayS conversion
38−{-import qualified Data.ByteString.Internal as BSI
39−import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as SA
40−import Foreign.ForeignPtr (plusForeignPtr)
40+import qualified Data.ByteString.Internal as BSI
41+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as Arr
4142import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
42−import GHC.Ptr (plusPtr)
43−-}
44−-- #define CURRYER_SHOW_BYTES 1
43+import GHC.Ptr (minusPtr)
44+
45+-- define CURRYER_SHOW_BYTES 1
4546#if CURRYER_SHOW_BYTES == 1
4647import Debug.Trace
4748#endif
@@ -86,6 +87,7 @@ type TimeoutMicroseconds = Int
8687deriving instance Generic Fingerprint
8788deriving via WineryVariant Fingerprint instance Serialise Fingerprint
8889
90+-- | Internal type used to mark envelope types.
8991data MessageType = RequestMessage TimeoutMicroseconds
9092 | ResponseMessage
9193 | TimeoutResponseMessage
@@ -93,27 +95,30 @@ data MessageType = RequestMessage TimeoutMicroseconds
9395 deriving (Generic, Show)
9496 deriving Serialise via WineryVariant MessageType
9597
96−type MessageHandlers serverState = [RequestHandler serverState]
97−
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.
98102data RequestHandler serverState where
99103 -- | create a request handler with a response
100104 RequestHandler :: forall a b serverState. (Serialise a, Serialise b) => (ConnectionState serverState -> a -> IO b) -> RequestHandler serverState
101105 -- | create an asynchronous request handler where the client does not expect nor await a response
102106 AsyncRequestHandler :: forall a serverState. Serialise a => (ConnectionState serverState -> a -> IO ()) -> RequestHandler serverState
103107
104−-- | Passed to
108+-- | Server state sent in via `serve` and passed to `RequestHandler`s.
105109data ConnectionState a = ConnectionState {
106110 connectionServerState :: a,
107111 connectionSocket :: Locking Socket
108112 }
109113
110−-- | 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.
111−sendMessage :: Serialise a => ConnectionState s -> a -> IO ()
112−sendMessage sState msg = do
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
113117 requestID <- UUID <$> UUIDBase.nextRandom
114118 let env =
115− Envelope (fingerprint msg) (RequestMessage 0) requestID (serialise msg)
116− sendEnvelope env (connectionSocket sState)
119+ Envelope (fingerprint msg) (RequestMessage timeout') requestID (serialise msg)
120+ timeout' = 0
121+ sendEnvelope env lockSock
117122
118123--avoid orphan instance
119124newtype UUID = UUID { _unUUID :: UUIDBase.UUID }
@@ -133,31 +138,39 @@ instance Serialise UUID where
133138 x -> error $ "invalid schema element " <> show x
134139 decodeCurrent = B.decode . BSL.fromStrict <$> (decodeVarInt >>= getBytes)
135140
136−
141+-- | Errors from remote calls.
137142data ConnectionError = CodecError String -- show of WineryException from exception initiator which cannot otherwise be transmitted over a line due to dependencies on TypeReps
138143 | TimeoutError
139144 | ExceptionError String
140145 deriving (Generic, Show, Eq)
141146 deriving Serialise via WineryVariant ConnectionError
142147
148+data TimeoutException = TimeoutException
149+ deriving Show
150+
151+instance Exception TimeoutException
152+
143153type HostAddr = (Word8, Word8, Word8, Word8)
144154
145155allHostAddrs,localHostAddr :: HostAddr
146156allHostAddrs = (0,0,0,0)
147157localHostAddr = (127,0,0,1)
148158
149−-- Each message is lxggength-prefixed by a 32-bit unsigned length.
159+-- Each message is length-prefixed by a 32-bit unsigned length.
150160envelopeP :: Parser IO Word8 Envelope
151161envelopeP = do
152− let s = FL.toList
153− msgTypeP = (P.satisfy (== 0) *>
162+ let msgTypeP = (P.satisfy (== 0) *>
154163 (RequestMessage . fromIntegral <$> word32P)) `P.alt`
155− (P.satisfy (== 1) *> pure ResponseMessage) `P.alt`
156− (P.satisfy (== 2) *> pure TimeoutResponseMessage) `P.alt`
157− (P.satisfy (== 3) *> pure ExceptionResponseMessage)
164+ (P.satisfy (== 1) $> ResponseMessage) `P.alt`
165+ (P.satisfy (== 2) $> TimeoutResponseMessage) `P.alt`
166+ (P.satisfy (== 3) $> ExceptionResponseMessage)
158167 lenPrefixedByteStringP = do
159168 c <- fromIntegral <$> word32P
160− BS.pack <$> P.takeEQ c s
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)
161174 Envelope <$> fingerprintP <*> msgTypeP <*> uuidP <*> lenPrefixedByteStringP
162175
163176encodeEnvelope :: Envelope -> BS.ByteString
@@ -172,15 +185,13 @@ encodeEnvelope (Envelope (Fingerprint fp1 fp2) msgType msgId bs) =
172185 ExceptionResponseMessage -> BS.singleton 3
173186 msgIdBs =
174187 case UUIDBase.toWords (_unUUID msgId) of
175− (u1, u2, u3, u4) -> foldr (<>) BS.empty (map BO.bytestring32 [u1, u2, u3, u4])
188+ (u1, u2, u3, u4) -> foldr ((<>) . BO.bytestring32) BS.empty [u1, u2, u3, u4]
176189 msgLen = fromIntegral (BS.length bs)
177190 lenPrefixedBs = BO.bytestring32 msgLen <> bs
178191
179192fingerprintP :: Parser IO Word8 Fingerprint
180−fingerprintP = do
181− f1 <- word64P
182− f2 <- word64P
183− pure (Fingerprint f1 f2)
193+fingerprintP =
194+ Fingerprint <$> word64P <*> word64P
184195
185196word64P :: Parser IO Word8 Word64
186197word64P = do
@@ -201,15 +212,18 @@ uuidP = do
201212 u1 <- word32P
202213 u2 <- word32P
203214 u3 <- word32P
204− u4 <- word32P
205− pure (UUID (UUIDBase.fromWords u1 u2 u3 u4))
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
206219
207220type NewConnectionHandler msg = IO (Maybe msg)
208221
209222type NewMessageHandler req resp = req -> IO resp
210−
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.
211225serve ::
212− MessageHandlers s->
226+ RequestHandlers s->
213227 s ->
214228 HostAddr ->
215229 PortNumber ->
@@ -225,7 +239,7 @@ serve userMsgHandlers serverState hostaddr port mSockLock = do
225239 pure True
226240
227241openEnvelope :: forall s. (Serialise s, Typeable s) => Envelope -> Maybe s
228−openEnvelope (Envelope eprint _ _ bytes) = do
242+openEnvelope (Envelope eprint _ _ bytes) =
229243 if eprint == fingerprint (undefined :: s) then
230244 case deserialise bytes of
231245 Left err -> error (show err)
@@ -242,23 +256,28 @@ matchEnvelope envelope dispatchf =
242256 Nothing -> Nothing
243257 Just decoded -> Just (dispatchf, decoded)
244258
259+-- | Called by `serve` to process incoming envelope requests. Never returns, so use `async` to spin it off on another thread.
245260serverEnvelopeHandler ::
246261 Locking Socket
247− -> MessageHandlers s
262+ -> RequestHandlers s
248263 -> s
249264 -> Envelope
250265 -> IO ()
251266serverEnvelopeHandler _ _ _ (Envelope _ TimeoutResponseMessage _ _) = pure ()
252267serverEnvelopeHandler _ _ _ (Envelope _ ExceptionResponseMessage _ _) = pure ()
268+serverEnvelopeHandler _ _ _ (Envelope _ ResponseMessage _ _) = pure ()
253269serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (RequestMessage timeoutms) msgId _) = do
254270 --find first matching handler
255271 let runTimeout :: IO b -> IO (Maybe b)
256272 runTimeout m =
257273 if timeoutms == 0 then
258− Just <$> m
274+ (Just <$> m) `catch` timeoutExcHandler
259275 else
260− timeout (fromIntegral timeoutms) m
261−
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
280+
262281 sState = ConnectionState {
263282 connectionServerState = serverState,
264283 connectionSocket = sockLock
@@ -274,8 +293,8 @@ serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (Req
274293 case mResponse of
275294 Just response ->
276295 Envelope (fingerprint response) ResponseMessage msgId (serialise response)
277− Nothing ->
278− Envelope (fingerprint TimeoutError) (TimeoutResponseMessage) msgId (BS.empty)
296+ Nothing ->
297+ Envelope (fingerprint TimeoutError) TimeoutResponseMessage msgId BS.empty
279298 sendEnvelope envelopeResponse sockLock
280299 pure (Just ())
281300 firstMatcher (AsyncRequestHandler msghandler) Nothing =
@@ -291,11 +310,9 @@ serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (Req
291310 let env = Envelope (fingerprint (show exc)) ExceptionResponseMessage msgId (serialise (show exc)) in
292311 sendEnvelope env sockLock
293312 Right () -> pure ()
294−serverEnvelopeHandler _ _ _ (Envelope _ ResponseMessage _ _) = error "server received response message"
295313
296−type EnvelopeHandler = Envelope -> IO ()
297314
298−type AsyncMessageHandler a = a -> IO ()
315+type EnvelopeHandler = Envelope -> IO ()
299316
300317drainSocketMessages :: Socket -> EnvelopeHandler -> IO ()
301318drainSocketMessages sock envelopeHandler = do
@@ -310,8 +327,16 @@ sendEnvelope envelope sockLock = do
310327 --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
311328 withLock sockLock $ \socket' ->
312329 Socket.sendAll socket' fullbytes
330+ --traceShowM ("sendEnvelope", envelope)
313331 traceBytes "sendEnvelope" fullbytes
314332
315333fingerprint :: Typeable a => a -> Fingerprint
316334fingerprint = typeRepFingerprint . typeOf
317335
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