on master help sign in
add socket locking for > PIPE_BUF messages
commit08578c17071c61ce59dbc6cb442c7ec17aeb1294 authorAgentM <agentm@themactionfaction.com> dateWed 21 Oct 2020, 01:06 (6 years ago) parentc6ea967
3 files changed+67−48
src/Network/RPC/Curryer/Client.hs+7−5 src/Network/RPC/Curryer/Server.hs+32−38 test/Curryer/Test/Basic.hs+28−5
src/Network/RPC/Curryer/Client.hs+7−5
@@ -15,7 +15,7 @@ import Data.Proxy
1515
1616type SyncMap a = STMMap.Map UUID (MVar (Either ConnectionError a), UTCTime)
1717-- the request map holds
18−data Connection a = Connection { _conn_sock :: Socket,
18+data Connection a = Connection { _conn_sockLock :: Locking Socket,
1919 _conn_asyncThread :: Async (),
2020 _conn_syncmap :: SyncMap a
2121 }
@@ -33,15 +33,17 @@ connect notificationCallback hostAddr portNum = do
3333 Left err -> error (show err)
3434 Right dec -> dec
3535 asyncThread <- async (clientAsync sock syncmap decoder notificationCallback)
36+ sockLock <- newLock sock
3637 pure (Connection {
37− _conn_sock = sock,
38+ _conn_sockLock = sockLock,
3839 _conn_asyncThread = asyncThread,
3940 _conn_syncmap = syncmap
4041 })
4142
4243close :: Connection a -> IO ()
4344close conn = do
44− Socket.close (_conn_sock conn)
45+ withLock (_conn_sockLock conn) $ \sock ->
46+ Socket.close sock
4547 cancel (_conn_asyncThread conn)
4648
4749-- async thread for handling client-side incoming messages- dispatch to proper waiting thread or handler asynchronous notifications
@@ -94,7 +96,7 @@ callTimeout mTimeout conn msg = do
9496 responseMVar <- newEmptyMVar
9597 now <- getCurrentTime
9698 atomically $ STMMap.insert (responseMVar, now) requestID mVarMap
97− sendMessage (ResponseExpectedRequest requestID mTimeout msg) (_conn_sock conn)
99+ sendMessage (ResponseExpectedRequest requestID mTimeout msg) (_conn_sockLock conn)
98100 let timeoutMicroseconds =
99101 case mTimeout of
100102 Just timeout' -> timeout' + 100 --add 100 ms to account for unknown network latency
@@ -110,6 +112,6 @@ callTimeout mTimeout conn msg = do
110112asyncCall :: (Serialise request, Serialise response) => Connection response -> request -> IO (Either ConnectionError ())
111113asyncCall conn msg = do
112114 requestID <- UUID <$> UUIDBase.nextRandom
113− sendMessage (AsyncRequest requestID msg) (_conn_sock conn)
115+ sendMessage (AsyncRequest requestID msg) (_conn_sockLock conn)
114116 pure (Right ())
115117
src/Network/RPC/Curryer/Server.hs+32−38
@@ -12,7 +12,7 @@ import Codec.Winery
1212import Codec.Winery.Internal (varInt, decodeVarInt, getBytes)
1313import Codec.Winery.Class (mkExtractor)
1414import GHC.Generics
15−import Control.Concurrent.MVar (MVar)
15+import Control.Concurrent.MVar (MVar, newMVar, withMVar)
1616import Control.Exception
1717import Data.Function ((&))
1818import Data.Word
@@ -50,6 +50,20 @@ traceBytes msg bs = traceShowM (msg, BS.length bs, bs)
5050traceBytes _ _ = pure ()
5151#endif
5252
53+data Locking a = Locking (MVar ()) a
54+
55+newLock :: a -> IO (Locking a)
56+newLock x = do
57+ lock <- newMVar ()
58+ pure (Locking lock x)
59+
60+withLock :: Locking a -> (a -> IO b) -> IO b
61+withLock (Locking mvar v) m =
62+ withMVar mvar $ \_ -> m v
63+
64+lockless :: Locking a -> a
65+lockless (Locking _ a) = a
66+
5367type Timeout = Int
5468
5569data Message a = Response UUID a
@@ -131,23 +145,24 @@ serve connhandler userMsgHandler hostaddr port mSockLock = do
131145 Right dec -> dec
132146 handleSock sock = do
133147 mResp <- connhandler
148+ lockingSocket <- newLock sock
134149 case mResp of
135150 Nothing -> pure ()
136151 Just resp -> do
137152 requestID <- UUID <$> UUIDBase.nextRandom
138− sendMessage (AsyncRequest requestID resp) sock
139− drainSocketMessages sock (decoder userMsgHandler) (serverMessageHandler sock userMsgHandler)
153+ sendMessage (AsyncRequest requestID resp) lockingSocket
154+ drainSocketMessages sock (decoder userMsgHandler) (serverMessageHandler lockingSocket userMsgHandler)
140155 serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & parallely . S.mapM (handleWithM handleSock) & S.drain
141156 pure True
142157
143158serverMessageHandler :: forall req resp. (Show req,
144159 Serialise req,
145160 Serialise resp)
146− => Socket
161+ => Locking Socket
147162 -> NewMessageHandler req resp
148163 -> Message req
149164 -> IO ()
150−serverMessageHandler sock requestMessageHandler msg = do
165+serverMessageHandler sockLock requestMessageHandler msg = do
151166 --putStrLn $ "GOT MSG " ++ show msg
152167 let --runTimeout :: IO (HandlerResponse resp) -> IO (Maybe (HandlerResponse resp))
153168 runTimeout mTimeout m = case mTimeout of
@@ -159,18 +174,18 @@ serverMessageHandler sock requestMessageHandler msg = do
159174 let normalResponder = do
160175 resp <- runTimeout mTimeout (requestMessageHandler val)
161176 case resp of
162− Just (HandlerResponse responseVal) -> sendMessage (Response requestID responseVal) sock
177+ Just (HandlerResponse responseVal) -> sendMessage (Response requestID responseVal) sockLock
163178 Just NoResponse -> error "attempt to return non-response to expected response message"
164− Just (HandlerException exc) -> sendMessage (ExceptionResponse @(Message resp) requestID exc) sock
179+ Just (HandlerException exc) -> sendMessage (ExceptionResponse @(Message resp) requestID exc) sockLock
165180 Nothing ->
166− sendMessage (TimedOutResponse @(Message resp) requestID) sock
181+ sendMessage (TimedOutResponse @(Message resp) requestID) sockLock
167182 excHandler :: SomeException -> IO ()
168183 excHandler e = do
169184 --send exception to client
170− sendMessage (ExceptionResponse @(Message resp) requestID (show e)) sock
185+ sendMessage (ExceptionResponse @(Message resp) requestID (show e)) sockLock
171186 throwIO e
172187 catch normalResponder excHandler
173− AsyncRequest _ val -> do
188+ AsyncRequest _ val ->
174189 void $ requestMessageHandler val
175190 --no response necessary
176191 ExceptionResponse{} -> putStrLn "client sent exception response"
@@ -192,36 +207,15 @@ drainSocketMessages sock decoder msgHandler = do
192207 S.drain $ serially $ S.parseMany messageBoundaryP sockStream & S.mapM handler
193208
194209--send length-tagged bytestring, perhaps should be in network byte order?
195−sendMessage :: Serialise a => Message a -> Socket -> IO ()
196−sendMessage msg socket' = do
210+sendMessage :: Serialise a => Message a -> Locking Socket -> IO ()
211+sendMessage msg sockLock = do
197212 let
198213 msgbytes = serialiseOnly msg
199214 fullbytes = lenbytes <> msgbytes
200215 len = BS.length msgbytes
201216 lenbytes = BO.bytestring32 (fromIntegral len)
202−
203− byteCount <- Socket.send socket' fullbytes
204− when (byteCount /= BS.length fullbytes) (error "bytes sent mismatch")
205− traceBytes "sent bytes" fullbytes
206−
207−
208−
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.
213−{-
214−{-# INLINE toArrayS #-}
215−toArrayS :: BS.ByteString -> SA.Array Word8
216−--slow path
217−--toArrayS = SA.fromList . BS.unpack
218−toArrayS (BSI.PS fp off len) = SA.Array nfp endPtr
219− where
220− nfp = fp `plusForeignPtr` off
221− endPtr = unsafeForeignPtrToPtr nfp `plusPtr` len `plusPtr` 1 -- ? why +1?
222−
223−toArraySlow :: BS.ByteString -> SA.Array Word8
224−toArraySlow bs = arr
225− where
226− arr = SA.fromList (BS.unpack bs)
227−-}
217+
218+ --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
219+ withLock sockLock $ \socket' ->
220+ Socket.sendAll socket' fullbytes
221+ traceBytes "sendMessage" fullbytes
test/Curryer/Test/Basic.hs+28−5
@@ -19,13 +19,15 @@ testTree = testGroup "basic" [testCase "simple" testSimpleCall,
1919 testCase "client async" testAsyncServerCall,
2020 testCase "server async" testAsyncClientCall,
2121 testCase "client sync timeout" testSyncClientCallTimeout,
22− testCase "server-side exception" testSyncException
22+ testCase "server-side exception" testSyncException,
23+ testCase "multi-threaded client" testMultithreadedClient
2324 ]
2425
2526--client-to-server request
2627data TestRequest = AddTwoNumbersReq Int Int
2728 | TestAsyncReq String
2829 | TestCallMeBackReq String
30+ | RoundtripStringReq String
2931 | DelayMicrosecondsReq Int
3032 | ThrowServerSideExceptionReq
3133 deriving (Generic, Show)
@@ -34,13 +36,13 @@ data TestRequest = AddTwoNumbersReq Int Int
3436--server responds to client
3537data TestResponse = AddTwoNumbersResp Int
3638 | AsyncHello String
37− | TestCallMeBackResp String
3839 | DelayMicrosecondsResp
40+ | RoundtripStringResp String
3941 deriving (Generic, Show, Eq)
4042 deriving Serialise via WineryVariant TestResponse
4143
4244testServerMessageHandler :: Maybe (MVar String) -> TestRequest -> IO (HandlerResponse TestResponse)
43−testServerMessageHandler mAsyncMVar msg = do
45+testServerMessageHandler mAsyncMVar msg =
4446 --print msg
4547 case msg of
4648 --round-trip request to add two Ints
@@ -52,6 +54,7 @@ testServerMessageHandler mAsyncMVar msg = do
5254 Just mvar -> putMVar mvar s
5355 pure NoResponse
5456 TestAsyncReq _ -> pure NoResponse
57+ RoundtripStringReq s -> pure (HandlerResponse (RoundtripStringResp s))
5558 DelayMicrosecondsReq ms -> do
5659 threadDelay ms
5760 pure (HandlerResponse DelayMicrosecondsResp)
@@ -85,7 +88,7 @@ testAsyncServerCall = do
8588 receivedAsyncMessageVar <- newEmptyMVar
8689
8790 let testServerNewConnHandler :: NewConnectionHandler TestResponse
88− testServerNewConnHandler = do
91+ testServerNewConnHandler =
8992 pure (Just (AsyncHello "welcome"))
9093
9194 server <- async (serve testServerNewConnHandler (testServerMessageHandler Nothing) localHostAddr 0 (Just portReadyVar))
@@ -155,4 +158,24 @@ testSyncException = do
155158 Right _ -> assertFailure "missed exception"
156159 close conn
157160 cancel server
158−
161+
162+--throw large messages (> PIPE_BUF) at the server from multiple client connections to exercise the socket lock
163+testMultithreadedClient :: Assertion
164+testMultithreadedClient = do
165+ readyVar <- newEmptyMVar
166+
167+ server <- async (serve (pure Nothing) (testServerMessageHandler Nothing) localHostAddr 0 (Just readyVar))
168+ (SockAddrInet port _) <- takeMVar readyVar
169+ let clientHandler :: AsyncMessageHandler TestResponse
170+ clientHandler _ = error "async handler called"
171+ mkConn :: IO (Connection TestResponse)
172+ mkConn = connect clientHandler localHostAddr port
173+ conn <- mkConn
174+ let bigString = replicate (1024 * 1000) 'x'
175+ replicateM_ 10 $ do
176+ ret <- call conn (RoundtripStringReq bigString)
177+ assertEqual "big string multithread" (Right (RoundtripStringResp bigString)) ret
178+ putStrLn "plus one"
179+ close conn
180+ cancel server
181+