@@ -0,0 +1,220 @@
1+{-# LANGUAGE ExplicitForAll, ScopedTypeVariables #-}
2+module Control.Distributed.Process (
13+ -- * Process management
26+import qualified Network.Transport as Trans
27+import Network.Transport (Transport)
29+import qualified Data.ByteString.Char8 as BS
30+import Data.ByteString.Char8 (ByteString)
31+import qualified Data.IntMap as IntMap
32+import Data.IntMap (IntMap)
34+import Control.Applicative
36+import Control.Monad.Trans
37+import Control.Concurrent.MVar
38+import Control.Concurrent.Chan
39+import Control.Concurrent
40+import Control.Exception
44+------------------------
45+-- Cloud Haskell layer
48+data NodeId = NodeId -- !Trans.SendEnd !Trans.SendEnd
50+data ProcessId = ProcessId !Trans.SendEnd !NodeId !LocalProcessId
51+type LocalProcessId = Int
53+newtype SendPort a = SendPort Trans.SendEnd
55+newtype Process a = Process { unProcess :: ProcessState -> IO a }
57+instance Functor Process where
58+ fmap f m = Process (\ps -> unProcess m ps >>= \x -> return (f x))
60+instance Applicative Process where
64+instance Monad Process where
65+ m >>= k = Process (\ps -> unProcess m ps >>= \x -> unProcess (k x) ps)
66+ return x = Process (\_ -> return x)
68+instance MonadIO Process where
69+ liftIO io = Process (\_ -> io)
71+getProcessState :: Process ProcessState
72+getProcessState = Process (\ps -> return ps)
74+getLocalNode :: Process LocalNode
75+getLocalNode = prNode <$> getProcessState
77+getSelfPid :: Process ProcessId
78+getSelfPid = prPid <$> getProcessState
80+-- State a process carries around and has local access to
82+data ProcessState = ProcessState {
84+ prChan :: !Trans.ReceiveEnd,
85+ prQueue :: !(CQueue Message),
89+data Message = Message String --string rep of TypeRep, sigh.
92+-- Context for a local node, all processes running on a node have direct access to this
94+data LocalNode = LocalNode {
95+ ndProcessTable :: !(MVar ProcessTable),
96+ ndTransport :: !Transport
99+data ProcessTable = ProcessTable !LocalProcessId !(IntMap ProcessTableEntry)
100+data ProcessTableEntry = ProcessTableEntry {
101+ pteThread :: !ThreadId
104+newLocalNode :: Transport -> IO LocalNode
105+newLocalNode trans = do
106+ processTableVar <- newMVar (ProcessTable 0 IntMap.empty)
108+ ndProcessTable = processTableVar,
112+runProcess :: LocalNode -> Process () -> IO ()
113+runProcess node proc = do
114+ waitVar <- newEmptyMVar
115+ _ <- forkProcess node (proc >> liftIO (putMVar waitVar ()))
116+ --TODO: should use linking for waiting for the end
119+forkProcess :: LocalNode -> Process () -> IO ProcessId
120+forkProcess node (Process action) = do
122+ (sendAddr, chan) <- Trans.newConnection (ndTransport node)
123+ sendEnd <- Trans.connectWith sendAddr Trans.SendHints
124+ processTable@(ProcessTable lpid _) <- takeMVar (ndProcessTable node)
125+ let pid = ProcessId sendEnd NodeId lpid
128+ lpid <- putMVar (ndProcessTable node) (insertProcess tid processTable)
130+ forkIO $ receiverPump chan queue
132+ let pstate = ProcessState {
143+ insertProcess :: ThreadId -> ProcessTable -> ProcessTable
144+ insertProcess tid (ProcessTable nextPid table) =
145+ let pte = ProcessTableEntry tid
146+ in ProcessTable (nextPid+1) (IntMap.insert nextPid pte table)
148+ receiverPump chan queue = do
149+ msgBlobs <- Trans.receive chan
150+ let (typerep, body) = read (concatMap BS.unpack msgBlobs)
151+-- print ("receiverPump", typerep, body)
152+ enqueue queue (Message typerep body)
154+send :: (Typeable a, Show a) => ProcessId -> a -> Process ()
155+send (ProcessId chan _ _) msg =
156+ liftIO (Trans.send chan msgBlobs)
158+ msgBlobs :: [ByteString]
159+ msgBlobs = [BS.pack (show (show (typeOf msg), show msg))]
161+expect :: forall a. (Typeable a, Read a) => Process a
163+ ProcessState { prQueue = queue } <- getProcessState
164+ let typerepstr = show (typeOf (undefined :: a))
165+ Message _ body <- liftIO $
166+ dequeueMatching queue (\(Message typerepstr' _) -> typerepstr' == typerepstr)
169+spawnLocal :: Process () -> Process ProcessId
171+ node <- getLocalNode
172+ liftIO $ forkProcess node proc
175+send :: Serializable a -> ProcessId -> a -> Process ()
176+expect :: Serializable a -> Process a
178+newChan :: Serializable a => Process (SendPort a, ReceivePort a)
179+sendChan :: Serializable a => SendPort a -> a -> Process ()
180+receiveChan :: Serializable a => ReceivePort a -> Process a
182+spawn :: NodeId -> Closure (Process ()) -> Process ProcessId
183+terminate :: ProcessM a
184+getSelfPid :: ProcessM ProcessId
185+getSelfNode :: ProcessM NodeId
187+monitorProcess :: ProcessId -> ProcessId -> MonitorAction -> Process ()
191+-- Concurrent queue for single reader, single writer
193+data CQueue a = CQueue (MVar [a]) -- arrived
194+ (Chan a) -- incomming
196+newCQueue :: IO (CQueue a)
198+ arrived <- newMVar []
199+ incomming <- newChan
200+ return (CQueue arrived incomming)
202+enqueue :: CQueue a -> a -> IO ()
203+enqueue (CQueue _arrived incomming) a = writeChan incomming a
205+dequeueMatching :: forall a. CQueue a -> (a -> Bool) -> IO a
206+dequeueMatching (CQueue arrived incomming) matches = do
207+ modifyMVar arrived (checkArrived [])
209+ checkArrived :: [a] -> [a] -> IO ([a], a)
210+ checkArrived xs' [] = checkIncomming xs'
211+ checkArrived xs' (x:xs)
212+ | matches x = return (reverse xs' ++ xs, x)
213+ | otherwise = checkArrived (x:xs') xs
215+ checkIncomming :: [a] -> IO ([a], a)
216+ checkIncomming xs' = do
217+ x <- readChan incomming
219+ then return (reverse xs', x)
220+ else checkIncomming (x:xs')