git.iko.soy/distributed-process
on 87bb129 help sign in
distributed-process.cabal+4−1
since splitsnapshots
unifiedsplit
@@ -54,7 +54,9 @@ Library
54 ghc-prim >= 0.2 && < 0.5,
54 ghc-prim >= 0.2 && < 0.5,
55 distributed-static >= 0.2 && < 0.4,
55 distributed-static >= 0.2 && < 0.4,
56 rank1dynamic >= 0.1 && < 0.4,
56 rank1dynamic >= 0.1 && < 0.4,
57− syb >= 0.3 && < 0.7
57+ syb >= 0.3 && < 0.7,
58+ transformers-base >= 0.3 && < 0.5,
59+ monad-control >= 0.3 && < 0.4
58 Exposed-modules: Control.Distributed.Process,
60 Exposed-modules: Control.Distributed.Process,
59 Control.Distributed.Process.Closure,
61 Control.Distributed.Process.Closure,
60 Control.Distributed.Process.Debug,
62 Control.Distributed.Process.Debug,
@@ -67,6 +69,7 @@ Library
67 Control.Distributed.Process.Internal.StrictContainerAccessors,
69 Control.Distributed.Process.Internal.StrictContainerAccessors,
68 Control.Distributed.Process.Internal.StrictList,
70 Control.Distributed.Process.Internal.StrictList,
69 Control.Distributed.Process.Internal.StrictMVar,
71 Control.Distributed.Process.Internal.StrictMVar,
72+ Control.Distributed.Process.Internal.ThreadPool,
70 Control.Distributed.Process.Internal.Types,
73 Control.Distributed.Process.Internal.Types,
71 Control.Distributed.Process.Internal.WeakTQueue,
74 Control.Distributed.Process.Internal.WeakTQueue,
72 Control.Distributed.Process.Management,
75 Control.Distributed.Process.Management,
src/Control/Distributed/Process.hs+17−3
@@ -415,7 +415,21 @@ spawnChannelLocal proc = do
415callLocal :: Process a -> Process a
415callLocal :: Process a -> Process a
416callLocal proc = mask $ \release -> do
416callLocal proc = mask $ \release -> do
417 mv <- liftIO newEmptyMVar :: Process (MVar (Either SomeException a))
417 mv <- liftIO newEmptyMVar :: Process (MVar (Either SomeException a))
418− child <- spawnLocal $ try (release proc) >>= liftIO . putMVar mv
418+ child <- spawnLocal $ mask_ $ try (release proc) >>= liftIO . putMVar mv
419+ ref <- monitor child
419− rs <- liftIO (takeMVar mv) `onException`
420+ rs <- liftIO (takeMVar mv)
421+ `onException`
420− (kill child "exception in parent process" >> liftIO (takeMVar mv))
422+ (do kill child "exception in parent process"
423+ waitFor ref
424+ )
421 either throw return rs
425 either throw return rs
426+ where
427+ -- TODO: when processes spawned with spawnLocal inherit the masking state of
428+ -- the parent, this can be simplified to simply reading the MVar.
429+ --
430+ -- At the time of this writing, the spawned process is unmasked and can be
431+ -- killed before the MVar is filled.
432+ waitFor ref = receiveWait
433+ [ matchIf (\(ProcessMonitorNotification ref' _ _) -> ref == ref')
434+ (\_ -> return ())
435+ ]
src/Control/Distributed/Process/Internal/Messaging.hs+4−4
@@ -18,7 +18,7 @@ import Control.Distributed.Process.Serializable ()
18import Control.Concurrent (forkIO)
18import Control.Concurrent (forkIO)
19import Control.Concurrent.Chan (writeChan)
19import Control.Concurrent.Chan (writeChan)
20import Control.Exception (mask_)
20import Control.Exception (mask_)
21−import Control.Monad (unless)
21+import Control.Monad (unless, void)
22import Control.Monad.IO.Class (liftIO)
22import Control.Monad.IO.Class (liftIO)
23import Control.Monad.Reader (ask)
23import Control.Monad.Reader (ask)
24import qualified Network.Transport as NT
24import qualified Network.Transport as NT
@@ -30,7 +30,7 @@ import qualified Network.Transport as NT
30 , close
30 , close
31 )
31 )
32import Control.Distributed.Process.Internal.Types
32import Control.Distributed.Process.Internal.Types
33− ( LocalNode(localState, localEndPoint, localCtrlChan)
33+ ( LocalNode(localEndPoint, localCtrlChan)
34 , withValidLocalState
34 , withValidLocalState
35 , modifyValidLocalState
35 , modifyValidLocalState
36 , modifyValidLocalState_
36 , modifyValidLocalState_
@@ -146,9 +146,9 @@ disconnect node from to = mask_ $ do
146 return (vst, return ())
146 return (vst, return ())
147 Just (conn, _) -> do
147 Just (conn, _) -> do
148 return ( localConnectionBetween from to ^= Nothing $ vst
148 return ( localConnectionBetween from to ^= Nothing $ vst
149− , NT.close conn
149+ , void $ forkIO $ NT.close conn
150 )
150 )
151− forM_ mio forkIO
151+ forM_ mio id
152
152
153closeImplicitReconnections :: LocalNode -> Identifier -> IO ()
153closeImplicitReconnections :: LocalNode -> Identifier -> IO ()
154closeImplicitReconnections node to = mask_ $ do
154closeImplicitReconnections node to = mask_ $ do
src/Control/Distributed/Process/Internal/ThreadPool.hsnew file+78
@@ -0,0 +1,78 @@
1+-- | An implementation of a pool of threads
2+--
3+{-# LANGUAGE RecursiveDo #-}
4+module Control.Distributed.Process.Internal.ThreadPool
5+ ( newThreadPool
6+ , submitTask
7+ , lookupWorker
8+ , ThreadPool
9+ ) where
10+
11+import Control.Exception
12+import Control.Monad
13+import Data.IORef
14+import qualified Data.Map as Map
15+
16+
17+-- | A pool of worker threads that execute tasks.
18+--
19+-- Each worker thread is named with a key @k@. Tasks are submitted to a
20+-- specific worker using its key. While the worker is busy the tasks are queued.
21+-- When there are no more queued tasks the worker ceases to exist.
22+--
23+-- The next time a task is submitted the worker will be respawned.
24+--
25+newtype ThreadPool k w = ThreadPool (IORef (Map.Map k (Maybe (IO ()), w)))
26+
27+-- Each worker has an entry in the map with a closure that contains all
28+-- queued actions fot it.
29+--
30+-- No entry in the map is kept for defunct workers.
31+
32+-- | Creates a pool with no workers.
33+newThreadPool :: IO (ThreadPool k w)
34+newThreadPool = fmap ThreadPool $ newIORef Map.empty
35+
36+-- | @submitTask pool fork k task@ submits a task for the worker @k@.
37+--
38+-- If worker @k@ is busy, then the task is queued until the worker is available.
39+--
40+-- If worker @k@ does not exist, then the given @fork@ operation is used to
41+-- spawn the worker. @fork@ returns whatever information is deemed useful for
42+-- later retrieval via 'lookupWorker'.
43+--
44+submitTask :: Ord k
45+ => ThreadPool k w
46+ -> (IO () -> IO w)
47+ -> k -> IO () -> IO ()
48+submitTask (ThreadPool mapRef) fork k task = mdo
49+ m' <- join $ atomicModifyIORef mapRef $ \m ->
50+ case Map.lookup k m of
51+ -- There is no worker for this key, create one.
52+ Nothing -> ( m'
53+ , do w <- fork $ flip onException terminateWorker $ do
54+ task
55+ continue
56+ return $ Map.insert k (Nothing, w) m
57+ )
58+ -- Queue an action for the existing worker.
59+ Just (mp, w) ->
60+ (m', return $ Map.insert k (Just $ maybe task (>> task) mp, w) m)
61+ return ()
62+ where
63+ continue = join $ atomicModifyIORef mapRef $ \m ->
64+ case Map.lookup k m of
65+ -- Execute the next batch of queued actions.
66+ Just (Just p, w) -> (Map.insert k (Nothing, w) m, p >> continue)
67+ -- There are no more queued actions. Terminate the worker.
68+ Just (Nothing, w) -> (Map.delete k m, return ())
69+ -- The worker key was removed already (?)
70+ Nothing -> (m, return ())
71+ -- Remove the worker key regardless of whether there are more queued
72+ -- actions.
73+ terminateWorker = atomicModifyIORef mapRef $ \m -> (Map.delete k m, ())
74+
75+-- | Looks up a worker with the given key.
76+lookupWorker :: Ord k => ThreadPool k w -> k -> IO (Maybe w)
77+lookupWorker (ThreadPool mapRef) k =
78+ atomicModifyIORef mapRef $ \m -> (m, fmap snd $ Map.lookup k m)
src/Control/Distributed/Process/Internal/Types.hs+25−4
@@ -1,9 +1,11 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE ExistentialQuantification #-}
4{-# LANGUAGE ExistentialQuantification #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GADTs #-}
5−{-# LANGUAGE DeriveGeneric #-}
7+{-# LANGUAGE MultiParamTypeClasses #-}
6−{-# LANGUAGE CPP #-}
8+{-# LANGUAGE TypeFamilies #-}
7
9
8-- | Types used throughout the Cloud Haskell framework
10-- | Types used throughout the Cloud Haskell framework
9--
11--
@@ -116,7 +118,9 @@ import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
116import Control.Applicative (Applicative, Alternative, (<$>), (<*>))
118import Control.Applicative (Applicative, Alternative, (<$>), (<*>))
117import Control.Monad.Fix (MonadFix)
119import Control.Monad.Fix (MonadFix)
118import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
120import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
119−import Control.Monad.IO.Class (MonadIO)
121+import Control.Monad.IO.Class (MonadIO(..))
122+import Control.Monad.Base (MonadBase(..))
123+import Control.Monad.Trans.Control (MonadBaseControl(..))
120import Control.Distributed.Process.Serializable
124import Control.Distributed.Process.Serializable
121 ( Fingerprint
125 ( Fingerprint
122 , Serializable
126 , Serializable
@@ -133,6 +137,7 @@ import Control.Distributed.Process.Internal.StrictMVar
133 , modifyMVar
137 , modifyMVar
134 , modifyMVar_
138 , modifyMVar_
135 )
139 )
140+import Control.Distributed.Process.Internal.ThreadPool
136import Control.Distributed.Process.Internal.WeakTQueue (TQueue)
141import Control.Distributed.Process.Internal.WeakTQueue (TQueue)
137import Control.Distributed.Static (RemoteTable, Closure)
142import Control.Distributed.Static (RemoteTable, Closure)
138import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)
143import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)
@@ -262,6 +267,8 @@ data LocalNode = LocalNode
262 -- | Runtime lookup table for supporting closures
267 -- | Runtime lookup table for supporting closures
263 -- TODO: this should be part of the CH state, not the local endpoint state
268 -- TODO: this should be part of the CH state, not the local endpoint state
264 , remoteTable :: !RemoteTable
269 , remoteTable :: !RemoteTable
270+ -- The pool of threads and queues to send messages
271+ , localSendPool :: ThreadPool NodeId ThreadId
265 }
272 }
266
273
267data ImplicitReconnect = WithImplicitReconnect | NoImplicitReconnect
274data ImplicitReconnect = WithImplicitReconnect | NoImplicitReconnect
@@ -345,7 +352,21 @@ data LocalProcessState = LocalProcessState
345newtype Process a = Process {
352newtype Process a = Process {
346 unProcess :: ReaderT LocalProcess IO a
353 unProcess :: ReaderT LocalProcess IO a
347 }
354 }
355+ deriving ( Applicative, Functor, Monad, MonadFix, MonadIO
348− deriving (Functor, Monad, MonadIO, MonadReader LocalProcess, Typeable, Applicative, MonadFix)
356+ , MonadReader LocalProcess, Typeable
357+ )
358+
359+instance MonadBase IO Process where
360+ liftBase = liftIO
361+
362+instance MonadBaseControl IO Process where
363+ newtype StM Process a =
364+ StMProcess { unStMProcess :: StM (ReaderT LocalProcess IO) a }
365+
366+ liftBaseWith f = Process $ liftBaseWith $ \run ->
367+ f $ fmap StMProcess . run . unProcess
368+
369+ restoreM = Process . restoreM . unStMProcess
349
370
350--------------------------------------------------------------------------------
371--------------------------------------------------------------------------------
351-- Typed channels --
372-- Typed channels --
src/Control/Distributed/Process/Node.hs+24−7
@@ -83,6 +83,12 @@ import Control.Distributed.Process.Internal.StrictMVar
83 , putMVar
83 , putMVar
84 , takeMVar
84 , takeMVar
85 )
85 )
86+import Control.Distributed.Process.Internal.ThreadPool
87+ ( newThreadPool
88+ , submitTask
89+ , lookupWorker
90+ , ThreadPool
91+ )
86import Control.Concurrent.Chan (newChan, writeChan, readChan)
92import Control.Concurrent.Chan (newChan, writeChan, readChan)
87import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
93import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
88import Control.Concurrent.STM
94import Control.Concurrent.STM
@@ -249,12 +255,14 @@ createBareLocalNode endPoint rtable = do
249 , _localConnections = Map.empty
255 , _localConnections = Map.empty
250 }
256 }
251 ctrlChan <- newChan
257 ctrlChan <- newChan
258+ sendPool <- newThreadPool
252 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
259 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
253 , localEndPoint = endPoint
260 , localEndPoint = endPoint
254 , localState = state
261 , localState = state
255 , localCtrlChan = ctrlChan
262 , localCtrlChan = ctrlChan
256 , localEventBus = MxEventBusInitialising
263 , localEventBus = MxEventBusInitialising
257 , remoteTable = rtable
264 , remoteTable = rtable
265+ , localSendPool = sendPool
258 }
266 }
259 tracedNode <- startMxAgent node
267 tracedNode <- startMxAgent node
260
268
@@ -580,6 +588,8 @@ handleIncomingMessages node = go initConnectionState
580 , ctrlMsgSignal = Died nid DiedDisconnect
588 , ctrlMsgSignal = Died nid DiedDisconnect
581 }
589 }
582 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
590 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
591+ liftIO $ lookupWorker (localSendPool node) (NodeId theirAddr)
592+ >>= maybe (return ()) killThread
583 closeImplicitReconnections node nid
593 closeImplicitReconnections node nid
584 go ( (incomingFrom theirAddr ^= Set.empty)
594 go ( (incomingFrom theirAddr ^= Set.empty)
585 . (incoming ^: Map.filterWithKey (const . notLost))
595 . (incoming ^: Map.filterWithKey (const . notLost))
@@ -634,6 +644,12 @@ data NCState = NCState
634 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
644 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
635 }
645 }
636
646
647+submitSendPool :: LocalNode -> NodeId -> IO () -> IO ()
648+submitSendPool node nid task = submitTask (localSendPool node) fork nid task
649+ where
650+ fork io =
651+ forkIO $ io `Exception.catch` \(NodeClosedException _) -> return ()
652+
637newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
653newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
638 deriving ( Applicative
654 deriving ( Applicative
639 , Functor
655 , Functor
@@ -644,11 +660,12 @@ newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
644 )
660 )
645
661
646initNCState :: NCState
662initNCState :: NCState
647−initNCState = NCState { _links = Map.empty
663+initNCState = NCState
664+ { _links = Map.empty
648− , _monitors = Map.empty
665+ , _monitors = Map.empty
649− , _registeredHere = Map.empty
666+ , _registeredHere = Map.empty
650− , _registeredOnNodes = Map.empty
667+ , _registeredOnNodes = Map.empty
651− }
668+ }
652
669
653-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
670-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
654-- type is deliberately not exported so it cannot be caught explicitly.
671-- type is deliberately not exported so it cannot be caught explicitly.
@@ -666,7 +683,7 @@ ncSendToProcess pid msg = do
666 node <- ask
683 node <- ask
667 if processNodeId pid == localNodeId node
684 if processNodeId pid == localNodeId node
668 then ncEffectLocalSend node pid msg
685 then ncEffectLocalSend node pid msg
669− else liftIO $ sendBinary node
686+ else liftIO $ submitSendPool node (processNodeId pid) $ sendBinary node
670 (NodeIdentifier $ localNodeId node)
687 (NodeIdentifier $ localNodeId node)
671 (NodeIdentifier $ processNodeId pid)
688 (NodeIdentifier $ processNodeId pid)
672 WithImplicitReconnect
689 WithImplicitReconnect
@@ -679,7 +696,7 @@ ncSendToNode to msg = do
679 node <- ask
696 node <- ask
680 liftIO $ if to == localNodeId node
697 liftIO $ if to == localNodeId node
681 then writeChan (localCtrlChan node) $! msg
698 then writeChan (localCtrlChan node) $! msg
682− else sendBinary node
699+ else submitSendPool node to $ sendBinary node
683 (NodeIdentifier $ localNodeId node)
700 (NodeIdentifier $ localNodeId node)
684 (NodeIdentifier to)
701 (NodeIdentifier to)
685 WithImplicitReconnect
702 WithImplicitReconnect