@@ -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,7 +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 Control.Concurrent.STM
30+import Data.Functor
3131
3232import qualified Network.RPC.Curryer.StreamlyAdditions as SA
3333--import Control.Monad
@@ -35,14 +35,14 @@ import Data.Hashable
3535import System.Timeout
3636import qualified Network.ByteOrder as BO
3737
38+
3839-- for toArrayS conversion
39−{-import qualified Data.ByteString.Internal as BSI
40−import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as SA
41−import Foreign.ForeignPtr (plusForeignPtr)
40+import qualified Data.ByteString.Internal as BSI
41+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as Arr
4242import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
43−import GHC.Ptr (plusPtr)
44−-}
45−-- #define CURRYER_SHOW_BYTES 1
43+import GHC.Ptr (minusPtr)
44+
45+-- define CURRYER_SHOW_BYTES 1
4646#if CURRYER_SHOW_BYTES == 1
4747import Debug.Trace
4848#endif
@@ -87,6 +87,7 @@ type TimeoutMicroseconds = Int
8787deriving instance Generic Fingerprint
8888deriving via WineryVariant Fingerprint instance Serialise Fingerprint
8989
90+-- | Internal type used to mark envelope types.
9091data MessageType = RequestMessage TimeoutMicroseconds
9192 | ResponseMessage
9293 | TimeoutResponseMessage
@@ -94,27 +95,30 @@ data MessageType = RequestMessage TimeoutMicroseconds
9495 deriving (Generic, Show)
9596 deriving Serialise via WineryVariant MessageType
9697
97−type MessageHandlers serverState = [RequestHandler serverState]
98−
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.
99102data RequestHandler serverState where
100103 -- | create a request handler with a response
101104 RequestHandler :: forall a b serverState. (Serialise a, Serialise b) => (ConnectionState serverState -> a -> IO b) -> RequestHandler serverState
102105 -- | create an asynchronous request handler where the client does not expect nor await a response
103106 AsyncRequestHandler :: forall a serverState. Serialise a => (ConnectionState serverState -> a -> IO ()) -> RequestHandler serverState
104107
105−-- | Passed to
108+-- | Server state sent in via `serve` and passed to `RequestHandler`s.
106109data ConnectionState a = ConnectionState {
107− connectionServerState :: STM a,
110+ connectionServerState :: a,
108111 connectionSocket :: Locking Socket
109112 }
110113
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+-- | 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
114117 requestID <- UUID <$> UUIDBase.nextRandom
115118 let env =
116− Envelope (fingerprint msg) (RequestMessage 0) requestID (serialise msg)
117− sendEnvelope env (connectionSocket sState)
119+ Envelope (fingerprint msg) (RequestMessage timeout') requestID (serialise msg)
120+ timeout' = 0
121+ sendEnvelope env lockSock
118122
119123--avoid orphan instance
120124newtype UUID = UUID { _unUUID :: UUIDBase.UUID }
@@ -134,31 +138,39 @@ instance Serialise UUID where
134138 x -> error $ "invalid schema element " <> show x
135139 decodeCurrent = B.decode . BSL.fromStrict <$> (decodeVarInt >>= getBytes)
136140
137−
141+-- | Errors from remote calls.
138142data ConnectionError = CodecError String -- show of WineryException from exception initiator which cannot otherwise be transmitted over a line due to dependencies on TypeReps
139143 | TimeoutError
140144 | ExceptionError String
141145 deriving (Generic, Show, Eq)
142146 deriving Serialise via WineryVariant ConnectionError
143147
148+data TimeoutException = TimeoutException
149+ deriving Show
150+
151+instance Exception TimeoutException
152+
144153type HostAddr = (Word8, Word8, Word8, Word8)
145154
146155allHostAddrs,localHostAddr :: HostAddr
147156allHostAddrs = (0,0,0,0)
148157localHostAddr = (127,0,0,1)
149158
150−-- Each message is lxggength-prefixed by a 32-bit unsigned length.
159+-- Each message is length-prefixed by a 32-bit unsigned length.
151160envelopeP :: Parser IO Word8 Envelope
152161envelopeP = do
153− let s = FL.toList
154− msgTypeP = (P.satisfy (== 0) *>
162+ let msgTypeP = (P.satisfy (== 0) *>
155163 (RequestMessage . fromIntegral <$> word32P)) `P.alt`
156− (P.satisfy (== 1) *> pure ResponseMessage) `P.alt`
157− (P.satisfy (== 2) *> pure TimeoutResponseMessage) `P.alt`
158− (P.satisfy (== 3) *> pure ExceptionResponseMessage)
164+ (P.satisfy (== 1) $> ResponseMessage) `P.alt`
165+ (P.satisfy (== 2) $> TimeoutResponseMessage) `P.alt`
166+ (P.satisfy (== 3) $> ExceptionResponseMessage)
159167 lenPrefixedByteStringP = do
160168 c <- fromIntegral <$> word32P
161− 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)
162174 Envelope <$> fingerprintP <*> msgTypeP <*> uuidP <*> lenPrefixedByteStringP
163175
164176encodeEnvelope :: Envelope -> BS.ByteString
@@ -173,15 +185,13 @@ encodeEnvelope (Envelope (Fingerprint fp1 fp2) msgType msgId bs) =
173185 ExceptionResponseMessage -> BS.singleton 3
174186 msgIdBs =
175187 case UUIDBase.toWords (_unUUID msgId) of
176− (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]
177189 msgLen = fromIntegral (BS.length bs)
178190 lenPrefixedBs = BO.bytestring32 msgLen <> bs
179191
180192fingerprintP :: Parser IO Word8 Fingerprint
181−fingerprintP = do
182− f1 <- word64P
183− f2 <- word64P
184− pure (Fingerprint f1 f2)
193+fingerprintP =
194+ Fingerprint <$> word64P <*> word64P
185195
186196word64P :: Parser IO Word8 Word64
187197word64P = do
@@ -202,16 +212,19 @@ uuidP = do
202212 u1 <- word32P
203213 u2 <- word32P
204214 u3 <- word32P
205− u4 <- word32P
206− 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
207219
208220type NewConnectionHandler msg = IO (Maybe msg)
209221
210222type NewMessageHandler req resp = req -> IO resp
211−
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.
212225serve ::
213− MessageHandlers s->
214− STM s ->
226+ RequestHandlers s->
227+ s ->
215228 HostAddr ->
216229 PortNumber ->
217230 Maybe (MVar SockAddr) ->
@@ -226,7 +239,7 @@ serve userMsgHandlers serverState hostaddr port mSockLock = do
226239 pure True
227240
228241openEnvelope :: forall s. (Serialise s, Typeable s) => Envelope -> Maybe s
229−openEnvelope (Envelope eprint _ _ bytes) = do
242+openEnvelope (Envelope eprint _ _ bytes) =
230243 if eprint == fingerprint (undefined :: s) then
231244 case deserialise bytes of
232245 Left err -> error (show err)
@@ -243,23 +256,28 @@ matchEnvelope envelope dispatchf =
243256 Nothing -> Nothing
244257 Just decoded -> Just (dispatchf, decoded)
245258
259+-- | Called by `serve` to process incoming envelope requests. Never returns, so use `async` to spin it off on another thread.
246260serverEnvelopeHandler ::
247261 Locking Socket
248− -> MessageHandlers s
249− -> STM s
262+ -> RequestHandlers s
263+ -> s
250264 -> Envelope
251265 -> IO ()
252266serverEnvelopeHandler _ _ _ (Envelope _ TimeoutResponseMessage _ _) = pure ()
253267serverEnvelopeHandler _ _ _ (Envelope _ ExceptionResponseMessage _ _) = pure ()
268+serverEnvelopeHandler _ _ _ (Envelope _ ResponseMessage _ _) = pure ()
254269serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (RequestMessage timeoutms) msgId _) = do
255270 --find first matching handler
256271 let runTimeout :: IO b -> IO (Maybe b)
257272 runTimeout m =
258273 if timeoutms == 0 then
259− Just <$> m
274+ (Just <$> m) `catch` timeoutExcHandler
260275 else
261− timeout (fromIntegral timeoutms) m
262−
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+
263281 sState = ConnectionState {
264282 connectionServerState = serverState,
265283 connectionSocket = sockLock
@@ -275,8 +293,8 @@ serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (Req
275293 case mResponse of
276294 Just response ->
277295 Envelope (fingerprint response) ResponseMessage msgId (serialise response)
278− Nothing ->
279− Envelope (fingerprint TimeoutError) (TimeoutResponseMessage) msgId (BS.empty)
296+ Nothing ->
297+ Envelope (fingerprint TimeoutError) TimeoutResponseMessage msgId BS.empty
280298 sendEnvelope envelopeResponse sockLock
281299 pure (Just ())
282300 firstMatcher (AsyncRequestHandler msghandler) Nothing =
@@ -292,11 +310,9 @@ serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (Req
292310 let env = Envelope (fingerprint (show exc)) ExceptionResponseMessage msgId (serialise (show exc)) in
293311 sendEnvelope env sockLock
294312 Right () -> pure ()
295−serverEnvelopeHandler _ _ _ (Envelope _ ResponseMessage _ _) = error "server received response message"
296313
297−type EnvelopeHandler = Envelope -> IO ()
298314
299−type AsyncMessageHandler a = a -> IO ()
315+type EnvelopeHandler = Envelope -> IO ()
300316
301317drainSocketMessages :: Socket -> EnvelopeHandler -> IO ()
302318drainSocketMessages sock envelopeHandler = do
@@ -311,8 +327,16 @@ sendEnvelope envelope sockLock = do
311327 --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
312328 withLock sockLock $ \socket' ->
313329 Socket.sendAll socket' fullbytes
330+ --traceShowM ("sendEnvelope", envelope)
314331 traceBytes "sendEnvelope" fullbytes
315332
316333fingerprint :: Typeable a => a -> Fingerprint
317334fingerprint = typeRepFingerprint . typeOf
318335
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