@@ -1,35 +1,308 @@
1−module Control.Distributed.Process ( expect ) where
1+module Control.Distributed.Process
2+ ( -- * Cloud Haskell API
14+import qualified Data.ByteString as BSS (ByteString, concat)
3−import Data.ByteString.Lazy (ByteString)
15+import qualified Data.ByteString.Lazy as BSL ( ByteString
4−import Data.Typeable (Typeable(typeOf))
16+ , toChunks
5−import Data.Binary (decode)
20+import Data.Binary (Binary, decode, encode, put, get)
22+import qualified Data.Map as Map (empty)
23+import Data.IntMap (IntMap)
24+import qualified Data.IntMap as IntMap (empty, lookup, insert, delete)
25+import qualified Data.List as List (delete)
26+import Data.Typeable (Typeable)
27+import Control.Monad (void, liftM2)
6−import Control.Monad.Reader (MonadReader(..), ReaderT, liftIO)
28+import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
29+import Control.Monad.IO.Class (MonadIO, liftIO)
7import Control.Applicative ((<$>))
30import Control.Applicative ((<$>))
31+import Control.Category ((>>>))
32+import Control.Exception (throwIO)
33+import Control.Concurrent (forkIO)
34+import Control.Concurrent.MVar ( MVar
8import Control.Distributed.Process.Internal.CQueue ( CQueue
43import Control.Distributed.Process.Internal.CQueue ( CQueue
9 , dequeueMatching
44 , dequeueMatching
11import Control.Distributed.Process.Serializable ( Serializable
48import Control.Distributed.Process.Serializable ( Serializable
12 , Fingerprint
49 , Fingerprint
13 , encodeFingerprint
50 , encodeFingerprint
14 , fingerprint
52 , fingerprint
55+import qualified Network.Transport as NT ( Transport
59+ , Reliability(ReliableOrdered)
60+ , defaultConnectHints
69+import qualified Network.Transport.Internal as NTI (encodeInt32, decodeInt32)
70+import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
71+import qualified Data.Accessor.Container as DAC (mapMaybe, intMapMaybe)
73+type LocalProcessId = Int
75+data ProcessId = ProcessId
76+ { processAddress :: NT.EndPointAddress
77+ , processLocalId :: LocalProcessId
79+ deriving (Eq, Ord, Typeable, Show)
81+instance Binary ProcessId where
82+ put pid = do put (processAddress pid)
83+ put (processLocalId pid)
84+ get = liftM2 ProcessId get get
86+data Message = Message
87+ { messageFingerprint :: Fingerprint
88+ , messageEncoding :: BSL.ByteString
91+data LocalNode = LocalNode
92+ { localEndPoint :: NT.EndPoint
93+ , localState :: MVar LocalNodeState
96+data LocalNodeState = LocalNodeState
97+ { _localConnections :: Map ProcessId NT.Connection
98+ , _localProcesses :: IntMap ProcessState
99+ , _localNextProcId :: LocalProcessId
17−data Message = Message { messageFingerprint :: Fingerprint
102+data ProcessState = ProcessState
18− , messageEncoding :: ByteString
103+ { processQueue :: CQueue Message
104+ , processNode :: LocalNode
105+ , processId :: ProcessId
21−data ProcessState = ProcessState { processQueue :: CQueue Message }
108+newtype Process a = Process { unProcess :: ReaderT ProcessState IO a }
109+ deriving (Functor, Monad, MonadIO, MonadReader ProcessState)
23−newtype Process a = Process (ReaderT ProcessState IO a)
111+--------------------------------------------------------------------------------
24− deriving (Monad)
112+-- Cloud Haskell API --
113+--------------------------------------------------------------------------------
26expect :: forall a. Serializable a => Process a
115expect :: forall a. Serializable a => Process a
27−expect = Process $ do
116+expect = do
28 queue <- processQueue <$> ask
117 queue <- processQueue <$> ask
29 let fp = fingerprint (undefined :: a)
118 let fp = fingerprint (undefined :: a)
30 msg <- liftIO $ dequeueMatching queue ((== fp) . messageFingerprint)
119 msg <- liftIO $ dequeueMatching queue ((== fp) . messageFingerprint)
31 return (decode . messageEncoding $ msg)
120 return (decode . messageEncoding $ msg)
122+send :: Serializable a => ProcessId -> a -> Process ()
124+ -- This requires a lookup on every send. If we want to avoid that we need to
125+ -- modify serializable to allow for stateful (IO) deserialization
126+ node <- processNode <$> ask
128+ conn <- connectionTo node pid
129+ sendBinary node pid conn ( encodeFingerprint (fingerprint msg)
130+ : BSL.toChunks (encode msg)
133+getSelfPid :: Process ProcessId
134+getSelfPid = processId <$> ask
136+--------------------------------------------------------------------------------
138+--------------------------------------------------------------------------------
140+newLocalNode :: NT.Transport -> IO LocalNode
141+newLocalNode transport = do
142+ mEndPoint <- NT.newEndPoint transport
144+ Left ex -> throwIO ex
145+ Right endPoint -> do
146+ state <- newMVar LocalNodeState
147+ { _localConnections = Map.empty
148+ , _localProcesses = IntMap.empty
149+ , _localNextProcId = firstNonReservedProcessId
151+ let node = LocalNode { localEndPoint = endPoint
152+ , localState = state
154+ void . forkIO $ handleIncomingMessages node
157+runProcess :: LocalNode -> Process () -> IO ()
158+runProcess node proc = do
159+ done <- newEmptyMVar
160+ void $ forkProcess node (proc >> liftIO (putMVar done ()))
163+forkProcess :: LocalNode -> Process () -> IO ProcessId
164+forkProcess node proc = do
166+ state <- modifyMVar (localState node) $ \st -> do
167+ let lpid = st ^. localNextProcId
168+ let pid = ProcessId { processAddress = NT.address (localEndPoint node)
169+ , processLocalId = lpid
171+ let state = ProcessState { processQueue = queue
172+ , processNode = node
175+ return ( (localProcessWithId lpid ^= Just state)
176+ . (localNextProcId ^: (+ 1))
180+ void . forkIO $ runReaderT (unProcess proc) state
181+ return (processId state)
183+handleIncomingMessages :: LocalNode -> IO ()
184+handleIncomingMessages node = go [] IntMap.empty
186+ go halfOpenConns openConns = do
187+ event <- NT.receive endpoint
189+ NT.ConnectionOpened cid _rel _theirAddr ->
190+ go (cid : halfOpenConns) openConns
191+ NT.Received cid payload ->
192+ case IntMap.lookup cid openConns of
194+ let msg = payloadToMessage payload
195+ enqueue (processQueue proc) msg
196+ Nothing -> if cid `elem` halfOpenConns
198+ let pid = NTI.decodeInt32 . BSS.concat $ payload
199+ mProc <- withMVar state $ return . (^. localProcessWithId pid)
202+ go (List.delete pid halfOpenConns)
203+ (IntMap.insert cid proc openConns)
208+ NT.ConnectionClosed cid ->
209+ go (List.delete cid halfOpenConns) (IntMap.delete cid openConns)
212+ NT.EndPointClosed ->
214+ NT.ReceivedMulticast _ _ ->
215+ fail "Unexpected multicast"
217+ state = localState node
218+ endpoint = localEndPoint node
220+--------------------------------------------------------------------------------
221+-- Auxiliary functions --
222+--------------------------------------------------------------------------------
224+connectionTo :: LocalNode -> ProcessId -> IO NT.Connection
225+connectionTo node pid = do
226+ mConn <- withMVar (localState node) $ return . (^. localConnectionTo pid)
228+ Just conn -> return conn
229+ Nothing -> createConnectionTo node pid
231+createConnectionTo :: LocalNode -> ProcessId -> IO NT.Connection
232+createConnectionTo node pid = do
233+ mConn <- NT.connect (localEndPoint node)
234+ (processAddress pid)
236+ NT.defaultConnectHints
239+ mConn' <- modifyMVar (localState node) $ \st ->
240+ case st ^. localConnectionTo pid of
241+ Just conn' -> return (st, Just conn')
242+ Nothing -> return ( localConnectionTo pid ^= Just conn $ st
247+ -- Somebody else already created a connection while we weren't looking
248+ -- (We don't want to keep localConnections locked while creating the
249+ -- connection because that would limit concurrency too much, and
250+ -- since Network.Transport supports lgihtweight connections creating
251+ -- an unnecessary connection now and then is cheap anyway)
255+ sendBinary node pid conn [NTI.encodeInt32 . processLocalId $ pid]
260+sendBinary :: LocalNode
265+sendBinary node pid conn msg = do
266+ result <- NT.send conn msg
268+ Right () -> return ()
270+ modifyMVar_ (localState node) $
271+ return . (localConnectionTo pid ^= Nothing)
274+payloadToMessage :: [BSS.ByteString] -> Message
275+payloadToMessage payload = Message fp msg
277+ (encFp, msg) = BSL.splitAt (fromIntegral sizeOfFingerprint)
278+ $ BSL.fromChunks payload
279+ fp = decodeFingerprint . BSS.concat . BSL.toChunks $ encFp
281+--------------------------------------------------------------------------------
283+--------------------------------------------------------------------------------
285+firstNonReservedProcessId :: Int
286+firstNonReservedProcessId = 1024
288+--------------------------------------------------------------------------------
290+--------------------------------------------------------------------------------
292+localConnections :: Accessor LocalNodeState (Map ProcessId NT.Connection)
293+localConnections = accessor _localConnections (\conns st -> st { _localConnections = conns })
295+localProcesses :: Accessor LocalNodeState (IntMap ProcessState)
296+localProcesses = accessor _localProcesses (\procs st -> st { _localProcesses = procs })
298+localNextProcId :: Accessor LocalNodeState Int
299+localNextProcId = accessor _localNextProcId (\pid st -> st { _localNextProcId = pid })
301+localConnectionTo :: ProcessId -> Accessor LocalNodeState (Maybe NT.Connection)
302+localConnectionTo pid = localConnections >>> DAC.mapMaybe pid
304+localProcessWithId :: LocalProcessId -> Accessor LocalNodeState (Maybe ProcessState)
305+localProcessWithId lpid = localProcesses >>> DAC.intMapMaybe lpid
35{-# LANGUAGE CPP, ExplicitForAll, ScopedTypeVariables #-}
308{-# LANGUAGE CPP, ExplicitForAll, ScopedTypeVariables #-}