packages feed

distributed-process 0.4.1 → 0.4.2

raw patch · 23 files changed

+2108/−883 lines, 23 filesdep ~test-frameworkdep ~test-framework-hunitnew-uploaderPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: test-framework, test-framework-hunit

API changes (from Hackage documentation)

- Control.Distributed.Process.Internal.StrictList: length :: StrictList a -> Int
- Control.Distributed.Process.Internal.StrictList: reverse :: StrictList a -> StrictList a
- Control.Distributed.Process.Internal.StrictList: reverse' :: StrictList a -> StrictList a -> StrictList a
+ Control.Distributed.Process: Handler :: (e -> Process a) -> Handler a
+ Control.Distributed.Process: ProcessInfo :: NodeId -> [String] -> Maybe Int -> [(ProcessId, MonitorRef)] -> [ProcessId] -> ProcessInfo
+ Control.Distributed.Process: RegisterReply :: String -> Bool -> RegisterReply
+ Control.Distributed.Process: catchExit :: (Show a, Serializable a) => Process b -> (ProcessId -> a -> Process b) -> Process b
+ Control.Distributed.Process: catches :: Process a -> [Handler a] -> Process a
+ Control.Distributed.Process: catchesExit :: Process b -> [(ProcessId -> AbstractMessage -> (Process (Maybe b)))] -> Process b
+ Control.Distributed.Process: data Handler a
+ Control.Distributed.Process: data ProcessInfo
+ Control.Distributed.Process: data RegisterReply
+ Control.Distributed.Process: die :: Serializable a => a -> Process b
+ Control.Distributed.Process: exit :: Serializable a => ProcessId -> a -> Process ()
+ Control.Distributed.Process: getProcessInfo :: ProcessId -> Process (Maybe ProcessInfo)
+ Control.Distributed.Process: infoLinks :: ProcessInfo -> [ProcessId]
+ Control.Distributed.Process: infoMessageQueueLength :: ProcessInfo -> Maybe Int
+ Control.Distributed.Process: infoMonitors :: ProcessInfo -> [(ProcessId, MonitorRef)]
+ Control.Distributed.Process: infoNode :: ProcessInfo -> NodeId
+ Control.Distributed.Process: infoRegisteredNames :: ProcessInfo -> [String]
+ Control.Distributed.Process: kill :: ProcessId -> String -> Process ()
+ Control.Distributed.Process: matchAnyIf :: Serializable a => (a -> Bool) -> (AbstractMessage -> Process b) -> Match b
+ Control.Distributed.Process: matchChan :: ReceivePort a -> (a -> Process b) -> Match b
+ Control.Distributed.Process: maybeHandleMessage :: AbstractMessage -> forall a b. Serializable a => (a -> Process b) -> Process (Maybe b)
+ Control.Distributed.Process: try :: Exception e => Process a -> Process (Either e a)
+ Control.Distributed.Process: withMonitor :: ProcessId -> Process a -> Process a
+ Control.Distributed.Process.Closure: mkStaticClosure :: Name -> Q Exp
+ Control.Distributed.Process.Internal.CQueue: MatchChan :: (STM a) -> MatchOn m a
+ Control.Distributed.Process.Internal.CQueue: MatchMsg :: (m -> Maybe a) -> MatchOn m a
+ Control.Distributed.Process.Internal.CQueue: data MatchOn m a
+ Control.Distributed.Process.Internal.Closure.TH: mkStaticClosure :: Name -> Q Exp
+ Control.Distributed.Process.Internal.Primitives: Handler :: (e -> Process a) -> Handler a
+ Control.Distributed.Process.Internal.Primitives: ProcessInfo :: NodeId -> [String] -> Maybe Int -> [(ProcessId, MonitorRef)] -> [ProcessId] -> ProcessInfo
+ Control.Distributed.Process.Internal.Primitives: catchExit :: (Show a, Serializable a) => Process b -> (ProcessId -> a -> Process b) -> Process b
+ Control.Distributed.Process.Internal.Primitives: catches :: Process a -> [Handler a] -> Process a
+ Control.Distributed.Process.Internal.Primitives: catchesExit :: Process b -> [(ProcessId -> AbstractMessage -> (Process (Maybe b)))] -> Process b
+ Control.Distributed.Process.Internal.Primitives: data Handler a
+ Control.Distributed.Process.Internal.Primitives: data ProcessExitException
+ Control.Distributed.Process.Internal.Primitives: data ProcessInfo
+ Control.Distributed.Process.Internal.Primitives: die :: Serializable a => a -> Process b
+ Control.Distributed.Process.Internal.Primitives: exit :: Serializable a => ProcessId -> a -> Process ()
+ Control.Distributed.Process.Internal.Primitives: getProcessInfo :: ProcessId -> Process (Maybe ProcessInfo)
+ Control.Distributed.Process.Internal.Primitives: infoLinks :: ProcessInfo -> [ProcessId]
+ Control.Distributed.Process.Internal.Primitives: infoMessageQueueLength :: ProcessInfo -> Maybe Int
+ Control.Distributed.Process.Internal.Primitives: infoMonitors :: ProcessInfo -> [(ProcessId, MonitorRef)]
+ Control.Distributed.Process.Internal.Primitives: infoNode :: ProcessInfo -> NodeId
+ Control.Distributed.Process.Internal.Primitives: infoRegisteredNames :: ProcessInfo -> [String]
+ Control.Distributed.Process.Internal.Primitives: instance Functor Handler
+ Control.Distributed.Process.Internal.Primitives: kill :: ProcessId -> String -> Process ()
+ Control.Distributed.Process.Internal.Primitives: matchAnyIf :: Serializable a => (a -> Bool) -> (AbstractMessage -> Process b) -> Match b
+ Control.Distributed.Process.Internal.Primitives: matchChan :: ReceivePort a -> (a -> Process b) -> Match b
+ Control.Distributed.Process.Internal.Primitives: maybeHandleMessage :: AbstractMessage -> forall a b. Serializable a => (a -> Process b) -> Process (Maybe b)
+ Control.Distributed.Process.Internal.Primitives: trace :: String -> Process ()
+ Control.Distributed.Process.Internal.Primitives: try :: Exception e => Process a -> Process (Either e a)
+ Control.Distributed.Process.Internal.Primitives: withMonitor :: ProcessId -> Process a -> Process a
+ Control.Distributed.Process.Internal.StrictList: Append :: !(StrictList a) -> !(StrictList a) -> StrictList a
+ Control.Distributed.Process.Internal.StrictList: Snoc :: !(StrictList a) -> !a -> StrictList a
+ Control.Distributed.Process.Internal.StrictList: append :: StrictList a -> StrictList a -> StrictList a
+ Control.Distributed.Process.Internal.StrictList: foldr :: (a -> b -> b) -> b -> StrictList a -> b
+ Control.Distributed.Process.Internal.Trace: Trace :: a -> TraceArg
+ Control.Distributed.Process.Internal.Trace: TraceStr :: String -> TraceArg
+ Control.Distributed.Process.Internal.Trace: data TraceArg
+ Control.Distributed.Process.Internal.Trace: data Tracer
+ Control.Distributed.Process.Internal.Trace: startTracing :: LocalNode -> IO LocalNode
+ Control.Distributed.Process.Internal.Trace: stopTracer :: Tracer -> IO ()
+ Control.Distributed.Process.Internal.Trace: trace :: Tracer -> String -> IO ()
+ Control.Distributed.Process.Internal.Trace: traceFormat :: Tracer -> String -> [TraceArg] -> IO ()
+ Control.Distributed.Process.Internal.Types: EventLogTracer :: !(String -> IO ()) -> Tracer
+ Control.Distributed.Process.Internal.Types: Exit :: !ProcessId -> !Message -> ProcessSignal
+ Control.Distributed.Process.Internal.Types: GetInfo :: !ProcessId -> ProcessSignal
+ Control.Distributed.Process.Internal.Types: InactiveTracer :: Tracer
+ Control.Distributed.Process.Internal.Types: Kill :: !ProcessId -> !String -> ProcessSignal
+ Control.Distributed.Process.Internal.Types: LocalNodeTracer :: !LocalNode -> Tracer
+ Control.Distributed.Process.Internal.Types: LogFileTracer :: !ThreadId -> !(TQueue String) -> !Handle -> Tracer
+ Control.Distributed.Process.Internal.Types: ProcessExitException :: !ProcessId -> !Message -> ProcessExitException
+ Control.Distributed.Process.Internal.Types: ProcessInfo :: NodeId -> [String] -> Maybe Int -> [(ProcessId, MonitorRef)] -> [ProcessId] -> ProcessInfo
+ Control.Distributed.Process.Internal.Types: ProcessInfoNone :: DiedReason -> ProcessInfoNone
+ Control.Distributed.Process.Internal.Types: data ProcessExitException
+ Control.Distributed.Process.Internal.Types: data ProcessInfo
+ Control.Distributed.Process.Internal.Types: data ProcessInfoNone
+ Control.Distributed.Process.Internal.Types: data Tracer
+ Control.Distributed.Process.Internal.Types: forever' :: Monad m => m a -> m b
+ Control.Distributed.Process.Internal.Types: infoLinks :: ProcessInfo -> [ProcessId]
+ Control.Distributed.Process.Internal.Types: infoMessageQueueLength :: ProcessInfo -> Maybe Int
+ Control.Distributed.Process.Internal.Types: infoMonitors :: ProcessInfo -> [(ProcessId, MonitorRef)]
+ Control.Distributed.Process.Internal.Types: infoNode :: ProcessInfo -> NodeId
+ Control.Distributed.Process.Internal.Types: infoRegisteredNames :: ProcessInfo -> [String]
+ Control.Distributed.Process.Internal.Types: instance Binary ProcessInfo
+ Control.Distributed.Process.Internal.Types: instance Binary ProcessInfoNone
+ Control.Distributed.Process.Internal.Types: instance Eq ProcessInfo
+ Control.Distributed.Process.Internal.Types: instance Exception ProcessExitException
+ Control.Distributed.Process.Internal.Types: instance Show ProcessExitException
+ Control.Distributed.Process.Internal.Types: instance Show ProcessInfo
+ Control.Distributed.Process.Internal.Types: instance Show ProcessInfoNone
+ Control.Distributed.Process.Internal.Types: instance Typeable ProcessExitException
+ Control.Distributed.Process.Internal.Types: instance Typeable ProcessInfo
+ Control.Distributed.Process.Internal.Types: instance Typeable ProcessInfoNone
+ Control.Distributed.Process.Internal.Types: localTracer :: LocalNode -> !Tracer
+ Control.Distributed.Process.Node: instance Exception ProcessKillException
+ Control.Distributed.Process.Node: instance Show ProcessKillException
+ Control.Distributed.Process.Node: instance Typeable ProcessKillException
- Control.Distributed.Process: AbstractMessage :: (ProcessId -> Process ()) -> AbstractMessage
+ Control.Distributed.Process: AbstractMessage :: (ProcessId -> Process ()) -> (forall a b. Serializable a => (a -> Process b) -> Process (Maybe b)) -> AbstractMessage
- Control.Distributed.Process.Internal.CQueue: dequeue :: CQueue a -> BlockSpec -> [a -> Maybe b] -> IO (Maybe b)
+ Control.Distributed.Process.Internal.CQueue: dequeue :: CQueue m -> BlockSpec -> [MatchOn m a] -> IO (Maybe a)
- Control.Distributed.Process.Internal.Primitives: AbstractMessage :: (ProcessId -> Process ()) -> AbstractMessage
+ Control.Distributed.Process.Internal.Primitives: AbstractMessage :: (ProcessId -> Process ()) -> (forall a b. Serializable a => (a -> Process b) -> Process (Maybe b)) -> AbstractMessage
- Control.Distributed.Process.Internal.Types: LocalNode :: !NodeId -> !EndPoint -> !(StrictMVar LocalNodeState) -> !(Chan NCMsg) -> !RemoteTable -> LocalNode
+ Control.Distributed.Process.Internal.Types: LocalNode :: !NodeId -> !EndPoint -> !(StrictMVar LocalNodeState) -> !(Chan NCMsg) -> !Tracer -> !RemoteTable -> LocalNode

Files

benchmarks/Channels.hs view
@@ -23,11 +23,11 @@     receiveChan rc   liftIO . putStrLn $ "Did " ++ show n ++ " pings" -initialProcess :: String -> Process () +initialProcess :: String -> Process () initialProcess "SERVER" = do   us <- getSelfPid   liftIO $ BSL.writeFile "pingServer.pid" (encode us)-  pingServer +  pingServer initialProcess "CLIENT" = do   n <- liftIO $ getLine   them <- liftIO $ decode <$> BSL.readFile "pingServer.pid"@@ -37,5 +37,5 @@ main = do   [role, host, port] <- getArgs   Right transport <- createTransport host port defaultTCPParameters-  node <- newLocalNode transport initRemoteTable -  runProcess node $ initialProcess role +  node <- newLocalNode transport initRemoteTable+  runProcess node $ initialProcess role
benchmarks/Latency.hs view
@@ -18,11 +18,11 @@   replicateM_ n $ send them us >> (expect :: Process ())   liftIO . putStrLn $ "Did " ++ show n ++ " pings" -initialProcess :: String -> Process () +initialProcess :: String -> Process () initialProcess "SERVER" = do   us <- getSelfPid   liftIO $ BSL.writeFile "pingServer.pid" (encode us)-  pingServer +  pingServer initialProcess "CLIENT" = do   n <- liftIO $ getLine   them <- liftIO $ decode <$> BSL.readFile "pingServer.pid"@@ -32,5 +32,5 @@ main = do   [role, host, port] <- getArgs   Right transport <- createTransport host port defaultTCPParameters-  node <- newLocalNode transport initRemoteTable -  runProcess node $ initialProcess role +  node <- newLocalNode transport initRemoteTable+  runProcess node $ initialProcess role
benchmarks/Spawns.hs view
@@ -12,7 +12,7 @@ counter :: Process () counter = go 0   where-    go :: Int -> Process () +    go :: Int -> Process ()     go !n = do       b <- expect       case b of@@ -27,7 +27,7 @@   n' <- expect   liftIO $ print (n == n') -initialProcess :: String -> Process () +initialProcess :: String -> Process () initialProcess "SERVER" = do   us <- getSelfPid   liftIO $ BSL.writeFile "counter.pid" (encode us)@@ -41,5 +41,5 @@ main = do   [role, host, port] <- getArgs   Right transport <- createTransport host port defaultTCPParameters-  node <- newLocalNode transport initRemoteTable -  runProcess node $ initialProcess role +  node <- newLocalNode transport initRemoteTable+  runProcess node $ initialProcess role
benchmarks/Throughput.hs view
@@ -14,8 +14,8 @@  instance Binary a => Binary (SizedList a) where   put (SizedList sz xs) = put sz >> mapM_ put xs-  get = do -    sz <- get +  get = do+    sz <- get     xs <- getMany sz     return (SizedList sz xs) @@ -37,9 +37,9 @@ counter :: Process () counter = go 0   where-    go :: Int -> Process () +    go :: Int -> Process ()     go !n =-      receiveWait +      receiveWait         [ match $ \xs   -> go (n + size (xs :: SizedList Int))         , match $ \them -> send them n >> go 0         ]@@ -52,7 +52,7 @@   n' <- expect   liftIO $ print (packets * sz, n' == packets * sz) -initialProcess :: String -> Process () +initialProcess :: String -> Process () initialProcess "SERVER" = do   us <- getSelfPid   liftIO $ BSL.writeFile "counter.pid" (encode us)@@ -66,5 +66,5 @@ main = do   [role, host, port] <- getArgs   Right transport <- createTransport host port defaultTCPParameters-  node <- newLocalNode transport initRemoteTable -  runProcess node $ initialProcess role +  node <- newLocalNode transport initRemoteTable+  runProcess node $ initialProcess role
distributed-process.cabal view
@@ -1,28 +1,28 @@-Name:          distributed-process -Version:       0.4.1+Name:          distributed-process+Version:       0.4.2 Cabal-Version: >=1.8 Build-Type:    Simple-License:       BSD3 +License:       BSD3 License-File:  LICENSE Copyright:     Well-Typed LLP Author:        Duncan Coutts, Nicolas Wu, Edsko de Vries-Maintainer:    edsko@well-typed.com, duncan@well-typed.com+Maintainer:    watson.timothy@gmail.com, edsko@well-typed.com, duncan@well-typed.com Stability:     experimental Homepage:      http://github.com/haskell-distributed/distributed-process-Bug-Reports:   mailto:edsko@well-typed.com-Synopsis:      Cloud Haskell: Erlang-style concurrency in Haskell +Bug-Reports:   http://github.com/haskell-distributed/distributed-process/issues+Synopsis:      Cloud Haskell: Erlang-style concurrency in Haskell Description:   This is an implementation of Cloud Haskell, as described in                /Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black,                and Simon Peyton Jones                (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),                although some of the details are different. The precise message                passing semantics are based on /A unified semantics for future Erlang/-               by	Hans Svensson, Lars-Åke Fredlund and Clara Benac Earle.+               by Hans Svensson, Lars-Åke Fredlund and Clara Benac Earle.                 You will probably also want to install a Cloud Haskell backend such                as distributed-process-simplelocalnet. Tested-With:   GHC==7.2.2 GHC==7.4.1 GHC==7.4.2-Category:      Control +Category:      Control  Source-Repository head   Type:     git@@ -61,6 +61,7 @@                      Control.Distributed.Process.Internal.Primitives,                      Control.Distributed.Process.Internal.CQueue,                      Control.Distributed.Process.Internal.Types,+                     Control.Distributed.Process.Internal.Trace,                      Control.Distributed.Process.Internal.Closure.BuiltIn,                      Control.Distributed.Process.Internal.Messaging,                      Control.Distributed.Process.Internal.StrictList,@@ -96,14 +97,14 @@                      binary >= 0.5 && < 0.7,                      network >= 2.3 && < 2.5,                      HUnit >= 1.2 && < 1.3,-                     test-framework >= 0.6 && < 0.7,-                     test-framework-hunit >= 0.2 && < 0.3+                     test-framework >= 0.6 && < 0.9,+                     test-framework-hunit >= 0.2.0 && < 0.4   Extensions:        CPP,                      ScopedTypeVariables,                      DeriveDataTypeable,                      GeneralizedNewtypeDeriving-  ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind -  HS-Source-Dirs:    tests +  ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind+  HS-Source-Dirs:    tests  Test-Suite TestClosure   Type:              exitcode-stdio-1.0@@ -118,12 +119,36 @@                      bytestring >= 0.9 && < 0.11,                      network >= 2.3 && < 2.5,                      HUnit >= 1.2 && < 1.3,-                     test-framework >= 0.6 && < 0.7,-                     test-framework-hunit >= 0.2 && < 0.3-  Extensions:        CPP, +                     test-framework >= 0.6 && < 0.9,+                     test-framework-hunit >= 0.2.0 && < 0.4+  Extensions:        CPP,                      ScopedTypeVariables-  ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind +  ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind+  HS-Source-Dirs:    tests++Test-Suite TestStats+  Type:              exitcode-stdio-1.0+  Main-Is:           TestStats.hs+  Build-Depends:     base >= 4.4 && < 5,+                     random >= 1.0 && < 1.1,+                     ansi-terminal >= 0.5 && < 0.6,+                     containers >= 0.4 && < 0.6,+                     stm >= 2.3 && < 2.5,+                     distributed-process,+                     network-transport >= 0.3 && < 0.4,+                     network-transport-tcp >= 0.3 && < 0.4,+                     binary >= 0.5 && < 0.7,+                     network >= 2.3 && < 2.5,+                     HUnit >= 1.2 && < 1.3,+                     test-framework >= 0.6 && < 0.9,+                     test-framework-hunit >= 0.2.0 && < 0.4+  Extensions:        CPP,+                     ScopedTypeVariables,+                     DeriveDataTypeable,+                     GeneralizedNewtypeDeriving+  ghc-options:       -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind   HS-Source-Dirs:    tests +  Executable distributed-process-throughput    if flag(benchmarks)
src/Control/Distributed/Process.hs view
@@ -1,14 +1,19 @@--- | [Cloud Haskell]--- --- This is an implementation of Cloud Haskell, as described in --- /Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black, and Simon--- Peyton Jones--- (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),--- although some of the details are different. The precise message passing--- semantics are based on /A unified semantics for future Erlang/ by	Hans--- Svensson, Lars-Åke Fredlund and Clara Benac Earle.-module Control.Distributed.Process -  ( -- * Basic types +{- | [Cloud Haskell]++This is an implementation of Cloud Haskell, as described in+/Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black, and Simon+Peyton Jones (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),+although some of the details are different. The precise message passing+semantics are based on /A unified semantics for future Erlang/ by Hans+Svensson, Lars-Åke Fredlund and Clara Benac Earle.++For a detailed description of the package and other reference materials,+please see the distributed-process wiki page on github:+<https://github.com/haskell-distributed/distributed-process/wiki>.++-}+module Control.Distributed.Process+  ( -- * Basic types     ProcessId   , NodeId   , Process@@ -17,7 +22,7 @@   , sendPortProcessId   , liftIO -- Reexported for convenience     -- * Basic messaging-  , send +  , send   , expect   , expectTimeout     -- * Channels@@ -37,17 +42,26 @@   , match   , matchIf   , matchUnknown-  , AbstractMessage(..) +  , AbstractMessage(..)   , matchAny+  , matchAnyIf+  , matchChan     -- * Process management   , spawn   , call   , terminate+  , die+  , kill+  , exit+  , catchExit+  , catchesExit   , ProcessTerminationException(..)   , ProcessRegistrationException(..)   , SpawnRef   , getSelfPid   , getSelfNode+  , ProcessInfo(..)+  , getProcessInfo     -- * Monitoring and linking   , link   , linkNode@@ -59,10 +73,11 @@   , monitorNode   , monitorPort   , unmonitor+  , withMonitor+  , MonitorRef -- opaque   , ProcessLinkException(..)   , NodeLinkException(..)   , PortLinkException(..)-  , MonitorRef -- opaque   , ProcessMonitorNotification(..)   , NodeMonitorNotification(..)   , PortMonitorNotification(..)@@ -88,8 +103,12 @@   , whereisRemoteAsync   , nsendRemote   , WhereIsReply(..)+  , RegisterReply(..)     -- * Exception handling   , catch+  , Handler(..)+  , catches+  , try   , mask   , onException   , bracket@@ -102,7 +121,7 @@   , spawnMonitor   , spawnChannel   , DidSpawn(..)-    -- * Local versions of 'spawn' +    -- * Local versions of 'spawn'   , spawnLocal   , spawnChannelLocal     -- * Reconnecting@@ -119,7 +138,7 @@ import Control.Applicative ((<$>)) import Control.Monad.Reader (ask) import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)-import Control.Distributed.Static +import Control.Distributed.Static   ( Closure   , closure   , Static@@ -127,7 +146,7 @@   , closureCompose   , staticClosure   )-import Control.Distributed.Process.Internal.Types +import Control.Distributed.Process.Internal.Types   ( NodeId(..)   , ProcessId(..)   , Process(..)@@ -146,6 +165,7 @@   , ReceivePort(..)   , SendPortId(..)   , WhereIsReply(..)+  , RegisterReply(..)   , LocalProcess(processNode)   , nullProcessId   )@@ -154,8 +174,8 @@   ( sdictSendPort   , sndStatic   , idCP-  , seqCP -  , bindCP +  , seqCP+  , bindCP   , splitCP   , cpLink   , cpSend@@ -164,7 +184,7 @@   ) import Control.Distributed.Process.Internal.Primitives   ( -- Basic messaging-    send +    send   , expect     -- Channels   , newChan@@ -179,13 +199,22 @@   , match   , matchIf   , matchUnknown-  , AbstractMessage(..) +  , AbstractMessage(..)   , matchAny+  , matchAnyIf+  , matchChan     -- Process management   , terminate   , ProcessTerminationException(..)+  , die+  , exit+  , catchExit+  , catchesExit+  , kill   , getSelfPid   , getSelfNode+  , ProcessInfo(..)+  , getProcessInfo     -- Monitoring and linking   , link   , linkNode@@ -197,6 +226,7 @@   , monitorNode   , monitorPort   , unmonitor+  , withMonitor     -- Logging   , say     -- Registry@@ -215,6 +245,9 @@   , unClosure     -- Exception handling   , catch+  , Handler(..)+  , catches+  , try   , mask   , onException   , bracket@@ -231,7 +264,7 @@ import Control.Distributed.Process.Node (forkProcess)  -- INTERNAL NOTES--- +-- -- 1.  'send' never fails. If you want to know that the remote process received --     your message, you will need to send an explicit acknowledgement. If you --     want to know when the remote process failed, you will need to monitor@@ -269,17 +302,17 @@ --       http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/remote.pdf -- -- The precise semantics for message passing is based on--- +-- -- [2] "A Unified Semantics for Future Erlang", Hans Svensson, Lars-Ake Fredlund --     and Clara Benac Earle (not freely available online, unfortunately) -- -- Some pointers to related documentation about Erlang, for comparison and--- inspiration: +-- inspiration: -- -- [3] "Programming Distributed Erlang Applications: Pitfalls and Recipes",---     Hans Svensson and Lars-Ake Fredlund +--     Hans Svensson and Lars-Ake Fredlund --       http://man.lupaworld.com/content/develop/p37-svensson.pdf--- [4] The Erlang manual, sections "Message Sending" and "Send" +-- [4] The Erlang manual, sections "Message Sending" and "Send" --       http://www.erlang.org/doc/reference_manual/processes.html#id82409 --       http://www.erlang.org/doc/reference_manual/expressions.html#send -- [5] Questions "Is the order of message reception guaranteed?" and@@ -297,9 +330,9 @@ -- | Spawn a process -- -- For more information about 'Closure', see--- "Control.Distributed.Process.Closure". +-- "Control.Distributed.Process.Closure". ----- See also 'call'. +-- See also 'call'. spawn :: NodeId -> Closure (Process ()) -> Process ProcessId spawn nid proc = do   us   <- getSelfPid@@ -316,7 +349,7 @@  -- | Spawn a process and link to it ----- Note that this is just the sequential composition of 'spawn' and 'link'. +-- Note that this is just the sequential composition of 'spawn' and 'link'. -- (The "Unified" semantics that underlies Cloud Haskell does not even support -- a synchronous link operation) spawnLink :: NodeId -> Closure (Process ()) -> Process ProcessId@@ -333,16 +366,20 @@   return (pid, ref)  -- | Run a process remotely and wait for it to reply--- +-- -- We monitor the remote process: if it dies before it can send a reply, we die -- too. -- -- For more information about 'Static', 'SerializableDict', and 'Closure', see--- "Control.Distributed.Process.Closure". +-- "Control.Distributed.Process.Closure". ----- See also 'spawn'. -call :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (Process a) -> Process a-call dict nid proc = do +-- See also 'spawn'.+call :: Serializable a+        => Static (SerializableDict a)+        -> NodeId+        -> Closure (Process a)+        -> Process a+call dict nid proc = do   us <- getSelfPid   (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)   -- We are guaranteed to receive the reply before the monitor notification@@ -355,25 +392,25 @@     ]   case mResult of     Right a  -> do-      -- Wait for the monitor message so that we the mailbox doesn't grow -      receiveWait +      -- Wait for the monitor message so that we the mailbox doesn't grow+      receiveWait         [ matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)                   (\(ProcessMonitorNotification {}) -> return ())         ]       -- Clean up connection to pid       reconnect pid       return a-    Left err -> -      fail $ "call: remote process died: " ++ show err +    Left err ->+      fail $ "call: remote process died: " ++ show err  -- | Spawn a child process, have the child link to the parent and the parent -- monitor the child-spawnSupervised :: NodeId -                -> Closure (Process ()) +spawnSupervised :: NodeId+                -> Closure (Process ())                 -> Process (ProcessId, MonitorRef) spawnSupervised nid proc = do   us   <- getSelfPid-  them <- spawn nid (cpLink us `seqCP` proc) +  them <- spawn nid (cpLink us `seqCP` proc)   ref  <- monitor them   return (them, ref) @@ -381,19 +418,19 @@ -- the corresponding 'SendPort'. spawnChannel :: forall a. Typeable a => Static (SerializableDict a)              -> NodeId-             -> Closure (ReceivePort a -> Process ()) +             -> Closure (ReceivePort a -> Process ())              -> Process (SendPort a) spawnChannel dict nid proc = do     us <- getSelfPid-    spawn nid (go us) +    _ <- spawn nid (go us)     expect   where     go :: ProcessId -> Closure (Process ())-    go pid = cpNewChan dict -           `bindCP` +    go pid = cpNewChan dict+           `bindCP`              (cpSend (sdictSendPort dict) pid `splitCP` proc)            `bindCP`-             (idCP `closureCompose` staticClosure sndStatic) +             (idCP `closureCompose` staticClosure sndStatic)  -------------------------------------------------------------------------------- -- Local versions of spawn                                                    --@@ -402,22 +439,22 @@ -- | Spawn a process on the local node spawnLocal :: Process () -> Process ProcessId spawnLocal proc = do-  node <- processNode <$> ask +  node <- processNode <$> ask   liftIO $ forkProcess node proc  -- | Create a new typed channel, spawn a process on the local node, passing it -- the receive port, and return the send port spawnChannelLocal :: Serializable a-                  => (ReceivePort a -> Process ()) +                  => (ReceivePort a -> Process ())                   -> Process (SendPort a)-spawnChannelLocal proc = do -  node <- processNode <$> ask +spawnChannelLocal proc = do+  node <- processNode <$> ask   liftIO $ do     mvar <- newEmptyMVar-    forkProcess node $ do+    _ <- forkProcess node $ do       -- It is important that we allocate the new channel in the new process,       -- because otherwise it will be associated with the wrong process ID       (sport, rport) <- newChan       liftIO $ putMVar mvar sport       proc rport-    takeMVar mvar +    takeMVar mvar
src/Control/Distributed/Process/Closure.hs view
@@ -15,15 +15,15 @@ -- > f = ... -- -- you can use a Template Haskell splice to create a static version of 'f':--- +-- -- > $(mkStatic 'f) :: forall a1 .. an. (Typeable a1, .., Typeable an) => Static T--- +-- -- Every module that you write that contains calls to 'mkStatic' needs to -- have a call to 'remotable': -- -- > remotable [ 'f, 'g, ... ] ----- where you must pass every function (or other value) that you pass as an +-- where you must pass every function (or other value) that you pass as an -- argument to 'mkStatic'. The call to 'remotable' will create a definition -- -- > __remoteTable :: RemoteTable -> RemoteTable@@ -32,12 +32,12 @@ -- Cloud Haskell. You should have (at most) one call to 'remotable' per module, -- and compose all created functions when initializing Cloud Haskell: ----- > let rtable :: RemoteTable +-- > let rtable :: RemoteTable -- >     rtable = M1.__remoteTable -- >            . M2.__remoteTable -- >            . ... -- >            . Mn.__remoteTable--- >            $ initRemoteTable +-- >            $ initRemoteTable -- -- NOTE: If you get a type error from ghc along these lines --@@ -52,19 +52,19 @@ -- -- > call :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (Process a) -> Process a ----- Given some serializable type 'T' you can define +-- Given some serializable type 'T' you can define -- -- > sdictT :: SerializableDict T -- > sdictT = SerializableDict -- -- and then have--- +-- -- > $(mkStatic 'sdictT) :: Static (SerializableDict T) -- -- However, since these dictionaries are so frequently required Cloud Haskell -- provides special support for them.  When you call 'remotable' on a -- /monomorphic/ function @f :: T1 -> T2@--- +-- -- > remotable ['f] -- -- then a serialization dictionary is automatically created for you, which you@@ -80,26 +80,26 @@ -- -- Suppose you have a process ----- > isPrime :: Integer -> Process Bool +-- > isPrime :: Integer -> Process Bool ----- Then +-- Then -- -- > $(mkClosure 'isPrime) :: Integer -> Closure (Process Bool) ----- which you can then 'call', for example, to have a remote node check if +-- which you can then 'call', for example, to have a remote node check if -- a number is prime. -- -- In general, if you have a /monomorphic/ function -- -- > f :: T1 -> T2--- +-- -- then -- -- > $(mkClosure 'f) :: T1 -> Closure T2 -- -- provided that 'T1' is serializable (*) (remember to pass 'f' to 'remotable'). ----- (You can also create closures manually--see the documentation of +-- (You can also create closures manually--see the documentation of -- "Control.Distributed.Static" for examples.) -- -- [Example]@@ -114,34 +114,34 @@ -- > import Control.Distributed.Process.Closure -- > import Control.Distributed.Process.Backend.SimpleLocalnet -- > import Control.Distributed.Process.Node (initRemoteTable)--- > +-- > -- > isPrime :: Integer -> Process Bool -- > isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..] -- >   where -- >     sieve :: [Integer] -> [Integer] -- >     sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]--- > +-- > -- > remotable ['isPrime]--- > +-- > -- > master :: [NodeId] -> Process () -- > master [] = liftIO $ putStrLn "no slaves" -- > master (slave:_) = do -- >   isPrime79 <- call $(functionTDict 'isPrime) slave ($(mkClosure 'isPrime) (79 :: Integer))--- >   liftIO $ print isPrime79 --- > +-- >   liftIO $ print isPrime79+-- > -- > main :: IO () -- > main = do -- >   args <- getArgs -- >   case args of -- >     ["master", host, port] -> do--- >       backend <- initializeBackend host port rtable --- >       startMaster backend master +-- >       backend <- initializeBackend host port rtable+-- >       startMaster backend master -- >     ["slave", host, port] -> do--- >       backend <- initializeBackend host port rtable +-- >       backend <- initializeBackend host port rtable -- >       startSlave backend -- >   where -- >     rtable :: RemoteTable--- >     rtable = __remoteTable initRemoteTable +-- >     rtable = __remoteTable initRemoteTable -- -- [Notes] --@@ -150,42 +150,43 @@ --     a priori if 'T1' is serializable or not due to a bug in the Template --     Haskell libraries (<http://hackage.haskell.org/trac/ghc/ticket/7066>) ----- (**) Even though 'call' is passed an explicit serialization ---      dictionary, we still need the 'Serializable' constraint because +-- (**) Even though 'call' is passed an explicit serialization+--      dictionary, we still need the 'Serializable' constraint because --      'Static' is not the /true/ static. If it was, we could 'unstatic' --      the dictionary and pattern match on it to bring the 'Typeable' --      instance into scope, but unless proper 'static' support is added to---      ghc we need both the type class argument and the explicit dictionary. -module Control.Distributed.Process.Closure +--      ghc we need both the type class argument and the explicit dictionary.+module Control.Distributed.Process.Closure   ( -- * Serialization dictionaries (and their static versions)     SerializableDict(..)   , staticDecode   , sdictUnit   , sdictProcessId   , sdictSendPort-    -- * The CP type and associated combinators +    -- * The CP type and associated combinators   , CP   , idCP   , splitCP   , returnCP-  , bindCP -  , seqCP -    -- * CP versions of Cloud Haskell primitives  +  , bindCP+  , seqCP+    -- * CP versions of Cloud Haskell primitives   , cpLink   , cpUnlink   , cpSend   , cpExpect   , cpNewChan-#ifdef TemplateHaskellSupport -    -- * Template Haskell support for creating static values and closures +#ifdef TemplateHaskellSupport+    -- * Template Haskell support for creating static values and closures   , remotable   , remotableDecl   , mkStatic   , mkClosure+  , mkStaticClosure   , functionSDict   , functionTDict #endif-  ) where +  ) where  import Control.Distributed.Process.Serializable (SerializableDict(..)) import Control.Distributed.Process.Internal.Closure.BuiltIn@@ -194,27 +195,28 @@   , sdictUnit   , sdictProcessId   , sdictSendPort-    -- The CP type and associated combinators +    -- The CP type and associated combinators   , CP   , idCP   , splitCP   , returnCP-  , bindCP -  , seqCP -    -- CP versions of Cloud Haskell primitives  +  , bindCP+  , seqCP+    -- CP versions of Cloud Haskell primitives   , cpLink   , cpUnlink   , cpSend   , cpExpect   , cpNewChan   )-#ifdef TemplateHaskellSupport -import Control.Distributed.Process.Internal.Closure.TH +#ifdef TemplateHaskellSupport+import Control.Distributed.Process.Internal.Closure.TH   ( remotable   , remotableDecl   , mkStatic   , functionSDict   , functionTDict   , mkClosure+  , mkStaticClosure   ) #endif
src/Control/Distributed/Process/Internal/CQueue.hs view
@@ -1,8 +1,9 @@ -- | Concurrent queue for single reader, single writer-{-# LANGUAGE MagicHash, UnboxedTuples #-}-module Control.Distributed.Process.Internal.CQueue +{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards #-}+module Control.Distributed.Process.Internal.CQueue   ( CQueue   , BlockSpec(..)+  , MatchOn(..)   , newCQueue   , enqueue   , dequeue@@ -10,18 +11,21 @@   ) where  import Prelude hiding (length, reverse)-import Control.Concurrent.STM +import Control.Concurrent.STM   ( atomically+  , STM   , TChan+  , tryReadTChan   , newTChan   , writeTChan   , readTChan-  , tryReadTChan+  , orElse+  , retry   ) import Control.Applicative ((<$>), (<*>))-import Control.Exception (mask, onException)+import Control.Exception (mask_, onException) import System.Timeout (timeout)-import Control.Distributed.Process.Internal.StrictMVar +import Control.Distributed.Process.Internal.StrictMVar   ( StrictMVar(StrictMVar)   , newMVar   , takeMVar@@ -29,11 +33,11 @@   ) import Control.Distributed.Process.Internal.StrictList   ( StrictList(..)-  , reverse-  , reverse'+  , append   )+import Data.Maybe (fromJust) import GHC.MVar (MVar(MVar))-import GHC.IO (IO(IO)) +import GHC.IO (IO(IO)) import GHC.Prim (mkWeak#) import GHC.Weak (Weak(Weak)) @@ -50,76 +54,185 @@ enqueue :: CQueue a -> a -> IO () enqueue (CQueue _arrived incoming) !a = atomically $ writeTChan incoming a  -data BlockSpec = +data BlockSpec =     NonBlocking   | Blocking   | Timeout Int +data MatchOn m a+ = MatchMsg  (m -> Maybe a)+ | MatchChan (STM a)++type MatchChunks m a = [Either [m -> Maybe a] [STM a]]++chunkMatches :: [MatchOn m a] -> MatchChunks m a+chunkMatches [] = []+chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest+   where (chk, rest) = spanMatchMsg ms+chunkMatches (MatchChan r : ms) = Right (r : chk) : chunkMatches rest+   where (chk, rest) = spanMatchChan ms++spanMatchMsg :: [MatchOn m a] -> ([m -> Maybe a], [MatchOn m a])+spanMatchMsg [] = ([],[])+spanMatchMsg (m : ms)+    | MatchMsg msg <- m = (msg:msgs, rest)+    | otherwise         = ([], m:ms)+    where !(msgs,rest) = spanMatchMsg ms++spanMatchChan :: [MatchOn m a] -> ([STM a], [MatchOn m a])+spanMatchChan [] = ([],[])+spanMatchChan (m : ms)+    | MatchChan stm <- m = (stm:stms, rest)+    | otherwise          = ([], m:ms)+    where !(stms,rest)  = spanMatchChan ms+ -- | Dequeue an element -- -- The timeout (if any) is applied only to waiting for incoming messages, not -- to checking messages that have already arrived-dequeue :: forall a b. -           CQueue a          -- ^ Queue-        -> BlockSpec         -- ^ Blocking behaviour -        -> [a -> Maybe b]    -- ^ List of matches-        -> IO (Maybe b)      -- ^ 'Nothing' only on timeout-dequeue (CQueue arrived incoming) blockSpec matches = go -  where    -    go :: IO (Maybe b)-    go = mask $ \restore -> do-      arr <- takeMVar arrived -      -- We first check the arrived messages. If we get interrupted during this-      -- search, we just put the MVar back (we haven't read from the Chan yet)-      (arr', mb) <- onException (restore (checkArrived Nil arr))-                                (putMVar arrived arr) -      case (mb, blockSpec) of-        (Just b, _) -> do -          putMVar arrived arr'-          return (Just b)-        (Nothing, NonBlocking) ->-          checkNonBlocking arr'-        (Nothing, Blocking) ->-          Just <$> checkBlocking arr' -        (Nothing, Timeout n) ->-          timeout n $ checkBlocking arr'+dequeue :: forall m a.+           CQueue m          -- ^ Queue+        -> BlockSpec         -- ^ Blocking behaviour+        -> [MatchOn m a]     -- ^ List of matches+        -> IO (Maybe a)      -- ^ 'Nothing' only on timeout+dequeue (CQueue arrived incoming) blockSpec matchons =+  case blockSpec of+    Timeout n -> timeout n $ fmap fromJust run+    _other    ->+       case chunks of+         [Right ports] -> -- channels only, this is easy:+           case blockSpec of+             NonBlocking -> atomically $ waitChans ports (return Nothing)+             _           -> atomically $ waitChans ports retry+                              -- no onException needed+         _other -> run+  where+    chunks = chunkMatches matchons -    -- We reverse the accumulator on return only if we find a match-    checkArrived :: StrictList a -> StrictList a -> IO (StrictList a, Maybe b)-    checkArrived acc Nil = return (acc, Nothing)-    checkArrived acc (Cons x xs) = -      case check x of-        Just y  -> return (reverse' acc xs, Just y)-        Nothing -> checkArrived (Cons x acc) xs+    run = mask_ $ do+           arr <- takeMVar arrived+           let grabNew xs = do+                 r <- atomically $ tryReadTChan incoming+                 case r of+                   Nothing -> return xs+                   Just x  -> grabNew (Snoc xs x)+           arr' <- grabNew arr+           goCheck chunks arr' -    -- If we call checkBlocking there may or may not be a timeout-    checkBlocking :: StrictList a -> IO b-    checkBlocking acc = do-      -- readTChan is a blocking call, and hence is interruptable. If it is -      -- interrupted, we put the value of the accumulator in 'arrived'  -      -- (as opposed to the original value), so that no messages get lost-      -- (hence the low-level structure using mask rather than modifyMVar)-      x <- onException (atomically $ readTChan incoming)-                       (putMVar arrived $ reverse acc)-      case check x of-        Nothing -> checkBlocking (Cons x acc)-        Just y  -> putMVar arrived (reverse acc) >> return y +    waitChans ports on_block =+        foldr orElse on_block (map (fmap Just) ports) -    -- checkNonBlocking is only called if there is no timeout-    checkNonBlocking :: StrictList a -> IO (Maybe b)-    checkNonBlocking acc = do-      -- tryReadTChan is *not* interruptible-      mx <- atomically $ tryReadTChan incoming-      case mx of-        Nothing -> putMVar arrived (reverse acc) >> return Nothing-        Just x  -> case check x of-          Nothing -> checkNonBlocking (Cons x acc)-          Just y  -> putMVar arrived (reverse acc) >> return (Just y)-        -    check :: a -> Maybe b-    check = checkMatches matches +    --+    -- First check the MatchChunks against the messages already in the+    -- mailbox.  For channel matches, we do a non-blocking check at+    -- this point.+    --+    goCheck :: MatchChunks m a+            -> StrictList m  -- messages to check, in this order+            -> IO (Maybe a) -    checkMatches :: [a -> Maybe b] -> a -> Maybe b+    goCheck [] old = goWait old++    goCheck (Right ports : rest) old = do+      r <- atomically $ waitChans ports (return Nothing) -- does not block+      case r of+        Just _  -> returnOld old r+        Nothing -> goCheck rest old++    goCheck (Left matches : rest) old = do+           -- checkArrived might in principle take arbitrary time, so+           -- we ought to call restore and use an exception handler.  However,+           -- the check is usually fast (just a comparison), and the overhead+           -- of passing around restore and setting up exception handlers is+           -- high.  So just don't use expensive matchIfs!+      case checkArrived matches old of+        (old', Just r)  -> returnOld old' (Just r)+        (old', Nothing) -> goCheck rest old'+          -- use the result list, which is now left-biased++    --+    -- Construct an STM transaction that looks at the relevant channels+    -- in the correct order.+    --+    mkSTM :: MatchChunks m a -> STM (Either m a)+    mkSTM [] = retry+    mkSTM (Left _ : rest)+      = fmap Left (readTChan incoming) `orElse` mkSTM rest+    mkSTM (Right ports : rest)+      = foldr orElse (mkSTM rest) (map (fmap Right) ports)++    waitIncoming :: IO (Maybe (Either m a))+    waitIncoming = case blockSpec of+      NonBlocking -> atomically $ fmap Just stm `orElse` return Nothing+      _           -> atomically $ fmap Just stm+     where+      stm = mkSTM chunks++    --+    -- The initial pass didn't find a message, so now we go into blocking+    -- mode.+    --+    -- Contents of 'arrived' from now on is (old ++ new), and+    -- messages that arrive are snocced onto new.+    --+    goWait old = do+      r <- waitIncoming `onException` putMVar arrived old+      case r of+        --  Nothing => non-blocking and no message+        Nothing -> returnOld old Nothing+        Just e  -> case e of+          --+          -- Left => message arrived in the process mailbox.  We now have to+          -- run through the MatchChunks checking each one, because we might+          -- have a situation where the first chunk fails to match and the+          -- second chunk is a channel match and there *is* a message in the+          -- channel.  In that case the channel wins.+          --+          Left m -> goCheck1 chunks m old+          --+          -- Right => message arrived on a channel first+          --+          Right a -> returnOld old (Just a)++    --+    -- A message arrived in the process inbox; check the MatchChunks for+    -- a valid match.+    --+    goCheck1 :: MatchChunks m a+             -> m               -- single message to check+             -> StrictList m    -- old messages we have already checked+             -> IO (Maybe a)++    goCheck1 [] m old = goWait (Snoc old m)++    goCheck1 (Right ports : rest) m old = do+      r <- atomically $ waitChans ports (return Nothing) -- does not block+      case r of+        Nothing -> goCheck1 rest m old+        Just _  -> returnOld (Snoc old m) r++    goCheck1 (Left matches : rest) m old = do+      case checkMatches matches m of+        Nothing -> goCheck1 rest m old+        Just p  -> returnOld old (Just p)++    -- a common pattern for putting back the arrived queue at the end+    returnOld :: StrictList m -> Maybe a -> IO (Maybe a)+    returnOld old r = do putMVar arrived old; return r++    -- as a side-effect, this left-biases the list+    checkArrived :: [m -> Maybe a] -> StrictList m -> (StrictList m, Maybe a)+    checkArrived matches list = go list Nil+      where+        go Nil Nil           = (Nil, Nothing)+        go Nil r             = go r Nil+        go (Append xs ys) tl = go xs (append ys tl)+        go (Snoc xs x)    tl = go xs (Cons x tl)+        go (Cons x xs)    tl+          | Just y <- checkMatches matches x = (append xs tl, Just y)+          | otherwise = let !(rest,r) = go xs tl in (Cons x rest, r)++    checkMatches :: [m -> Maybe a] -> m -> Maybe a     checkMatches []     _ = Nothing     checkMatches (m:ms) a = case m a of Nothing -> checkMatches ms a                                         Just b  -> Just b
src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs view
@@ -1,5 +1,5 @@-module Control.Distributed.Process.Internal.Closure.BuiltIn -  ( -- * Remote table +module Control.Distributed.Process.Internal.Closure.BuiltIn+  ( -- * Remote table     remoteTable     -- * Static dictionaries and associated operations   , staticDecode@@ -8,14 +8,14 @@   , sdictSendPort     -- * Some static values   , sndStatic-    -- * The CP type and associated combinators +    -- * The CP type and associated combinators   , CP   , idCP   , splitCP   , returnCP-  , bindCP -  , seqCP -    -- * CP versions of Cloud Haskell primitives  +  , bindCP+  , seqCP+    -- * CP versions of Cloud Haskell primitives   , cpLink   , cpUnlink   , cpSend@@ -25,11 +25,11 @@   , cpDelay   ) where -import Data.ByteString.Lazy (ByteString)  +import Data.ByteString.Lazy (ByteString) import Data.Binary (decode, encode) import Data.Rank1Typeable (Typeable, ANY, ANY1, ANY2, ANY3, ANY4) import Data.Rank1Dynamic (toDynamic)-import Control.Distributed.Static +import Control.Distributed.Static   ( RemoteTable   , registerStatic   , Static@@ -42,18 +42,18 @@   , staticCompose   , staticClosure   )-import Control.Distributed.Process.Serializable +import Control.Distributed.Process.Serializable   ( SerializableDict(..)   , Serializable   )-import Control.Distributed.Process.Internal.Types +import Control.Distributed.Process.Internal.Types   ( Process   , ProcessId   , SendPort   , ReceivePort   , ProcessMonitorNotification(ProcessMonitorNotification)   )-import Control.Distributed.Process.Internal.Primitives +import Control.Distributed.Process.Internal.Primitives   ( link   , unlink   , send@@ -71,10 +71,10 @@ --------------------------------------------------------------------------------  remoteTable :: RemoteTable -> RemoteTable-remoteTable = +remoteTable =       registerStatic "$decodeDict"      (toDynamic (decodeDict       :: SerializableDict ANY -> ByteString -> ANY))     . registerStatic "$sdictUnit"       (toDynamic (SerializableDict :: SerializableDict ()))-    . registerStatic "$sdictProcessId"  (toDynamic (SerializableDict :: SerializableDict ProcessId)) +    . registerStatic "$sdictProcessId"  (toDynamic (SerializableDict :: SerializableDict ProcessId))     . registerStatic "$sdictSendPort_"  (toDynamic (sdictSendPort_   :: SerializableDict ANY -> SerializableDict (SendPort ANY)))     . registerStatic "$returnProcess"   (toDynamic (return           :: ANY -> Process ANY))     . registerStatic "$seqProcess"      (toDynamic ((>>)             :: Process ANY1 -> Process ANY2 -> Process ANY2))@@ -119,23 +119,23 @@ -- See module documentation of "Control.Distributed.Process.Closure" for an -- example. staticDecode :: Typeable a => Static (SerializableDict a) -> Static (ByteString -> a)-staticDecode dict = decodeDictStatic `staticApply` dict +staticDecode dict = decodeDictStatic `staticApply` dict   where     decodeDictStatic :: Typeable a => Static (SerializableDict a -> ByteString -> a)     decodeDictStatic = staticLabel "$decodeDict" --- | Serialization dictionary for '()' +-- | Serialization dictionary for '()' sdictUnit :: Static (SerializableDict ())-sdictUnit = staticLabel "$sdictUnit" +sdictUnit = staticLabel "$sdictUnit" --- | Serialization dictionary for 'ProcessId' +-- | Serialization dictionary for 'ProcessId' sdictProcessId :: Static (SerializableDict ProcessId)-sdictProcessId = staticLabel "$sdictProcessId" +sdictProcessId = staticLabel "$sdictProcessId"  -- | Serialization dictionary for 'SendPort'-sdictSendPort :: Typeable a +sdictSendPort :: Typeable a               => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))-sdictSendPort = staticApply (staticLabel "$sdictSendPort_") +sdictSendPort = staticApply (staticLabel "$sdictSendPort_")  -------------------------------------------------------------------------------- -- Static values                                                              --@@ -154,20 +154,20 @@ returnProcessStatic :: Typeable a => Static (a -> Process a) returnProcessStatic = staticLabel "$returnProcess" --- | 'CP' version of 'Control.Category.id' +-- | 'CP' version of 'Control.Category.id' idCP :: Typeable a => CP a a idCP = staticClosure returnProcessStatic  -- | 'CP' version of ('Control.Arrow.***')-splitCP :: (Typeable a, Typeable b, Typeable c, Typeable d) +splitCP :: (Typeable a, Typeable b, Typeable c, Typeable d)         => CP a c -> CP b d -> CP (a, b) (c, d) splitCP p q = cpSplitStatic `closureApplyStatic` p `closureApply` q   where     cpSplitStatic :: Static ((a -> Process c) -> (b -> Process d) -> (a, b) -> Process (c, d))-    cpSplitStatic = staticLabel "$cpSplit" +    cpSplitStatic = staticLabel "$cpSplit"  -- | 'CP' version of 'Control.Monad.return'-returnCP :: forall a. Serializable a +returnCP :: forall a. Serializable a          => Static (SerializableDict a) -> a -> Closure (Process a) returnCP dict x = closure decoder (encode x)   where@@ -179,18 +179,18 @@ -- | 'CP' version of ('Control.Monad.>>') seqCP :: (Typeable a, Typeable b)       => Closure (Process a) -> Closure (Process b) -> Closure (Process b)-seqCP p q = seqProcessStatic `closureApplyStatic` p `closureApply` q +seqCP p q = seqProcessStatic `closureApplyStatic` p `closureApply` q   where     seqProcessStatic :: (Typeable a, Typeable b)                      => Static (Process a -> Process b -> Process b)     seqProcessStatic = staticLabel "$seqProcess" --- | (Not quite the) 'CP' version of ('Control.Monad.>>=') +-- | (Not quite the) 'CP' version of ('Control.Monad.>>=') bindCP :: forall a b. (Typeable a, Typeable b)        => Closure (Process a) -> CP a b -> Closure (Process b)-bindCP x f = bindProcessStatic `closureApplyStatic` x `closureApply` f +bindCP x f = bindProcessStatic `closureApplyStatic` x `closureApply` f   where-    bindProcessStatic :: (Typeable a, Typeable b) +    bindProcessStatic :: (Typeable a, Typeable b)                       => Static (Process a -> (a -> Process b) -> Process b)     bindProcessStatic = staticLabel "$bindProcess" @@ -203,7 +203,7 @@  -- | 'CP' version of 'link' cpLink :: ProcessId -> Closure (Process ())-cpLink = closure (linkStatic `staticCompose` decodeProcessIdStatic) . encode +cpLink = closure (linkStatic `staticCompose` decodeProcessIdStatic) . encode   where     linkStatic :: Static (ProcessId -> Process ())     linkStatic = staticLabel "$link"@@ -216,18 +216,18 @@     unlinkStatic = staticLabel "$unlink"  -- | 'CP' version of 'send'-cpSend :: forall a. Typeable a -       => Static (SerializableDict a) -> ProcessId -> CP a () +cpSend :: forall a. Typeable a+       => Static (SerializableDict a) -> ProcessId -> CP a () cpSend dict pid = closure decoder (encode pid)   where     decoder :: Static (ByteString -> a -> Process ())     decoder = (sendDictStatic `staticApply` dict)-            `staticCompose` -              decodeProcessIdStatic +            `staticCompose`+              decodeProcessIdStatic -    sendDictStatic :: Typeable a +    sendDictStatic :: Typeable a                    => Static (SerializableDict a -> ProcessId -> a -> Process ())-    sendDictStatic = staticLabel "$sendDict" +    sendDictStatic = staticLabel "$sendDict"  -- | 'CP' version of 'expect' cpExpect :: Typeable a => Static (SerializableDict a) -> Closure (Process a)@@ -237,14 +237,14 @@     expectDictStatic = staticLabel "$expectDict"  -- | 'CP' version of 'newChan'-cpNewChan :: Typeable a -          => Static (SerializableDict a) +cpNewChan :: Typeable a+          => Static (SerializableDict a)           -> Closure (Process (SendPort a, ReceivePort a)) cpNewChan dict = staticClosure (newChanDictStatic `staticApply` dict)   where-    newChanDictStatic :: Typeable a +    newChanDictStatic :: Typeable a                       => Static (SerializableDict a -> Process (SendPort a, ReceivePort a))-    newChanDictStatic = staticLabel "$newChanDict"                  +    newChanDictStatic = staticLabel "$newChanDict"  -------------------------------------------------------------------------------- -- Support for spawn                                                          --@@ -252,7 +252,7 @@  -- | @delay them p@ is a process that waits for a signal (a message of type @()@) -- from 'them' (origin is not verified) before proceeding as @p@. In order to--- avoid waiting forever, @delay them p@ monitors 'them'. If it receives a +-- avoid waiting forever, @delay them p@ monitors 'them'. If it receives a -- monitor message instead it simply terminates. delay :: ProcessId -> Process () -> Process () delay them p = do@@ -269,7 +269,7 @@   where     cpDelay' :: ProcessId -> Closure (Process () -> Process ())     cpDelay' pid = closure decoder (encode pid)-    +     decoder :: Static (ByteString -> Process () -> Process ())     decoder = delayStatic `staticCompose` decodeProcessIdStatic 
src/Control/Distributed/Process/Internal/Closure/TH.hs view
@@ -1,6 +1,6 @@ -- | Template Haskell support {-# LANGUAGE TemplateHaskell #-}-module Control.Distributed.Process.Internal.Closure.TH +module Control.Distributed.Process.Internal.Closure.TH   ( -- * User-level API     remotable   , remotableDecl@@ -8,11 +8,12 @@   , functionSDict   , functionTDict   , mkClosure+  , mkStaticClosure   ) where  import Prelude hiding (succ, any) import Control.Applicative ((<$>))-import Language.Haskell.TH +import Language.Haskell.TH   ( -- Q monad and operations     Q   , reify@@ -42,7 +43,7 @@   , funD   , sigD   )-import Data.Maybe (catMaybes)  +import Data.Maybe (catMaybes) import Data.Binary (encode) import Data.Generics (everywhereM, mkM, gmapM) import Data.Rank1Dynamic (toDynamic)@@ -51,16 +52,17 @@   , Succ   , TypVar   )-import Control.Distributed.Static +import Control.Distributed.Static   ( RemoteTable   , registerStatic   , Static   , staticLabel   , closure   , staticCompose+  , staticClosure   ) import Control.Distributed.Process.Internal.Types (Process)-import Control.Distributed.Process.Serializable +import Control.Distributed.Process.Serializable   ( SerializableDict(SerializableDict)   ) import Control.Distributed.Process.Internal.Closure.BuiltIn (staticDecode)@@ -71,12 +73,12 @@  -- | Create the closure, decoder, and metadata definitions for the given list -- of functions-remotable :: [Name] -> Q [Dec] +remotable :: [Name] -> Q [Dec] remotable ns = do-    types <- mapM getType ns -    (closures, inserts) <- unzip <$> mapM generateDefs types +    types <- mapM getType ns+    (closures, inserts) <- unzip <$> mapM generateDefs types     rtable <- createMetaData (mkName "__remoteTable") (concat inserts)-    return $ concat closures ++ rtable +    return $ concat closures ++ rtable  -- | Like 'remotable', but parameterized by the declaration of a function -- instead of the function name. So where for 'remotable' you'd do@@ -106,9 +108,9 @@ remotableDecl qDecs = do     decs <- concat <$> sequence qDecs     let types = catMaybes (map typeOf decs)-    (closures, inserts) <- unzip <$> mapM generateDefs types +    (closures, inserts) <- unzip <$> mapM generateDefs types     rtable <- createMetaData (mkName "__remoteTableDecl") (concat inserts)-    return $ decs ++ concat closures ++ rtable +    return $ decs ++ concat closures ++ rtable   where     typeOf :: Dec -> Maybe (Name, Type)     typeOf (SigD name typ) = Just (name, typ)@@ -116,15 +118,15 @@  -- | Construct a static value. ----- If @f : forall a1 .. an. T@ --- then @$(mkStatic 'f) :: forall a1 .. an. Static T@. --- Be sure to pass 'f' to 'remotable'. +-- If @f : forall a1 .. an. T@+-- then @$(mkStatic 'f) :: forall a1 .. an. Static T@.+-- Be sure to pass 'f' to 'remotable'. mkStatic :: Name -> Q Exp mkStatic = varE . staticName --- | If @f : T1 -> T2@ is a monomorphic function +-- | If @f : T1 -> T2@ is a monomorphic function -- then @$(functionSDict 'f) :: Static (SerializableDict T1)@.--- +-- -- Be sure to pass 'f' to 'remotable'. functionSDict :: Name -> Q Exp functionSDict = varE . sdictName@@ -136,24 +138,31 @@ functionTDict :: Name -> Q Exp functionTDict = varE . tdictName --- | If @f : T1 -> T2@ then @$(mkClosure 'f) :: T1 -> Closure T2@. +-- | If @f : T1 -> T2@ then @$(mkClosure 'f) :: T1 -> Closure T2@. ----- TODO: The current version of mkClosure is too polymorphic +-- TODO: The current version of mkClosure is too polymorphic -- (@forall a. Binary a => a -> Closure T2). mkClosure :: Name -> Q Exp-mkClosure n = -  [|   closure ($(mkStatic n) `staticCompose` staticDecode $(functionSDict n)) +mkClosure n =+  [|   closure ($(mkStatic n) `staticCompose` staticDecode $(functionSDict n))      . encode   |] +-- | Make a 'Closure' from a static function.  This is useful for+-- making a closure for a top-level @Process ()@ function, because+-- using 'mkClosure' would require adding a dummy @()@ argument.+--+mkStaticClosure :: Name -> Q Exp+mkStaticClosure n = [| staticClosure $( mkStatic n ) |]+ -------------------------------------------------------------------------------- -- Internal (Template Haskell)                                                -- --------------------------------------------------------------------------------  -- | Generate the code to add the metadata to the CH runtime createMetaData :: Name -> [Q Exp] -> Q [Dec]-createMetaData name is = -  sequence [ sigD name [t| RemoteTable -> RemoteTable |] +createMetaData name is =+  sequence [ sigD name [t| RemoteTable -> RemoteTable |]            , sfnD name (compose is)            ] @@ -163,52 +172,52 @@     let (typVars, typ') = case fullType of ForallT vars [] mono -> (vars, mono)                                            _                    -> ([], fullType) -    -- The main "static" entry                                  -    (static, register) <- makeStatic typVars typ' -     -    -- If n :: T1 -> T2, static serializable dictionary for T1 +    -- The main "static" entry+    (static, register) <- makeStatic typVars typ'++    -- If n :: T1 -> T2, static serializable dictionary for T1     -- TODO: we should check if arg is an instance of Serializable, but we cannot     -- http://hackage.haskell.org/trac/ghc/ticket/7066     (sdict, registerSDict) <- case (typVars, typ') of-      ([], ArrowT `AppT` arg `AppT` _res) -> +      ([], ArrowT `AppT` arg `AppT` _res) ->         makeDict (sdictName origName) arg-      _ -> +      _ ->         return ([], [])-    +     -- If n :: T1 -> Process T2, static serializable dictionary for T2     -- TODO: check if T2 is serializable (same as above)     (tdict, registerTDict) <- case (typVars, typ') of-      ([], ArrowT `AppT` _arg `AppT` (proc' `AppT` res)) | proc' == proc -> -        makeDict (tdictName origName) res +      ([], ArrowT `AppT` _arg `AppT` (proc' `AppT` res)) | proc' == proc ->+        makeDict (tdictName origName) res       _ ->         return ([], [])-    +     return ( concat [static, sdict, tdict]            , concat [register, registerSDict, registerTDict]            )   where     makeStatic :: [TyVarBndr] -> Type -> Q ([Dec], [Q Exp])-    makeStatic typVars typ = do +    makeStatic typVars typ = do       static <- generateStatic origName typVars typ-      let dyn = case typVars of +      let dyn = case typVars of                   [] -> [| toDynamic $(varE origName) |]                   _  -> [| toDynamic ($(varE origName) :: $(monomorphize typVars typ)) |]       return ( static              , [ [| registerStatic $(showFQN origName) $dyn |] ]              ) -    makeDict :: Name -> Type -> Q ([Dec], [Q Exp]) +    makeDict :: Name -> Type -> Q ([Dec], [Q Exp])     makeDict dictName typ = do-      sdict <- generateDict dictName typ +      sdict <- generateDict dictName typ       let dyn = [| toDynamic (SerializableDict :: SerializableDict $(return typ)) |]       return ( sdict-             , [ [| registerStatic $(showFQN dictName) $dyn |] ] +             , [ [| registerStatic $(showFQN dictName) $dyn |] ]              )  -- | Turn a polymorphic type into a monomorphic type using ANY and co monomorphize :: [TyVarBndr] -> Type -> Q Type-monomorphize tvs = -    let subst = zip (map tyVarBndrName tvs) anys +monomorphize tvs =+    let subst = zip (map tyVarBndrName tvs) anys     in everywhereM (mkM (applySubst subst))   where     anys :: [Q Type]@@ -219,34 +228,34 @@      zero :: Q Type     zero = [t| Zero |]-    +     succ :: Q Type -> Q Type     succ t = [t| Succ $t |]- +     applySubst :: [(Name, Q Type)] -> Type -> Q Type-    applySubst s (VarT n) = -      case lookup n s of  +    applySubst s (VarT n) =+      case lookup n s of         Nothing -> return (VarT n)         Just t  -> t     applySubst s t = gmapM (mkM (applySubst s)) t --- | Generate a static value +-- | Generate a static value generateStatic :: Name -> [TyVarBndr] -> Type -> Q [Dec] generateStatic n xs typ = do     staticTyp <- [t| Static |]     sequence-      [ sigD (staticName n) $ -          return (ForallT xs -                  (map typeable xs) +      [ sigD (staticName n) $+          return (ForallT xs+                  (map typeable xs)                   (staticTyp `AppT` typ)           )       , sfnD (staticName n) [| staticLabel $(showFQN n) |]       ]   where     typeable :: TyVarBndr -> Pred-    typeable tv = ClassP (mkName "Typeable") [VarT (tyVarBndrName tv)] +    typeable tv = ClassP (mkName "Typeable") [VarT (tyVarBndrName tv)] --- | Generate a serialization dictionary with name 'n' for type 'typ' +-- | Generate a serialization dictionary with name 'n' for type 'typ' generateDict :: Name -> Type -> Q [Dec] generateDict n typ = do     sequence@@ -270,7 +279,7 @@ -- | Compose a set of expressions compose :: [Q Exp] -> Q Exp compose []     = [| id |]-compose [e]    = e +compose [e]    = e compose (e:es) = [| $e . $(compose es) |]  -- | Literal string as an expression@@ -279,17 +288,17 @@  -- | Look up the "original name" (module:name) and type of a top-level function getType :: Name -> Q (Name, Type)-getType name = do +getType name = do   info <- reify name-  case info of +  case info of     VarI origName typ _ _ -> return (origName, typ)     _                     -> fail $ show name ++ " not found"  -- | Variation on 'funD' which takes a single expression to define the function sfnD :: Name -> Q Exp -> Q Dec-sfnD n e = funD n [clause [] (normalB e) []] -    --- | The name of a type variable binding occurrence    +sfnD n e = funD n [clause [] (normalB e) []]++-- | The name of a type variable binding occurrence tyVarBndrName :: TyVarBndr -> Name tyVarBndrName (PlainTV n)    = n tyVarBndrName (KindedTV n _) = n@@ -299,7 +308,7 @@ -- We ignore the module part of the Name argument (which may or may not exist) -- because we construct various names (`staticName`, `sdictName`, `tdictName`) -- and those names certainly won't have Module components.-showFQN :: Name -> Q Exp +showFQN :: Name -> Q Exp showFQN n = do   loc <- location   stringE (loc_module loc ++ "." ++ nameBase n)
src/Control/Distributed/Process/Internal/Messaging.hs view
@@ -2,8 +2,8 @@   ( sendPayload   , sendBinary   , sendMessage-  , disconnect -  , closeImplicitReconnections +  , disconnect+  , closeImplicitReconnections   , impliesDeathOf   ) where @@ -15,7 +15,7 @@ import Control.Distributed.Process.Internal.StrictMVar (withMVar, modifyMVar_) import Control.Concurrent.Chan (writeChan) import Control.Monad (unless)-import qualified Network.Transport as NT +import qualified Network.Transport as NT   ( Connection   , send   , defaultConnectHints@@ -23,7 +23,7 @@   , Reliability(ReliableOrdered)   , close   )-import Control.Distributed.Process.Internal.Types +import Control.Distributed.Process.Internal.Types   ( LocalNode(localState, localEndPoint, localCtrlChan)   , Identifier   , localConnections@@ -44,14 +44,14 @@ import Control.Distributed.Process.Serializable (Serializable)  ----------------------------------------------------------------------------------- Message sending                                                            -- +-- Message sending                                                            -- -------------------------------------------------------------------------------- -sendPayload :: LocalNode +sendPayload :: LocalNode             -> Identifier             -> Identifier             -> ImplicitReconnect-            -> [BSS.ByteString] +            -> [BSS.ByteString]             -> IO () sendPayload node from to implicitReconnect payload = do   mConn <- connBetween node from to implicitReconnect@@ -59,53 +59,53 @@     Just conn -> do       didSend <- NT.send conn payload       case didSend of-        Left _err -> return False -        Right ()  -> return True +        Left _err -> return False+        Right ()  -> return True     Nothing -> return False   unless didSend $ do     writeChan (localCtrlChan node) NCMsg-      { ctrlMsgSender = to +      { ctrlMsgSender = to       , ctrlMsgSignal = Died to DiedDisconnect       } -sendBinary :: Binary a -           => LocalNode -           -> Identifier -           -> Identifier +sendBinary :: Binary a+           => LocalNode+           -> Identifier+           -> Identifier            -> ImplicitReconnect-           -> a +           -> a            -> IO ()-sendBinary node from to implicitReconnect +sendBinary node from to implicitReconnect   = sendPayload node from to implicitReconnect . BSL.toChunks . encode -sendMessage :: Serializable a -            => LocalNode -            -> Identifier -            -> Identifier +sendMessage :: Serializable a+            => LocalNode+            -> Identifier+            -> Identifier             -> ImplicitReconnect-            -> a +            -> a             -> IO ()-sendMessage node from to implicitReconnect = +sendMessage node from to implicitReconnect =   sendPayload node from to implicitReconnect . messageToPayload . createMessage -setupConnBetween :: LocalNode -                 -> Identifier -                 -> Identifier -                 -> ImplicitReconnect +setupConnBetween :: LocalNode+                 -> Identifier+                 -> Identifier+                 -> ImplicitReconnect                  -> IO (Maybe NT.Connection) setupConnBetween node from to implicitReconnect = do-    mConn <- NT.connect endPoint -                        (nodeAddress . nodeOf $ to) -                        NT.ReliableOrdered +    mConn <- NT.connect endPoint+                        (nodeAddress . nodeOf $ to)+                        NT.ReliableOrdered                         NT.defaultConnectHints-    case mConn of +    case mConn of       Right conn -> do         didSend <- NT.send conn (BSL.toChunks . encode $ to)         case didSend of           Left _ ->             return Nothing           Right () -> do-            modifyMVar_ nodeState $ return . +            modifyMVar_ nodeState $ return .               (localConnectionBetween from to ^= Just (conn, implicitReconnect))             return $ Just conn       Left _ ->@@ -114,33 +114,33 @@     endPoint  = localEndPoint node     nodeState = localState node -connBetween :: LocalNode -            -> Identifier -            -> Identifier +connBetween :: LocalNode+            -> Identifier+            -> Identifier             -> ImplicitReconnect             -> IO (Maybe NT.Connection) connBetween node from to implicitReconnect = do     mConn <- withMVar nodeState $ return . (^. localConnectionBetween from to)     case mConn of-      Just (conn, _) -> +      Just (conn, _) ->         return $ Just conn-      Nothing -> -        setupConnBetween node from to implicitReconnect +      Nothing ->+        setupConnBetween node from to implicitReconnect   where     nodeState = localState node  disconnect :: LocalNode -> Identifier -> Identifier -> IO () disconnect node from to =-  modifyMVar_ (localState node) $ \st -> +  modifyMVar_ (localState node) $ \st ->     case st ^. localConnectionBetween from to of-      Nothing -> +      Nothing ->         return st       Just (conn, _) -> do-        NT.close conn +        NT.close conn         return (localConnectionBetween from to ^= Nothing $ st)  closeImplicitReconnections :: LocalNode -> Identifier -> IO ()-closeImplicitReconnections node to = +closeImplicitReconnections node to =   modifyMVar_ (localState node) $ \st -> do     let shouldClose (_, to') (_, WithImplicitReconnect) = to `impliesDeathOf` to'         shouldClose _ _ = False@@ -153,7 +153,7 @@ impliesDeathOf :: Identifier                -> Identifier                -> Bool-NodeIdentifier nid `impliesDeathOf` NodeIdentifier nid' = +NodeIdentifier nid `impliesDeathOf` NodeIdentifier nid' =   nid' == nid NodeIdentifier nid `impliesDeathOf` ProcessIdentifier pid =   processNodeId pid == nid
src/Control/Distributed/Process/Internal/Primitives.hs view
@@ -1,10 +1,10 @@ -- | Cloud Haskell primitives ----- We define these in a separate module so that we don't have to rely on +-- We define these in a separate module so that we don't have to rely on -- the closure combinators-module Control.Distributed.Process.Internal.Primitives +module Control.Distributed.Process.Internal.Primitives   ( -- * Basic messaging-    send +    send   , expect     -- * Channels   , newChan@@ -19,18 +19,31 @@   , match   , matchIf   , matchUnknown-  , AbstractMessage(..) +  , AbstractMessage(..)   , matchAny+  , matchAnyIf+  , matchChan     -- * Process management   , terminate   , ProcessTerminationException(..)+  , die+  , kill+  , exit+  , catchExit+  , catchesExit+    -- keep the exception constructor hidden, so that handling exit+    -- reasons /must/ take place via the 'catchExit' family of primitives+  , ProcessExitException()   , getSelfPid   , getSelfNode+  , ProcessInfo(..)+  , getProcessInfo     -- * Monitoring and linking   , link   , unlink   , monitor   , unmonitor+  , withMonitor     -- * Logging   , say     -- * Registry@@ -49,6 +62,9 @@   , unStatic     -- * Exception handling   , catch+  , Handler(..)+  , catches+  , try   , mask   , onException   , bracket@@ -67,6 +83,8 @@     -- * Reconnecting   , reconnect   , reconnectPort+    -- * Tracing/Debugging+  , trace   ) where  #if ! MIN_VERSION_base(4,6,0)@@ -82,15 +100,15 @@ import Control.Monad.Reader (ask) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Applicative ((<$>))-import Control.Exception (Exception, throwIO, SomeException)-import qualified Control.Exception as Ex (catch, mask)-import Control.Distributed.Process.Internal.StrictMVar +import Control.Exception (Exception(..), throw, throwIO, SomeException)+import qualified Control.Exception as Ex (catch, mask, try)+import Control.Distributed.Process.Internal.StrictMVar   ( StrictMVar   , modifyMVar   , modifyMVar_   ) import Control.Concurrent.Chan (writeChan)-import Control.Concurrent.STM +import Control.Concurrent.STM   ( STM   , TVar   , atomically@@ -99,13 +117,17 @@   , readTVar   , writeTVar   )-import Control.Distributed.Process.Internal.CQueue (dequeue, BlockSpec(..))+import Control.Distributed.Process.Internal.CQueue+  ( dequeue+  , BlockSpec(..)+  , MatchOn(..)+  ) import Control.Distributed.Process.Serializable (Serializable, fingerprint) import Data.Accessor ((^.), (^:), (^=)) import Control.Distributed.Static (Closure, Static) import Data.Rank1Typeable (Typeable) import qualified Control.Distributed.Static as Static (unstatic, unclosure)-import Control.Distributed.Process.Internal.Types +import Control.Distributed.Process.Internal.Types   ( NodeId(..)   , ProcessId(..)   , LocalNode(..)@@ -116,7 +138,7 @@   , SpawnRef(..)   , NCMsg(..)   , ProcessSignal(..)-  , monitorCounter +  , monitorCounter   , spawnCounter   , SendPort(..)   , ReceivePort(..)@@ -125,6 +147,7 @@   , TypedChannel(..)   , SendPortId(..)   , Identifier(..)+  , ProcessExitException(..)   , DidUnmonitor(..)   , DidUnlinkProcess(..)   , DidUnlinkNode(..)@@ -132,6 +155,8 @@   , WhereIsReply(..)   , RegisterReply(..)   , ProcessRegistrationException(..)+  , ProcessInfo(..)+  , ProcessInfoNone(..)   , createMessage   , runLocalProcess   , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)@@ -139,13 +164,14 @@   , LocalSendPortId   , messageToPayload   )-import Control.Distributed.Process.Internal.Messaging +import Control.Distributed.Process.Internal.Messaging   ( sendMessage   , sendBinary   , sendPayload   , disconnect-  ) -import Control.Distributed.Process.Internal.WeakTQueue +  )+import qualified Control.Distributed.Process.Internal.Trace as Trace+import Control.Distributed.Process.Internal.WeakTQueue   ( newTQueueIO   , readTQueue   , mkWeakTQueue@@ -160,16 +186,16 @@ -- This requires a lookup on every send. If we want to avoid that we need to -- modify serializable to allow for stateful (IO) deserialization send them msg = do-  proc <- ask -  liftIO $ sendMessage (processNode proc) -                       (ProcessIdentifier (processId proc)) +  proc <- ask+  liftIO $ sendMessage (processNode proc)+                       (ProcessIdentifier (processId proc))                        (ProcessIdentifier them)                        NoImplicitReconnect                        msg  -- | Wait for a message of a specific type expect :: forall a. Serializable a => Process a-expect = receiveWait [match return] +expect = receiveWait [match return]  -------------------------------------------------------------------------------- -- Channels                                                                   --@@ -178,17 +204,17 @@ -- | Create a new typed channel newChan :: Serializable a => Process (SendPort a, ReceivePort a) newChan = do-    proc <- ask +    proc <- ask     liftIO . modifyMVar (processState proc) $ \st -> do       let lcid  = st ^. channelCounter       let cid   = SendPortId { sendPortProcessId = processId proc                              , sendPortLocalId   = lcid                              }-      let sport = SendPort cid +      let sport = SendPort cid       chan  <- liftIO newTQueueIO       chan' <- mkWeakTQueue chan $ finalizer (processState proc) lcid       let rport = ReceivePort $ readTQueue chan-      let tch   = TypedChannel chan' +      let tch   = TypedChannel chan'       return ( (channelCounter ^: (+ 1))              . (typedChannelWithId lcid ^= Just tch)              $ st@@ -196,7 +222,7 @@              )   where     finalizer :: StrictMVar LocalProcessState -> LocalSendPortId -> IO ()-    finalizer st lcid = modifyMVar_ st $ +    finalizer st lcid = modifyMVar_ st $       return . (typedChannelWithId lcid ^= Nothing)  -- | Send a message on a typed channel@@ -205,24 +231,24 @@   proc <- ask   liftIO $ sendBinary (processNode proc)                       (ProcessIdentifier (processId proc))-                      (SendPortIdentifier cid) +                      (SendPortIdentifier cid)                       NoImplicitReconnect-                      msg +                      msg  -- | Wait for a message on a typed channel receiveChan :: Serializable a => ReceivePort a -> Process a-receiveChan = liftIO . atomically . receiveSTM +receiveChan = liftIO . atomically . receiveSTM --- | Like 'receiveChan' but with a timeout. If the timeout is 0, do a +-- | Like 'receiveChan' but with a timeout. If the timeout is 0, do a -- non-blocking check for a message. receiveChanTimeout :: Serializable a => Int -> ReceivePort a -> Process (Maybe a)-receiveChanTimeout 0 ch = liftIO . atomically $ +receiveChanTimeout 0 ch = liftIO . atomically $   (Just <$> receiveSTM ch) `orElse` return Nothing-receiveChanTimeout n ch = liftIO . timeout n . atomically $ +receiveChanTimeout n ch = liftIO . timeout n . atomically $   receiveSTM ch  -- | Merge a list of typed channels.--- +-- -- The result port is left-biased: if there are messages available on more -- than one port, the first available message is returned. mergePortsBiased :: Serializable a => [ReceivePort a] -> Process (ReceivePort a)@@ -233,7 +259,7 @@ mergePortsRR :: Serializable a => [ReceivePort a] -> Process (ReceivePort a) mergePortsRR = \ps -> do     psVar <- liftIO . atomically $ newTVar (map receiveSTM ps)-    return $ ReceivePort (rr psVar) +    return $ ReceivePort (rr psVar)   where     rotate :: [a] -> [a]     rotate []     = []@@ -242,16 +268,16 @@     rr :: TVar [STM a] -> STM a     rr psVar = do       ps <- readTVar psVar-      a  <- foldr1 orElse ps +      a  <- foldr1 orElse ps       writeTVar psVar (rotate ps)       return a  ----------------------------------------------------------------------------------- Advanced messaging                                                         -- +-- Advanced messaging                                                         -- --------------------------------------------------------------------------------  -- | Opaque type used in 'receiveWait' and 'receiveTimeout'-newtype Match b = Match { unMatch :: Message -> Maybe (Process b) }+newtype Match b = Match { unMatch :: MatchOn Message (Process b) }  -- | Test the matches in order against each message in the queue receiveWait :: [Match b] -> Process b@@ -261,7 +287,7 @@   proc  -- | Like 'receiveWait' but with a timeout.--- +-- -- If the timeout is zero do a non-blocking check for matching messages. A -- non-zero timeout is applied only when waiting for incoming messages (that is, -- /after/ we have checked the messages that are already in the mailbox).@@ -274,45 +300,92 @@     Nothing   -> return Nothing     Just proc -> Just <$> proc +matchChan :: ReceivePort a -> (a -> Process b) -> Match b+matchChan p fn = Match $ MatchChan (fmap fn (receiveSTM p))+ -- | Match against any message of the right type match :: forall a b. Serializable a => (a -> Process b) -> Match b-match = matchIf (const True) +match = matchIf (const True)  -- | Match against any message of the right type that satisfies a predicate matchIf :: forall a b. Serializable a => (a -> Bool) -> (a -> Process b) -> Match b-matchIf c p = Match $ \msg -> +matchIf c p = Match $ MatchMsg $ \msg ->    case messageFingerprint msg == fingerprint (undefined :: a) of      True | c decoded -> Just (p decoded)        where          decoded :: a-         -- Make sure the value is fully decoded so that we don't hang to +         -- Make sure the value is fully decoded so that we don't hang to          -- bytestrings when the process calling 'matchIf' doesn't process          -- the values immediately          !decoded = decode (messageEncoding msg)      _ -> Nothing +-- | Represents a received message and provides two basic operations on it. data AbstractMessage = AbstractMessage {-    forward :: ProcessId -> Process ()+    forward :: ProcessId -> Process () -- ^ forward the message to @ProcessId@+  , maybeHandleMessage :: forall a b. (Serializable a)+            => (a -> Process b) -> Process (Maybe b) {- ^ Handle the message.+        If the type of the message matches the type of the first argument to+        the supplied expression, then the expression will be evaluated against+        it. If this runtime type checking fails, then @Nothing@ will be returned+        to indicate the fact. If the check succeeds and evaluation proceeds+        however, the resulting value with be wrapped with @Just@.+    -}   } --- | Match against an arbitrary message+-- | Match against an arbitrary message. 'matchAny' removes the first available+-- message from the process mailbox, and via the 'AbstractMessage' type,+-- supports forwarding /or/ handling the message /if/ it is of the correct+-- type. If /not/ of the right type, then the 'AbstractMessage'+-- @maybeHandleMessage@ function will not evaluate the supplied expression,+-- /but/ the message will still have been removed from the process mailbox!+-- matchAny :: forall b. (AbstractMessage -> Process b) -> Match b-matchAny p = Match $ Just . p . abstract-  where -    abstract :: Message -> AbstractMessage-    abstract msg = AbstractMessage {-        forward = \them -> do-          proc <- ask-          liftIO $ sendPayload (processNode proc)-                               (ProcessIdentifier (processId proc))-                               (ProcessIdentifier them)-                               NoImplicitReconnect -                               (messageToPayload msg)-      }+matchAny p = Match $ MatchMsg $ Just . p . abstract +-- | Match against an arbitrary message. 'matchAnyIf' will /only/ remove the+-- message from the process mailbox, /if/ the supplied condition matches. The+-- success (or failure) of runtime type checks in @maybeHandleMessage@ does not+-- count here, i.e., if the condition evaluates to @True@ then the message will+-- be removed from the process mailbox and decoded, but that does /not/+-- guarantee that an expression passed to @maybeHandleMessage@ will pass the+-- runtime type checks and therefore be evaluated. If the types do not match+-- up, then @maybeHandleMessage@ returns 'Nothing'.+matchAnyIf :: forall a b. (Serializable a)+                       => (a -> Bool)+                       -> (AbstractMessage -> Process b)+                       -> Match b+matchAnyIf c p = Match $ MatchMsg $ \msg ->+   case messageFingerprint msg == fingerprint (undefined :: a) of+     True | c decoded -> Just (p (abstract msg))+       where+         decoded :: a+         -- Make sure the value is fully decoded so that we don't hang to+         -- bytestrings when the calling process doesn't evaluate immediately+         !decoded = decode (messageEncoding msg)+     _ -> Nothing++abstract :: Message -> AbstractMessage+abstract msg = AbstractMessage {+    forward = \them -> do+      proc <- ask+      liftIO $ sendPayload (processNode proc)+                           (ProcessIdentifier (processId proc))+                           (ProcessIdentifier them)+                           NoImplicitReconnect+                           (messageToPayload msg)+  , maybeHandleMessage = \(proc :: (a -> Process b)) -> do+      case messageFingerprint msg == fingerprint (undefined :: a) of+        True -> do { r <- proc (decoded :: a); return (Just r) }+          where+            decoded :: a+            !decoded = decode (messageEncoding msg)+        _ -> return Nothing+  }+ -- | Remove any message from the queue matchUnknown :: Process b -> Match b-matchUnknown = Match . const . Just+matchUnknown p = Match $ MatchMsg (const (Just p))  -------------------------------------------------------------------------------- -- Process management                                                         --@@ -324,18 +397,108 @@  instance Exception ProcessTerminationException --- | Terminate (throws a ProcessTerminationException)+-- | Terminate immediately (throws a ProcessTerminationException) terminate :: Process a terminate = liftIO $ throwIO ProcessTerminationException +-- [Issue #110]+-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.+die :: Serializable a => a -> Process b+die reason = do+  pid <- getSelfPid+  liftIO $ throwIO (ProcessExitException pid (createMessage reason))++-- | Forceful request to kill a process. Where 'exit' provides an exception+-- that can be caught and handled, 'kill' throws an unexposed exception type+-- which cannot be handled explicitly (by type).+kill :: ProcessId -> String -> Process ()+-- NOTE: We send the message to our local node controller, which will then+-- forward it to a remote node controller (if applicable). Sending it directly+-- to a remote node controller means that that the message may overtake a+-- 'monitor' or 'link' request.+kill them reason = sendCtrlMsg Nothing (Kill them reason)++-- | Graceful request to exit a process. Throws 'ProcessExitException' with the+-- supplied @reason@ encoded as a message. Any /exit signal/ raised in this+-- manner can be handled using the 'catchExit' family of functions.+exit :: Serializable a => ProcessId -> a -> Process ()+-- NOTE: We send the message to our local node controller, which will then+-- forward it to a remote node controller (if applicable). Sending it directly+-- to a remote node controller means that that the message may overtake a+-- 'monitor' or 'link' request.+exit them reason = sendCtrlMsg Nothing (Exit them (createMessage reason))++-- | Catches 'ProcessExitException'. The handler will not be applied unless its+-- type matches the encoded data stored in the exception (see the /reason/+-- argument given to the 'exit' primitive). If the handler cannot be applied,+-- the exception will be re-thrown.+--+-- To handle 'ProcessExitException' without regard for /reason/, see 'catch'.+-- To handle multiple /reasons/ of differing types, see 'catchesExit'.+catchExit :: forall a b . (Show a, Serializable a)+                       => Process b+                       -> (ProcessId -> a -> Process b)+                       -> Process b+catchExit act exitHandler = catch act handleExit+  where+    handleExit ex@(ProcessExitException from msg) =+        if messageFingerprint msg == fingerprint (undefined :: a)+          then exitHandler from decoded+          else liftIO $ throwIO ex+     where+       decoded :: a+       -- Make sure the value is fully decoded so that we don't hang to+       -- bytestrings if the caller doesn't use the value immediately+       !decoded = decode (messageEncoding msg)++-- | Lift 'Control.Exception.catches' (almost).+--+-- As 'ProcessExitException' stores the exit @reason@ as a typed, encoded+-- message, a handler must accept an input of the expected type. In order to+-- handle a list of potentially different handlers (and therefore input types),+-- a handler passed to 'catchesExit' must accept 'AbstractMessage' and return+-- @Maybe@ (i.e., @Just p@ if it handled the exit reason, otherwise @Nothing@).+--+-- See 'maybeHandleMessage' and 'AsbtractMessage' for more details.+catchesExit :: Process b+            -> [(ProcessId -> AbstractMessage -> (Process (Maybe b)))]+            -> Process b+catchesExit act handlers = catch act ((flip handleExit) handlers)+  where+    handleExit :: ProcessExitException+               -> [(ProcessId -> AbstractMessage -> Process (Maybe b))]+               -> Process b+    handleExit ex [] = liftIO $ throwIO ex+    handleExit ex@(ProcessExitException from msg) (h:hs) = do+      r <- h from (abstract msg)+      case r of+        Nothing -> handleExit ex hs+        Just p  -> return p+ -- | Our own process ID getSelfPid :: Process ProcessId-getSelfPid = processId <$> ask +getSelfPid = processId <$> ask  -- | Get the node ID of our local node getSelfNode :: Process NodeId-getSelfNode = localNodeId . processNode <$> ask +getSelfNode = localNodeId . processNode <$> ask +-- | Get information about the specified process+getProcessInfo :: ProcessId -> Process (Maybe ProcessInfo)+getProcessInfo pid =+  let them = processNodeId pid in do+  us <- getSelfNode+  dest <- mkNode them us+  sendCtrlMsg dest $ GetInfo pid+  receiveWait [+       match (\(p :: ProcessInfo)     -> return $ Just p)+     , match (\(_ :: ProcessInfoNone) -> return Nothing)+     ]+  where mkNode :: NodeId -> NodeId -> Process (Maybe NodeId)+        mkNode them us = case them == us of+                           True -> return Nothing+                           _    -> return $ Just them+ -------------------------------------------------------------------------------- -- Monitoring and linking                                                     -- --------------------------------------------------------------------------------@@ -354,7 +517,7 @@ -- > unlink pidB -- Unlink again -- -- doesn't quite do what one might expect: if process B sends a message to--- process A, and /subsequently terminates/, then process A might or might not +-- process A, and /subsequently terminates/, then process A might or might not -- be terminated too, depending on whether the exception is thrown before or -- after the 'unlink' (i.e., this code has a race condition). --@@ -369,7 +532,7 @@  -- | Monitor another process (asynchronous) ----- When process A monitors process B (that is, process A calls +-- When process A monitors process B (that is, process A calls -- @monitor pidB@) then process A will receive a 'ProcessMonitorNotification' -- when process B terminates (normally or abnormally), or when process A gets -- disconnected from process B. You receive this message like any other (using@@ -377,27 +540,41 @@ -- 'DiedDisconnect', etc.). -- -- Every call to 'monitor' returns a new monitor reference 'MonitorRef'; if--- multiple monitors are set up, multiple notifications will be delivered +-- multiple monitors are set up, multiple notifications will be delivered -- and monitors can be disabled individually using 'unmonitor'.-monitor :: ProcessId -> Process MonitorRef -monitor = monitor' . ProcessIdentifier +monitor :: ProcessId -> Process MonitorRef+monitor = monitor' . ProcessIdentifier --- | Remove a link +-- | Establishes temporary monitoring of another process. --+-- @withMonitor pid code@ sets up monitoring of @pid@ for the duration+-- of @code@.  Note: although monitoring is no longer active when+-- @withMonitor@ returns, there might still be unreceived monitor+-- messages in the queue.+--+withMonitor :: ProcessId -> Process a -> Process a+withMonitor pid code = bracket (monitor pid) unmonitor (\_ -> code)+  -- unmonitor blocks waiting for the response, so there's a possibility+  -- that an exception might interrupt withMonitor before the unmonitor+  -- has completed.  I think that's better than making the unmonitor+  -- uninterruptible.++-- | Remove a link+-- -- This is synchronous in the sense that once it returns you are guaranteed -- that no exception will be raised if the remote process dies. However, it is--- asynchronous in the sense that we do not wait for a response from the remote +-- asynchronous in the sense that we do not wait for a response from the remote -- node. unlink :: ProcessId -> Process () unlink pid = do   unlinkAsync pid-  receiveWait [ matchIf (\(DidUnlinkProcess pid') -> pid' == pid) -                        (\_ -> return ()) +  receiveWait [ matchIf (\(DidUnlinkProcess pid') -> pid' == pid)+                        (\_ -> return ())               ] --- | Remove a node link +-- | Remove a node link ----- This has the same synchronous/asynchronous nature as 'unlink'. +-- This has the same synchronous/asynchronous nature as 'unlink'. unlinkNode :: NodeId -> Process () unlinkNode nid = do   unlinkNodeAsync nid@@ -407,7 +584,7 @@  -- | Remove a channel (send port) link ----- This has the same synchronous/asynchronous nature as 'unlink'. +-- This has the same synchronous/asynchronous nature as 'unlink'. unlinkPort :: SendPort a -> Process () unlinkPort sport = do   unlinkPortAsync sport@@ -415,9 +592,9 @@                         (\_ -> return ())               ] --- | Remove a monitor +-- | Remove a monitor ----- This has the same synchronous/asynchronous nature as 'unlink'. +-- This has the same synchronous/asynchronous nature as 'unlink'. unmonitor :: MonitorRef -> Process () unmonitor ref = do   unmonitorAsync ref@@ -433,11 +610,17 @@ catch :: Exception e => Process a -> (e -> Process a) -> Process a catch p h = do   lproc <- ask-  liftIO $ Ex.catch (runLocalProcess lproc p) (runLocalProcess lproc . h) +  liftIO $ Ex.catch (runLocalProcess lproc p) (runLocalProcess lproc . h) --- | Lift 'Control.Exception.mask' +-- | Lift 'Control.Exception.try'+try :: Exception e => Process a -> Process (Either e a)+try p = do+  lproc <- ask+  liftIO $ Ex.try (runLocalProcess lproc p)++-- | Lift 'Control.Exception.mask' mask :: ((forall a. Process a -> Process a) -> Process b) -> Process b-mask p = do +mask p = do     lproc <- ask     liftIO $ Ex.mask $ \restore ->       runLocalProcess lproc (p (liftRestore lproc restore))@@ -465,18 +648,35 @@  -- | Lift 'Control.Exception.finally' finally :: Process a -> Process b -> Process a-finally a sequel = bracket_ (return ()) sequel a +finally a sequel = bracket_ (return ()) sequel a +-- | You need this when using 'catches'+data Handler a = forall e . Exception e => Handler (e -> Process a)++instance Functor Handler where+     fmap f (Handler h) = Handler (fmap f . h)++-- | Lift 'Control.Exception.catches'+catches :: Process a -> [Handler a] -> Process a+catches proc handlers = proc `catch` catchesHandler handlers++catchesHandler :: [Handler a] -> SomeException -> Process a+catchesHandler handlers e = foldr tryHandler (throw e) handlers+    where tryHandler (Handler handler) res+              = case fromException e of+                Just e' -> handler e'+                Nothing -> res+ -------------------------------------------------------------------------------- -- Auxiliary API                                                              -- --------------------------------------------------------------------------------  -- | Like 'expect' but with a timeout expectTimeout :: forall a. Serializable a => Int -> Process (Maybe a)-expectTimeout n = receiveTimeout n [match return] +expectTimeout n = receiveTimeout n [match return]  -- | Asynchronous version of 'spawn'--- +-- -- ('spawn' is defined in terms of 'spawnAsync' and 'expect') spawnAsync :: NodeId -> Closure (Process ()) -> Process SpawnRef spawnAsync nid proc = do@@ -486,41 +686,41 @@  -- | Monitor a node (asynchronous) monitorNode :: NodeId -> Process MonitorRef-monitorNode = +monitorNode =   monitor' . NodeIdentifier  -- | Monitor a typed channel (asynchronous) monitorPort :: forall a. Serializable a => SendPort a -> Process MonitorRef-monitorPort (SendPort cid) = -  monitor' (SendPortIdentifier cid) +monitorPort (SendPort cid) =+  monitor' (SendPortIdentifier cid)  -- | Remove a monitor (asynchronous) unmonitorAsync :: MonitorRef -> Process ()-unmonitorAsync = +unmonitorAsync =   sendCtrlMsg Nothing . Unmonitor  -- | Link to a node (asynchronous) linkNode :: NodeId -> Process ()-linkNode = link' . NodeIdentifier +linkNode = link' . NodeIdentifier  -- | Link to a channel (asynchronous) linkPort :: SendPort a -> Process ()-linkPort (SendPort cid) = +linkPort (SendPort cid) =   link' (SendPortIdentifier cid)  -- | Remove a process link (asynchronous) unlinkAsync :: ProcessId -> Process ()-unlinkAsync = +unlinkAsync =   sendCtrlMsg Nothing . Unlink . ProcessIdentifier  -- | Remove a node link (asynchronous) unlinkNodeAsync :: NodeId -> Process ()-unlinkNodeAsync = +unlinkNodeAsync =   sendCtrlMsg Nothing . Unlink . NodeIdentifier  -- | Remove a channel (send port) link (asynchronous) unlinkPortAsync :: SendPort a -> Process ()-unlinkPortAsync (SendPort cid) = +unlinkPortAsync (SendPort cid) =   sendCtrlMsg Nothing . Unlink $ SendPortIdentifier cid  --------------------------------------------------------------------------------@@ -573,12 +773,12 @@ -- -- See comments in 'whereisRemoteAsync' registerRemoteAsync :: NodeId -> String -> ProcessId -> Process ()-registerRemoteAsync nid label pid = -  sendCtrlMsg (Just nid) (Register label nid (Just pid) False) +registerRemoteAsync nid label pid =+  sendCtrlMsg (Just nid) (Register label nid (Just pid) False)  reregisterRemoteAsync :: NodeId -> String -> ProcessId -> Process ()-reregisterRemoteAsync nid label pid = -  sendCtrlMsg (Just nid) (Register label nid (Just pid) True) +reregisterRemoteAsync nid label pid =+  sendCtrlMsg (Just nid) (Register label nid (Just pid) True)  -- | Remove a process from the local registry (asynchronous). -- This version will wait until a response is gotten from the@@ -594,12 +794,12 @@ -- | Deal with the result from an attempted registration or unregistration -- by throwing an exception if necessary handleRegistrationReply :: String -> Bool -> Process ()-handleRegistrationReply label ok = +handleRegistrationReply label ok =   when (not ok) $      liftIO $ throwIO $ ProcessRegistrationException label  -- | Remove a process from a remote registry (asynchronous).--- +-- -- Reply wil come in the form of a 'RegisterReply' message -- -- See comments in 'whereisRemoteAsync'@@ -617,25 +817,25 @@  -- | Query a remote process registry (asynchronous) ----- Reply will come in the form of a 'WhereIsReply' message. +-- Reply will come in the form of a 'WhereIsReply' message. -- -- There is currently no synchronous version of 'whereisRemoteAsync': if -- you implement one yourself, be sure to take into account that the remote -- node might die or get disconnect before it can respond (i.e. you should--- use 'monitorNode' and take appropriate action when you receive a +-- use 'monitorNode' and take appropriate action when you receive a -- 'NodeMonitorNotification'). whereisRemoteAsync :: NodeId -> String -> Process ()-whereisRemoteAsync nid label = +whereisRemoteAsync nid label =   sendCtrlMsg (Just nid) (WhereIs label) --- | Named send to a process in the local registry (asynchronous) +-- | Named send to a process in the local registry (asynchronous) nsend :: Serializable a => String -> a -> Process ()-nsend label msg = +nsend label msg =   sendCtrlMsg Nothing (NamedSend label (createMessage msg))  -- | Named send to a process in a remote registry (asynchronous) nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()-nsendRemote nid label msg = +nsendRemote nid label msg =   sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))  --------------------------------------------------------------------------------@@ -653,9 +853,9 @@ -- | Resolve a closure unClosure :: Typeable a => Closure a -> Process a unClosure closure = do-  rtable <- remoteTable . processNode <$> ask +  rtable <- remoteTable . processNode <$> ask   case Static.unclosure rtable closure of-    Left err -> fail $ "Could not resolve closure: " ++ err +    Left err -> fail $ "Could not resolve closure: " ++ err     Right x  -> return x  --------------------------------------------------------------------------------@@ -666,11 +866,11 @@ -- message passing. However, when network connections get disrupted this -- illusion cannot always be maintained. Once a network connection breaks (even -- temporarily) no further communication on that connection will be possible.--- For example, if process A sends a message to process B, and A is then +-- For example, if process A sends a message to process B, and A is then -- notified (by monitor notification) that it got disconnected from B, A will--- not be able to send any further messages to B, /unless/ A explicitly +-- not be able to send any further messages to B, /unless/ A explicitly -- indicates that it is acceptable to attempt to reconnect to B using the--- Cloud Haskell 'reconnect' primitive. +-- Cloud Haskell 'reconnect' primitive. -- -- Importantly, when A calls 'reconnect' it acknowledges that some messages to -- B might have been lost. For instance, if A sends messages m1 and m2 to B,@@ -690,22 +890,65 @@  -- | Reconnect to a sendport. See 'reconnect' for more information. reconnectPort :: SendPort a -> Process ()-reconnectPort them = do +reconnectPort them = do   us <- getSelfPid   node <- processNode <$> ask   liftIO $ disconnect node (ProcessIdentifier us) (SendPortIdentifier (sendPortId them))  --------------------------------------------------------------------------------+-- Debugging/Tracing                                                          --+--------------------------------------------------------------------------------++-- | Send a message to the internal (system) trace facility. If tracing is+-- enabled, this will create a custom trace event. Note that several Cloud Haskell+-- sub-systems also generate trace events for informational/debugging purposes,+-- thus traces generated this way will not be the only output seen.+--+-- Just as with the "Debug.Trace" module, this is a debugging/tracing facility+-- for use in development, and should not be used in a production setting -+-- which is why the default behaviour is to trace to the GHC eventlog. For a+-- general purpose logging facility, you should consider 'say'.+--+-- Trace events can be written to the GHC event log, a text file, or to the+-- standard system logger process (see 'say'). The default behaviour for writing+-- to the eventlog requires specific intervention to work, without which traces+-- are silently dropped/ignored and no output will be generated.+-- The GHC eventlog documentation provides information about enabling, viewing+-- and working with event traces: <http://hackage.haskell.org/trac/ghc/wiki/EventLog>.+--+-- When a new local node is started, the contents of the environment variable+-- @DISTRIBUTED_PROCESS_TRACE_FILE@ are checked for a valid file path. If this+-- exists and the file can be opened for writing, all trace output will be directed+-- thence. If the environment variable is empty, the path invalid, or the file+-- unavailable for writing - e.g., because another node has already started+-- tracing to it - then the @DISTRIBUTED_PROCESS_TRACE_CONSOLE@ environment+-- variable is checked for /any/ non-empty value. If this is set, then all trace+-- output will be directed to the system logger process. If neither evironment+-- variable provides a valid trace configuration, all internal traces are written+-- to "Debug.Trace.traceEventIO", which writes to the GHC eventlog.+--+-- Users of the /simplelocalnet/ Cloud Haskell backend should also note that+-- because the trace file option only supports trace output from a single node+-- (so as to avoid interleaving), a file trace configured for the master node will+-- prevent slaves from tracing to the file and they will fall back to using the+-- console/'say' or eventlog instead.+--+trace :: String -> Process ()+trace s = do+  node <- processNode <$> ask+  liftIO $ Trace.trace (localTracer node) s++-------------------------------------------------------------------------------- -- Auxiliary functions                                                        -- --------------------------------------------------------------------------------  getMonitorRefFor :: Identifier -> Process MonitorRef getMonitorRefFor ident = do   proc <- ask-  liftIO $ modifyMVar (processState proc) $ \st -> do -    let counter = st ^. monitorCounter +  liftIO $ modifyMVar (processState proc) $ \st -> do+    let counter = st ^. monitorCounter     return ( monitorCounter ^: (+ 1) $ st-           , MonitorRef ident counter +           , MonitorRef ident counter            )  getSpawnRef :: Process SpawnRef@@ -720,7 +963,7 @@ -- | Monitor a process/node/channel monitor' :: Identifier -> Process MonitorRef monitor' ident = do-  monitorRef <- getMonitorRefFor ident +  monitorRef <- getMonitorRefFor ident   sendCtrlMsg Nothing $ Monitor monitorRef   return monitorRef @@ -730,17 +973,16 @@  -- Send a control message sendCtrlMsg :: Maybe NodeId  -- ^ Nothing for the local node-            -> ProcessSignal -- ^ Message to send +            -> ProcessSignal -- ^ Message to send             -> Process ()-sendCtrlMsg mNid signal = do            +sendCtrlMsg mNid signal = do   proc <- ask-  let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc) +  let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc)                   , ctrlMsgSignal = signal                   }   case mNid of     Nothing -> do-      ctrlChan <- localCtrlChan . processNode <$> ask -      liftIO $ writeChan ctrlChan msg +      liftIO $ writeChan (localCtrlChan (processNode proc)) msg     Just nid ->       liftIO $ sendBinary (processNode proc)                           (ProcessIdentifier (processId proc))
src/Control/Distributed/Process/Internal/StrictContainerAccessors.hs view
@@ -1,4 +1,4 @@-module Control.Distributed.Process.Internal.StrictContainerAccessors +module Control.Distributed.Process.Internal.StrictContainerAccessors   ( mapMaybe   , mapDefault   ) where
src/Control/Distributed/Process/Internal/StrictList.hs view
@@ -1,25 +1,27 @@ -- | Spine and element strict list-module Control.Distributed.Process.Internal.StrictList -  ( StrictList(Cons, Nil)-  , length -  , reverse-  , reverse'+module Control.Distributed.Process.Internal.StrictList+  ( StrictList(..)+  , append+  , foldr   ) where -import Prelude hiding (length, reverse)+import Prelude hiding (length, reverse, foldr)  -- | Strict list-data StrictList a = Cons !a !(StrictList a) | Nil--length :: StrictList a -> Int-length Nil         = 0-length (Cons _ xs) = 1 + length xs+data StrictList a+   = Cons !a !(StrictList a)+   | Nil+   | Snoc !(StrictList a) !a+   | Append !(StrictList a) !(StrictList a) --- | Reverse a strict list-reverse :: StrictList a -> StrictList a -reverse xs = reverse' xs Nil+append :: StrictList a -> StrictList a -> StrictList a+append Nil l = l+append l Nil = l+append l1 l2 = l1 `Append` l2 --- | @reverseStrict' xs ys@ is 'reverse xs ++ ys' if they were lists-reverse' :: StrictList a -> StrictList a -> StrictList a -reverse' Nil         ys = ys-reverse' (Cons x xs) ys = reverse' xs (Cons x ys)+foldr :: (a -> b -> b) -> b -> StrictList a -> b+foldr f c0 xs0 = go xs0 c0+  where go Nil            c = c+        go (Cons x xs)    c = f x (go xs c)+        go (Snoc xs x)    c = go xs (f x c)+        go (Append xs ys) c = go xs (go ys c)
src/Control/Distributed/Process/Internal/StrictMVar.hs view
@@ -1,6 +1,6 @@ -- | Like Control.Concurrent.MVar.Strict but reduce to HNF, not NF {-# LANGUAGE MagicHash, UnboxedTuples #-}-module Control.Distributed.Process.Internal.StrictMVar +module Control.Distributed.Process.Internal.StrictMVar   ( StrictMVar(StrictMVar)   , newEmptyMVar   , newMVar@@ -15,7 +15,7 @@ import Control.Applicative ((<$>)) import Control.Monad ((>=>)) import Control.Exception (evaluate)-import qualified Control.Concurrent.MVar as MVar +import qualified Control.Concurrent.MVar as MVar   ( MVar   , newEmptyMVar   , newMVar@@ -26,7 +26,7 @@   , modifyMVar   ) import GHC.MVar (MVar(MVar))-import GHC.IO (IO(IO)) +import GHC.IO (IO(IO)) import GHC.Prim (mkWeak#) import GHC.Weak (Weak(Weak)) @@ -48,7 +48,7 @@ withMVar (StrictMVar v) = MVar.withMVar v  modifyMVar_ :: StrictMVar a -> (a -> IO a) -> IO ()-modifyMVar_ (StrictMVar v) f = MVar.modifyMVar_ v (f >=> evaluate) +modifyMVar_ (StrictMVar v) f = MVar.modifyMVar_ v (f >=> evaluate)  modifyMVar :: StrictMVar a -> (a -> IO (a, b)) -> IO b modifyMVar (StrictMVar v) f = MVar.modifyMVar v (f >=> evaluateFst)
+ src/Control/Distributed/Process/Internal/Trace.hs view
@@ -0,0 +1,122 @@+-- | Simple (internal) system logging/tracing support.+module Control.Distributed.Process.Internal.Trace+  ( Tracer+  , TraceArg(..)+  , trace+  , traceFormat+  , startTracing+  , stopTracer+  ) where++import Control.Concurrent (forkIO)+import Control.Concurrent.Chan (writeChan)+import Control.Concurrent.STM+  ( TQueue+  , newTQueueIO+  , readTQueue+  , writeTQueue+  , atomically+  )+import Control.Distributed.Process.Internal.Types+  ( Tracer(..)+  , LocalNode(..)+  , NCMsg(..)+  , Identifier(ProcessIdentifier)+  , ProcessSignal(NamedSend)+  , forever'+  , nullProcessId+  , createMessage+  )+import Control.Exception+  ( catch+  , throwTo+  , SomeException+  , AsyncException(ThreadKilled)+  )+import Data.List (intersperse)+import Data.Time.Clock (getCurrentTime)+import Data.Time.Format (formatTime)+import Debug.Trace (traceEventIO)++import Prelude hiding (catch)++import System.Environment (getEnv)+import System.IO+  ( Handle+  , IOMode(AppendMode)+  , BufferMode(..)+  , openFile+  , hClose+  , hPutStrLn+  , hSetBuffering+  )+import System.Locale (defaultTimeLocale)++data TraceArg = +    TraceStr String+  | forall a. (Show a) => Trace a++startTracing :: LocalNode -> IO LocalNode+startTracing node = do+  tracer <- defaultTracer node+  return node { localTracer = tracer }++defaultTracer :: LocalNode -> IO Tracer+defaultTracer node = do+  catch (getEnv "DISTRIBUTED_PROCESS_TRACE_FILE" >>= logfileTracer)+        (\(_ :: IOError) -> defaultTracerAux node)++defaultTracerAux :: LocalNode -> IO Tracer+defaultTracerAux node = do+  catch (getEnv "DISTRIBUTED_PROCESS_TRACE_CONSOLE" >> procTracer node)+        (\(_ :: IOError) -> return (EventLogTracer traceEventIO))+  where procTracer :: LocalNode -> IO Tracer+        procTracer n = return $ (LocalNodeTracer n)++logfileTracer :: FilePath -> IO Tracer+logfileTracer p = do+    q <- newTQueueIO+    h <- openFile p AppendMode+    hSetBuffering h LineBuffering+    tid <- forkIO $ logger h q `catch` (\(_ :: SomeException) ->+                                         hClose h >> return ())+    return $ LogFileTracer tid q h+  where logger :: Handle -> TQueue String -> IO ()+        logger h q' = forever' $ do+          msg <- atomically $ readTQueue q'+          now <- getCurrentTime+          hPutStrLn h $ msg ++ (formatTime defaultTimeLocale " - %c" now)++-- TODO: compatibility layer for GHC/base versions (e.g., where's killThread?)++stopTracer :: Tracer -> IO ()  -- overzealous but harmless duplication of hClose+stopTracer (LogFileTracer tid _ h) = throwTo tid ThreadKilled >> hClose h+stopTracer _                       = return ()++trace :: Tracer -> String -> IO ()+trace (LogFileTracer _ q _) msg = atomically $ writeTQueue q msg+trace (LocalNodeTracer n)   msg = sendTraceMsg n msg+trace (EventLogTracer t)    msg = t msg+trace InactiveTracer        _   = return ()++traceFormat :: Tracer+            -> String+            -> [TraceArg]+            -> IO ()+traceFormat t d ls =+    trace t $ concat (intersperse d (map toS ls))+  where toS :: TraceArg -> String+        toS (TraceStr s) = s+        toS (Trace    a) = show a++sendTraceMsg :: LocalNode -> String -> IO ()+sendTraceMsg node string = do+  now <- getCurrentTime+  msg <- return $ (formatTime defaultTimeLocale "%c" now, string)+  emptyPid <- return $ (nullProcessId (localNodeId node))+  traceMsg <- return $ NCMsg {+                         ctrlMsgSender = ProcessIdentifier (emptyPid)+                       , ctrlMsgSignal = (NamedSend "logger" (createMessage msg))+                       }+  writeChan (localCtrlChan node) traceMsg+
src/Control/Distributed/Process/Internal/Types.hs view
@@ -1,10 +1,10 @@ -- | Types used throughout the Cloud Haskell framework ----- We collect all types used internally in a single module because +-- We collect all types used internally in a single module because -- many of these data types are mutually recursive and cannot be split across -- modules. module Control.Distributed.Process.Internal.Types-  ( -- * Node and process identifiers +  ( -- * Node and process identifiers     NodeId(..)   , LocalProcessId(..)   , ProcessId(..)@@ -14,6 +14,7 @@   , nullProcessId     -- * Local nodes and processes   , LocalNode(..)+  , Tracer(..)   , LocalNodeState(..)   , LocalProcess(..)   , LocalProcessState(..)@@ -21,21 +22,22 @@   , runLocalProcess   , ImplicitReconnect(..)     -- * Typed channels-  , LocalSendPortId +  , LocalSendPortId   , SendPortId(..)   , TypedChannel(..)   , SendPort(..)   , ReceivePort(..)-    -- * Messages +    -- * Messages   , Message(..)   , createMessage   , messageToPayload   , payloadToMessage-    -- * Node controller user-visible data types +    -- * Node controller user-visible data types   , MonitorRef(..)   , ProcessMonitorNotification(..)   , NodeMonitorNotification(..)   , PortMonitorNotification(..)+  , ProcessExitException(..)   , ProcessLinkException(..)   , NodeLinkException(..)   , PortLinkException(..)@@ -49,7 +51,9 @@   , DidSpawn(..)   , WhereIsReply(..)   , RegisterReply(..)-    -- * Node controller internal data types +  , ProcessInfo(..)+  , ProcessInfoNone(..)+    -- * Node controller internal data types   , NCMsg(..)   , ProcessSignal(..)     -- * Accessors@@ -64,6 +68,8 @@   , channelCounter   , typedChannels   , typedChannelWithId+    -- * Utilities+  , forever'   ) where  import System.Mem.Weak (Weak)@@ -72,7 +78,7 @@ import Data.Typeable (Typeable) import Data.Binary (Binary(put, get), putWord8, getWord8, encode) import qualified Data.ByteString as BSS (ByteString, concat, copy)-import qualified Data.ByteString.Lazy as BSL +import qualified Data.ByteString.Lazy as BSL   ( ByteString   , toChunks   , splitAt@@ -85,11 +91,12 @@ import Control.Concurrent (ThreadId) import Control.Concurrent.Chan (Chan) import Control.Concurrent.STM (STM)+import qualified Control.Concurrent.STM as STM (TQueue) import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection) import Control.Applicative (Applicative, Alternative, (<$>), (<*>)) import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT) import Control.Monad.IO.Class (MonadIO)-import Control.Distributed.Process.Serializable +import Control.Distributed.Process.Serializable   ( Fingerprint   , Serializable   , fingerprint@@ -103,43 +110,44 @@ import Control.Distributed.Process.Internal.WeakTQueue (TQueue) import Control.Distributed.Static (RemoteTable, Closure) import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)+import System.IO (Handle)  -------------------------------------------------------------------------------- -- Node and process identifiers                                               -- -------------------------------------------------------------------------------- --- | Node identifier +-- | Node identifier newtype NodeId = NodeId { nodeAddress :: NT.EndPointAddress }   deriving (Eq, Ord, Binary, Typeable)  instance Show NodeId where-  show (NodeId addr) = "nid://" ++ show addr +  show (NodeId addr) = "nid://" ++ show addr  -- | A local process ID consists of a seed which distinguishes processes from -- different instances of the same local node and a counter-data LocalProcessId = LocalProcessId +data LocalProcessId = LocalProcessId   { lpidUnique  :: {-# UNPACK #-} !Int32   , lpidCounter :: {-# UNPACK #-} !Int32   }   deriving (Eq, Ord, Typeable, Show)  -- | Process identifier-data ProcessId = ProcessId +data ProcessId = ProcessId   { -- | The ID of the node the process is running on     processNodeId  :: !NodeId     -- | Node-local identifier for the process-  , processLocalId :: {-# UNPACK #-} !LocalProcessId +  , processLocalId :: {-# UNPACK #-} !LocalProcessId   }   deriving (Eq, Ord, Typeable)  instance Show ProcessId where-  show (ProcessId (NodeId addr) (LocalProcessId _ lid)) +  show (ProcessId (NodeId addr) (LocalProcessId _ lid))     = "pid://" ++ show addr ++ ":" ++ show lid --- | Union of all kinds of identifiers -data Identifier = +-- | Union of all kinds of identifiers+data Identifier =     NodeIdentifier !NodeId-  | ProcessIdentifier !ProcessId +  | ProcessIdentifier !ProcessId   | SendPortIdentifier !SendPortId   deriving (Eq, Ord) @@ -161,7 +169,7 @@ firstNonReservedProcessId = 1  nullProcessId :: NodeId -> ProcessId-nullProcessId nid = +nullProcessId nid =   ProcessId { processNodeId  = nid             , processLocalId = LocalProcessId { lpidUnique  = 0                                               , lpidCounter = 0@@ -172,40 +180,49 @@ -- Local nodes and processes                                                  -- -------------------------------------------------------------------------------- +-- | Required for system tracing in the node controller+data Tracer =+    LogFileTracer   !ThreadId !(STM.TQueue String) !Handle+  | EventLogTracer  !(String -> IO ())+  | LocalNodeTracer !LocalNode+  | InactiveTracer  -- NB: never used, this is required to initialize LocalNode+ -- | Local nodes-data LocalNode = LocalNode +data LocalNode = LocalNode   { -- | 'NodeId' of the node-    localNodeId :: !NodeId-    -- | The network endpoint associated with this node -  , localEndPoint :: !NT.EndPoint -    -- | Local node state -  , localState :: !(StrictMVar LocalNodeState)+    localNodeId     :: !NodeId+    -- | The network endpoint associated with this node+  , localEndPoint   :: !NT.EndPoint+    -- | Local node state+  , localState      :: !(StrictMVar LocalNodeState)     -- | Channel for the node controller-  , localCtrlChan :: !(Chan NCMsg)+  , localCtrlChan   :: !(Chan NCMsg)+    -- | Current active system debug/trace log+  , localTracer     :: !Tracer     -- | Runtime lookup table for supporting closures     -- TODO: this should be part of the CH state, not the local endpoint state-  , remoteTable :: !RemoteTable +  , remoteTable     :: !RemoteTable   }  data ImplicitReconnect = WithImplicitReconnect | NoImplicitReconnect   deriving (Eq, Show)  -- | Local node state-data LocalNodeState = LocalNodeState +data LocalNodeState = LocalNodeState   { -- | Processes running on this node     _localProcesses   :: !(Map LocalProcessId LocalProcess)-    -- | Counter to assign PIDs +    -- | Counter to assign PIDs   , _localPidCounter  :: !Int32     -- | The 'unique' value used to create PIDs (so that processes on     -- restarted nodes have new PIDs)   , _localPidUnique   :: !Int32     -- | Outgoing connections-  , _localConnections :: !(Map (Identifier, Identifier) +  , _localConnections :: !(Map (Identifier, Identifier)                                (NT.Connection, ImplicitReconnect))   }  -- | Processes running on our local node-data LocalProcess = LocalProcess +data LocalProcess = LocalProcess   { processQueue  :: !(CQueue Message)   , processWeakQ  :: !(Weak (CQueue Message))   , processId     :: !ProcessId@@ -214,7 +231,7 @@   , processNode   :: !LocalNode   } --- | Deconstructor for 'Process' (not exported to the public API) +-- | Deconstructor for 'Process' (not exported to the public API) runLocalProcess :: LocalProcess -> Process a -> IO a runLocalProcess lproc proc = runReaderT (unProcess proc) lproc @@ -227,8 +244,8 @@   }  -- | The Cloud Haskell 'Process' type-newtype Process a = Process { -    unProcess :: ReaderT LocalProcess IO a +newtype Process a = Process {+    unProcess :: ReaderT LocalProcess IO a   }   deriving (Functor, Monad, MonadIO, MonadReader LocalProcess, Typeable, Applicative) @@ -251,15 +268,15 @@   deriving (Eq, Ord)  instance Show SendPortId where-  show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)  +  show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)     = "cid://" ++ show addr ++ ":" ++ show plid ++ ":" ++ show clid  data TypedChannel = forall a. Serializable a => TypedChannel (Weak (TQueue a))  -- | The send send of a typed channel (serializable)-newtype SendPort a = SendPort { +newtype SendPort a = SendPort {     -- | The (unique) ID of this send port-    sendPortId :: SendPortId +    sendPortId :: SendPortId   }   deriving (Typeable, Binary, Show, Eq, Ord) @@ -271,13 +288,13 @@   deriving (Typeable, Functor, Applicative, Alternative, Monad)  {--data ReceivePort a = +data ReceivePort a =     -- | A single receive port     ReceivePortSingle (TQueue a)-    -- | A left-biased combination of receive ports -  | ReceivePortBiased [ReceivePort a] +    -- | A left-biased combination of receive ports+  | ReceivePortBiased [ReceivePort a]     -- | A round-robin combination of receive ports-  | ReceivePortRR (TVar [ReceivePort a]) +  | ReceivePortRR (TVar [ReceivePort a])   deriving Typeable -} @@ -286,13 +303,13 @@ --------------------------------------------------------------------------------  -- | Messages consist of their typeRep fingerprint and their encoding-data Message = Message -  { messageFingerprint :: !Fingerprint +data Message = Message+  { messageFingerprint :: !Fingerprint   , messageEncoding    :: !BSL.ByteString   }  instance Show Message where-  show (Message fp enc) = show enc ++ " :: " ++ showFingerprint fp [] +  show (Message fp enc) = show enc ++ " :: " ++ showFingerprint fp []  -- | Turn any serialiable term into a message createMessage :: Serializable a => a -> Message@@ -304,26 +321,26 @@  -- | Deserialize a message payloadToMessage :: [BSS.ByteString] -> Message-payloadToMessage payload = Message fp (copy msg) +payloadToMessage payload = Message fp (copy msg)   where     encFp :: BSL.ByteString     msg   :: BSL.ByteString-    (encFp, msg) = BSL.splitAt (fromIntegral sizeOfFingerprint) -                 $ BSL.fromChunks payload +    (encFp, msg) = BSL.splitAt (fromIntegral sizeOfFingerprint)+                 $ BSL.fromChunks payload      fp :: Fingerprint     fp = decodeFingerprint . BSS.concat . BSL.toChunks $ encFp      copy :: BSL.ByteString -> BSL.ByteString     copy (BSL.Chunk bs BSL.Empty) = BSL.Chunk (BSS.copy bs) BSL.Empty-    copy bsl = BSL.fromChunks . return . BSS.concat . BSL.toChunks $ bsl +    copy bsl = BSL.fromChunks . return . BSS.concat . BSL.toChunks $ bsl  -------------------------------------------------------------------------------- -- Node controller user-visible data types                                    -- -------------------------------------------------------------------------------- --- | MonitorRef is opaque for regular Cloud Haskell processes -data MonitorRef = MonitorRef +-- | MonitorRef is opaque for regular Cloud Haskell processes+data MonitorRef = MonitorRef   { -- | ID of the entity to be monitored     monitorRefIdent   :: !Identifier     -- | Unique to distinguish multiple monitor requests by the same process@@ -332,49 +349,58 @@   deriving (Eq, Ord, Show)  -- | Message sent by process monitors-data ProcessMonitorNotification = +data ProcessMonitorNotification =     ProcessMonitorNotification !MonitorRef !ProcessId !DiedReason   deriving (Typeable, Show)  -- | Message sent by node monitors-data NodeMonitorNotification = +data NodeMonitorNotification =     NodeMonitorNotification !MonitorRef !NodeId !DiedReason   deriving (Typeable, Show)  -- | Message sent by channel (port) monitors-data PortMonitorNotification = +data PortMonitorNotification =     PortMonitorNotification !MonitorRef !SendPortId !DiedReason   deriving (Typeable, Show)  -- | Exceptions thrown when a linked process dies-data ProcessLinkException = +data ProcessLinkException =     ProcessLinkException !ProcessId !DiedReason   deriving (Typeable, Show)  -- | Exception thrown when a linked node dies-data NodeLinkException = +data NodeLinkException =     NodeLinkException !NodeId !DiedReason   deriving (Typeable, Show)  -- | Exception thrown when a linked channel (port) dies-data PortLinkException = +data PortLinkException =     PortLinkException !SendPortId !DiedReason   deriving (Typeable, Show)  -- | Exception thrown when a process attempts to register -- a process under an already-registered name or to -- unregister a name that hasn't been registered-data ProcessRegistrationException = +data ProcessRegistrationException =     ProcessRegistrationException !String   deriving (Typeable, Show) +-- | Internal exception thrown indirectly by 'exit'+data ProcessExitException =+    ProcessExitException !ProcessId !Message+  deriving Typeable++instance Exception ProcessExitException+instance Show ProcessExitException where+  show (ProcessExitException pid _) = "exit-from=" ++ (show pid)+ instance Exception ProcessLinkException instance Exception NodeLinkException instance Exception PortLinkException instance Exception ProcessRegistrationException  -- | Why did a process die?-data DiedReason = +data DiedReason =     -- | Normal termination     DiedNormal     -- | The process exited with an exception@@ -384,7 +410,7 @@   | DiedDisconnect     -- | The process node died   | DiedNodeDown-    -- | Invalid (process/node/channel) identifier +    -- | Invalid (process/node/channel) identifier   | DiedUnknownId   deriving (Show, Eq) @@ -401,7 +427,7 @@   deriving (Typeable, Binary)  -- | (Asynchronous) reply from unlinkPort-newtype DidUnlinkPort = DidUnlinkPort SendPortId +newtype DidUnlinkPort = DidUnlinkPort SendPortId   deriving (Typeable, Binary)  -- | 'SpawnRef' are used to return pids of spawned processes@@ -420,28 +446,43 @@ data RegisterReply = RegisterReply String Bool   deriving (Show, Typeable) +-- | Provide information about a running process+data ProcessInfo = ProcessInfo {+    infoNode               :: NodeId+  , infoRegisteredNames    :: [String]+  , infoMessageQueueLength :: Maybe Int+  , infoMonitors           :: [(ProcessId, MonitorRef)]+  , infoLinks              :: [ProcessId]+  } deriving (Show, Eq, Typeable)++data ProcessInfoNone = ProcessInfoNone DiedReason+    deriving (Show, Typeable)+ -------------------------------------------------------------------------------- -- Node controller internal data types                                        -- --------------------------------------------------------------------------------  -- | Messages to the node controller-data NCMsg = NCMsg -  { ctrlMsgSender :: !Identifier +data NCMsg = NCMsg+  { ctrlMsgSender :: !Identifier   , ctrlMsgSignal :: !ProcessSignal   }   deriving Show  -- | Signals to the node controller (see 'NCMsg') data ProcessSignal =-    Link !Identifier -  | Unlink !Identifier +    Link !Identifier+  | Unlink !Identifier   | Monitor !MonitorRef   | Unmonitor !MonitorRef   | Died Identifier !DiedReason-  | Spawn !(Closure (Process ())) !SpawnRef +  | Spawn !(Closure (Process ())) !SpawnRef   | WhereIs !String   | Register !String !NodeId !(Maybe ProcessId) !Bool -- Use 'Nothing' to unregister, use True to force reregister   | NamedSend !String !Message+  | Kill !ProcessId !String+  | Exit !ProcessId !Message+  | GetInfo !ProcessId   deriving Show  --------------------------------------------------------------------------------@@ -480,29 +521,35 @@   put (Link pid)            = putWord8 0 >> put pid   put (Unlink pid)          = putWord8 1 >> put pid   put (Monitor ref)         = putWord8 2 >> put ref-  put (Unmonitor ref)       = putWord8 3 >> put ref +  put (Unmonitor ref)       = putWord8 3 >> put ref   put (Died who reason)     = putWord8 4 >> put who >> put reason   put (Spawn proc ref)      = putWord8 5 >> put proc >> put ref   put (WhereIs label)       = putWord8 6 >> put label   put (Register label nid pid force) = putWord8 7 >> put label >> put nid >> put pid >> put force-  put (NamedSend label msg) = putWord8 8 >> put label >> put (messageToPayload msg) +  put (NamedSend label msg) = putWord8 8 >> put label >> put (messageToPayload msg)+  put (Kill pid reason)     = putWord8 9 >> put pid >> put reason+  put (Exit pid reason)     = putWord8 10 >> put pid >> put (messageToPayload reason)+  put (GetInfo about)       = putWord8 30 >> put about   get = do     header <- getWord8     case header of-      0 -> Link <$> get-      1 -> Unlink <$> get-      2 -> Monitor <$> get-      3 -> Unmonitor <$> get-      4 -> Died <$> get <*> get-      5 -> Spawn <$> get <*> get-      6 -> WhereIs <$> get-      7 -> Register <$> get <*> get <*> get <*> get-      8 -> NamedSend <$> get <*> (payloadToMessage <$> get)+      0  -> Link <$> get+      1  -> Unlink <$> get+      2  -> Monitor <$> get+      3  -> Unmonitor <$> get+      4  -> Died <$> get <*> get+      5  -> Spawn <$> get <*> get+      6  -> WhereIs <$> get+      7  -> Register <$> get <*> get <*> get <*> get+      8  -> NamedSend <$> get <*> (payloadToMessage <$> get)+      9  -> Kill <$> get <*> get+      10 -> Exit <$> get <*> (payloadToMessage <$> get)+      30 -> GetInfo <$> get       _ -> fail "ProcessSignal.get: invalid"  instance Binary DiedReason where   put DiedNormal        = putWord8 0-  put (DiedException e) = putWord8 1 >> put e +  put (DiedException e) = putWord8 1 >> put e   put DiedDisconnect    = putWord8 2   put DiedNodeDown      = putWord8 3   put DiedUnknownId     = putWord8 4@@ -513,7 +560,7 @@       1 -> DiedException <$> get       2 -> return DiedDisconnect       3 -> return DiedNodeDown-      4 -> return DiedUnknownId +      4 -> return DiedUnknownId       _ -> fail "DiedReason.get: invalid"  instance Binary DidSpawn where@@ -522,18 +569,18 @@  instance Binary SendPortId where   put cid = put (sendPortProcessId cid) >> put (sendPortLocalId cid)-  get = SendPortId <$> get <*> get +  get = SendPortId <$> get <*> get  instance Binary Identifier where   put (ProcessIdentifier pid)  = putWord8 0 >> put pid   put (NodeIdentifier nid)     = putWord8 1 >> put nid   put (SendPortIdentifier cid) = putWord8 2 >> put cid   get = do-    header <- getWord8 +    header <- getWord8     case header of       0 -> ProcessIdentifier <$> get       1 -> NodeIdentifier <$> get-      2 -> SendPortIdentifier <$> get +      2 -> SendPortIdentifier <$> get       _ -> fail "Identifier.get: invalid"  instance Binary WhereIsReply where@@ -541,9 +588,21 @@   get = WhereIsReply <$> get <*> get  instance Binary RegisterReply where-  put (RegisterReply label ok) = put label >> put ok +  put (RegisterReply label ok) = put label >> put ok   get = RegisterReply <$> get <*> get +instance Binary ProcessInfo where+  get = ProcessInfo <$> get <*> get <*> get <*> get <*> get+  put pInfo = put (infoNode pInfo)+           >> put (infoRegisteredNames pInfo)+           >> put (infoMessageQueueLength pInfo)+           >> put (infoMonitors pInfo)+           >> put (infoLinks pInfo)++instance Binary ProcessInfoNone where+  get = ProcessInfoNone <$> get+  put (ProcessInfoNone r) = put r+ -------------------------------------------------------------------------------- -- Accessors                                                                  -- --------------------------------------------------------------------------------@@ -580,3 +639,13 @@  typedChannelWithId :: LocalSendPortId -> Accessor LocalProcessState (Maybe TypedChannel) typedChannelWithId cid = typedChannels >>> DAC.mapMaybe cid++--------------------------------------------------------------------------------+-- Utilities                                                                  --+--------------------------------------------------------------------------------++-- Like 'Control.Monad.forever' but sans space leak+{-# INLINE forever' #-}+forever' :: Monad m => m a -> m b+forever' a = let a' = a >> a' in a'+
src/Control/Distributed/Process/Internal/WeakTQueue.hs view
@@ -21,7 +21,7 @@ import Prelude hiding (read) import GHC.Conc import Data.Typeable (Typeable)-import GHC.IO (IO(IO)) +import GHC.IO (IO(IO)) import GHC.Prim (mkWeak#) import GHC.Weak (Weak(Weak)) @@ -98,4 +98,4 @@  mkWeakTQueue :: TQueue a -> IO () -> IO (Weak (TQueue a)) mkWeakTQueue q@(TQueue _read (TVar write#)) f = IO $ \s ->-  case mkWeak# write# q f s of (# s', w #) -> (# s', Weak w #) +  case mkWeak# write# q f s of (# s', w #) -> (# s', Weak w #)
src/Control/Distributed/Process/Node.hs view
@@ -1,8 +1,6 @@ -- | Local nodes ----- TODO: Calls to 'sendBinary' and co by the node controller may stall the--- node controller.-module Control.Distributed.Process.Node +module Control.Distributed.Process.Node   ( LocalNode   , newLocalNode   , closeLocalNode@@ -12,6 +10,8 @@   , localNodeId   ) where +-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.+ #if ! MIN_VERSION_base(4,6,0) import Prelude hiding (catch) #endif@@ -21,7 +21,7 @@ import qualified Data.ByteString.Lazy as BSL (fromChunks) import Data.Binary (decode) import Data.Map (Map)-import qualified Data.Map as Map +import qualified Data.Map as Map   ( empty   , toList   , fromList@@ -29,9 +29,16 @@   , partitionWithKey   , elems   , filterWithKey+  , foldlWithKey   ) import Data.Set (Set)-import qualified Data.Set as Set (empty, insert, delete, member)+import qualified Data.Set as Set+  ( empty+  , insert+  , delete+  , member+  , toList+  ) import Data.Foldable (forM_) import Data.Maybe (isJust, isNothing, catMaybes) import Data.Typeable (Typeable)@@ -46,7 +53,7 @@ import qualified Control.Exception as Exception (catch) import Control.Concurrent (forkIO) import Control.Distributed.Process.Internal.StrictMVar-  ( newMVar +  ( newMVar   , withMVar   , modifyMVar   , modifyMVar_@@ -55,14 +62,16 @@   , takeMVar   ) import Control.Concurrent.Chan (newChan, writeChan, readChan)-import Control.Concurrent.STM (atomically)-import Control.Distributed.Process.Internal.CQueue +import Control.Concurrent.STM+  ( atomically+  )+import Control.Distributed.Process.Internal.CQueue   ( CQueue   , enqueue   , newCQueue   , mkWeakCQueue   )-import qualified Network.Transport as NT +import qualified Network.Transport as NT   ( Transport   , EndPoint   , newEndPoint@@ -81,15 +90,16 @@ import Data.Accessor (Accessor, accessor, (^.), (^=), (^:)) import System.Random (randomIO) import Control.Distributed.Static (RemoteTable, Closure)-import qualified Control.Distributed.Static as Static +import qualified Control.Distributed.Static as Static   ( unclosure   , initRemoteTable   )-import Control.Distributed.Process.Internal.Types +import Control.Distributed.Process.Internal.Types   ( NodeId(..)   , LocalProcessId(..)   , ProcessId(..)   , LocalNode(..)+  , Tracer(InactiveTracer)   , LocalNodeState(..)   , LocalProcess(..)   , LocalProcessState(..)@@ -101,10 +111,12 @@   , localPidUnique   , localProcessWithId   , localConnections+  , forever'   , MonitorRef(..)   , ProcessMonitorNotification(..)   , NodeMonitorNotification(..)   , PortMonitorNotification(..)+  , ProcessExitException(..)   , ProcessLinkException(..)   , NodeLinkException(..)   , PortLinkException(..)@@ -118,6 +130,8 @@   , TypedChannel(..)   , Identifier(..)   , nodeOf+  , ProcessInfo(..)+  , ProcessInfoNone(..)   , SendPortId(..)   , typedChannelWithId   , RegisterReply(..)@@ -129,15 +143,28 @@   , firstNonReservedProcessId   , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)   )+import Control.Distributed.Process.Internal.Trace+  ( TraceArg(..)+  , traceFormat+  , startTracing+  , stopTracer+  ) import Control.Distributed.Process.Serializable (Serializable) import Control.Distributed.Process.Internal.Messaging   ( sendBinary   , sendMessage   , sendPayload-  , closeImplicitReconnections +  , closeImplicitReconnections   , impliesDeathOf   )-import Control.Distributed.Process.Internal.Primitives (expect, register, finally)+import Control.Distributed.Process.Internal.Primitives+  ( register+  , finally+  , receiveWait+  , match+  , sendChan+  )+import Control.Distributed.Process.Internal.Types (SendPort) import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable) import Control.Distributed.Process.Internal.WeakTQueue (writeTQueue) import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC@@ -150,27 +177,27 @@ --------------------------------------------------------------------------------  initRemoteTable :: RemoteTable-initRemoteTable = BuiltIn.remoteTable Static.initRemoteTable +initRemoteTable = BuiltIn.remoteTable Static.initRemoteTable --- | Initialize a new local node. +-- | Initialize a new local node. newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode newLocalNode transport rtable = do     mEndPoint <- NT.newEndPoint transport     case mEndPoint of       Left ex -> throwIO ex       Right endPoint -> do-        localNode <- createBareLocalNode endPoint rtable +        localNode <- createBareLocalNode endPoint rtable         startServiceProcesses localNode         return localNode-    + -- | Create a new local node (without any service processes running) createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode createBareLocalNode endPoint rtable = do   unq <- randomIO-  state <- newMVar LocalNodeState +  state <- newMVar LocalNodeState     { _localProcesses   = Map.empty-    , _localPidCounter  = firstNonReservedProcessId -    , _localPidUnique   = unq +    , _localPidCounter  = firstNonReservedProcessId+    , _localPidUnique   = unq     , _localConnections = Map.empty     }   ctrlChan <- newChan@@ -178,25 +205,33 @@                        , localEndPoint = endPoint                        , localState    = state                        , localCtrlChan = ctrlChan+                       , localTracer   = InactiveTracer                        , remoteTable   = rtable                        }-  void . forkIO $ runNodeController node -  void . forkIO $ handleIncomingMessages node-  return node---- Like 'Control.Monad.forever' but sans space leak-{-# INLINE forever' #-}-forever' :: Monad m => m a -> m b-forever' a = let a' = a >> a' in a'+  tracedNode <- startTracing node+  void . forkIO $ runNodeController tracedNode+  void . forkIO $ handleIncomingMessages tracedNode+  return tracedNode --- | Start and register the service processes on a node +-- | Start and register the service processes on a node -- (for now, this is only the logger) startServiceProcesses :: LocalNode -> IO () startServiceProcesses node = do-  logger <- forkProcess node . forever' $ do-    (time, pid, string) <- expect :: Process (String, ProcessId, String)-    liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string +  logger <- forkProcess node loop   runProcess node $ register "logger" logger+ where+  loop = do+    receiveWait+      [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do+          liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string+          loop+      , match $ \((time, string) :: (String, String)) -> do+          -- this is a 'trace' message from the local node tracer+          liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string+          loop+      , match $ \(ch :: SendPort ()) -> -- a shutdown request+          sendChan ch ()+      ]  -- | Force-close a local node --@@ -221,7 +256,7 @@       let lpid  = LocalProcessId { lpidCounter = st ^. localPidCounter                                  , lpidUnique  = st ^. localPidUnique                                  }-      let pid   = ProcessId { processNodeId  = localNodeId node +      let pid   = ProcessId { processNodeId  = localNodeId node                             , processLocalId = lpid                             }       pst <- newMVar LocalProcessState { _monitorCounter = 0@@ -235,19 +270,20 @@         let lproc = LocalProcess { processQueue  = queue                                  , processWeakQ  = weakQueue                                  , processId     = pid-                                 , processState  = pst +                                 , processState  = pst                                  , processThread = tid                                  , processNode   = node                                  }         tid' <- forkIO $ do-          reason <- Exception.catch +          reason <- Exception.catch             (runLocalProcess lproc proc >> return DiedNormal)             (return . DiedException . (show :: SomeException -> String))+           -- [Unified: Table 4, rules termination and exiting]           modifyMVar_ (localState node) (cleanupProcess pid)-          writeChan (localCtrlChan node) NCMsg -            { ctrlMsgSender = ProcessIdentifier pid -            , ctrlMsgSignal = Died (ProcessIdentifier pid) reason +          writeChan (localCtrlChan node) NCMsg+            { ctrlMsgSender = ProcessIdentifier pid+            , ctrlMsgSignal = Died (ProcessIdentifier pid) reason             }         return (tid', lproc) @@ -264,12 +300,12 @@           return ( (localProcessWithId lpid ^= Just lproc)                  . (localPidCounter ^: (+ 1))                  $ st-                 , pid +                 , pid                  )      cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState     cleanupProcess pid st = do-      let pid' = ProcessIdentifier pid +      let pid' = ProcessIdentifier pid       let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)       mapM_ (NT.close . fst) (Map.elems affected)       return $ (localProcessWithId (processLocalId pid) ^= Nothing)@@ -282,7 +318,7 @@  type IncomingConnection = (NT.EndPointAddress, IncomingTarget) -data IncomingTarget =  +data IncomingTarget =     Uninit   | ToProc (Weak (CQueue Message))   | ToChan TypedChannel@@ -305,20 +341,20 @@ incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection) incomingAt cid = incoming >>> DAC.mapMaybe cid -incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId) +incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId) incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr   where     aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })  handleIncomingMessages :: LocalNode -> IO ()-handleIncomingMessages node = go initConnectionState +handleIncomingMessages node = go initConnectionState   where-    go :: ConnectionState -> IO () +    go :: ConnectionState -> IO ()     go !st = do       event <- NT.receive endpoint       case event of         NT.ConnectionOpened cid rel theirAddr ->-          if rel == NT.ReliableOrdered +          if rel == NT.ReliableOrdered             then go ( (incomingAt cid ^= Just (theirAddr, Uninit))                     . (incomingFrom theirAddr ^: Set.insert cid)                     $ st@@ -328,33 +364,33 @@           case st ^. incomingAt cid of             Just (_, ToProc weakQueue) -> do               mQueue <- deRefWeak weakQueue-              forM_ mQueue $ \queue -> do +              forM_ mQueue $ \queue -> do                 -- TODO: if we find that the queue is Nothing, should we remove                 -- it from the NC state? (and same for channels, below)                 let msg = payloadToMessage payload                 enqueue queue msg -- 'enqueue' is strict-              go st +              go st             Just (_, ToChan (TypedChannel chan')) -> do               mChan <- deRefWeak chan'               -- If mChan is Nothing, the process has given up the read end of               -- the channel and we simply ignore the incoming message-              forM_ mChan $ \chan -> atomically $  +              forM_ mChan $ \chan -> atomically $                 -- We make sure the message is fully decoded when it is enqueued                 writeTQueue chan $! decode (BSL.fromChunks payload)-              go st +              go st             Just (_, ToNode) -> do               let ctrlMsg = decode . BSL.fromChunks $ payload               writeChan ctrlChan $! ctrlMsg-              go st -            Just (src, Uninit) -> +              go st+            Just (src, Uninit) ->               case decode (BSL.fromChunks payload) of                 ProcessIdentifier pid -> do                   let lpid = processLocalId pid-                  mProc <- withMVar state $ return . (^. localProcessWithId lpid) +                  mProc <- withMVar state $ return . (^. localProcessWithId lpid)                   case mProc of-                    Just proc -> -                      go (incomingAt cid ^= Just (src, ToProc (processWeakQ proc)) $ st) -                    Nothing -> +                    Just proc ->+                      go (incomingAt cid ^= Just (src, ToProc (processWeakQ proc)) $ st)+                    Nothing ->                       invalidRequest cid st                 SendPortIdentifier chId -> do                   let lcid = sendPortLocalId chId@@ -364,7 +400,7 @@                     Just proc -> do                       mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)                       case mChannel of-                        Just channel -> +                        Just channel ->                           go (incomingAt cid ^= Just (src, ToChan channel) $ st)                         Nothing ->                           invalidRequest cid st@@ -373,50 +409,55 @@                 NodeIdentifier nid ->                   if nid == localNodeId node                     then go (incomingAt cid ^= Just (src, ToNode) $ st)-                    else invalidRequest cid st +                    else invalidRequest cid st             Nothing ->               invalidRequest cid st-        NT.ConnectionClosed cid -> +        NT.ConnectionClosed cid ->           case st ^. incomingAt cid of-            Nothing -> +            Nothing ->               invalidRequest cid st-            Just (src, _) -> +            Just (src, _) ->               go ( (incomingAt cid ^= Nothing)                  . (incomingFrom src ^: Set.delete cid)                  $ st                  )-        NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do +        NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do           -- [Unified table 9, rule node_disconnect]           let nid = NodeIdentifier $ NodeId theirAddr-          writeChan ctrlChan NCMsg +          writeChan ctrlChan NCMsg             { ctrlMsgSender = nid             , ctrlMsgSignal = Died nid DiedDisconnect             }           let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))-          closeImplicitReconnections node nid +          closeImplicitReconnections node nid           go ( (incomingFrom theirAddr ^= Set.empty)              . (incoming ^: Map.filterWithKey (const . notLost))-             $ st +             $ st              )         NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->-          fail $ "Cloud Haskell fatal error: end point failed: " ++ str +          fatal $ "Cloud Haskell fatal error: end point failed: " ++ str         NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->-          fail $ "Cloud Haskell fatal error: transport failed: " ++ str +          fatal $ "Cloud Haskell fatal error: transport failed: " ++ str         NT.EndPointClosed ->-          return ()+          stopTracer (localTracer node) >> return ()         NT.ReceivedMulticast _ _ ->           -- If we received a multicast message, something went horribly wrong           -- and we just give up-          fail "Cloud Haskell fatal error: received unexpected multicast"+          fatal "Cloud Haskell fatal error: received unexpected multicast" +    fatal :: String -> IO ()+    fatal msg = stopTracer (localTracer node) >> fail msg+     invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()-    invalidRequest cid st = +    invalidRequest cid st = do       -- TODO: We should treat this as a fatal error on the part of the remote       -- node. That is, we should report the remote node as having died, and we       -- should close incoming connections (this requires a Transport layer       -- extension).+      traceEventFmtIO node "" [(TraceStr "[network] invalid request: "),+                               (Trace cid)]       go ( incomingAt cid ^= Nothing-         $ st +         $ st          )      state    = localState node@@ -435,8 +476,8 @@ -- Internal data types                                                        -- -------------------------------------------------------------------------------- -data NCState = NCState -  {  -- Mapping from remote processes to linked local processes +data NCState = NCState+  {  -- Mapping from remote processes to linked local processes     _links    :: !(Map Identifier (Set ProcessId))      -- Mapping from remote processes to monitoring local processes   , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))@@ -455,26 +496,66 @@                       , _registeredOnNodes = Map.empty                       } +-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This+-- type is deliberately not exported so it cannot be caught explicitly.+data ProcessKillException =+    ProcessKillException !ProcessId !String+  deriving (Typeable)++instance Exception ProcessKillException+instance Show ProcessKillException where+  show (ProcessKillException pid reason) =+    "killed-by=" ++ show pid ++ ",reason=" ++ reason+ --------------------------------------------------------------------------------+-- Tracing/Debugging                                                          --+--------------------------------------------------------------------------------++-- [Issue #104]++traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()+traceNotifyDied node ident reason =+  case reason of+    DiedNormal -> return ()+    _ -> traceNcEventFmt node " "+                         [(TraceStr "[node-controller]"),+                          (Trace ident),+                          (Trace reason)]++traceNcEventFmt :: LocalNode -> String -> [TraceArg] -> NC ()+traceNcEventFmt node fmt args =+  liftIO $ traceEventFmtIO node fmt args++traceEventFmtIO :: LocalNode+                -> String+                -> [TraceArg]+                -> IO ()+traceEventFmtIO node fmt args =+  withLocalTracer node $ \t -> traceFormat t fmt args++withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()+withLocalTracer node act = act (localTracer node)++-------------------------------------------------------------------------------- -- Core functionality                                                         -- --------------------------------------------------------------------------------  -- [Unified: Table 7] nodeController :: NC () nodeController = do-  node <- ask +  node <- ask   forever' $ do     msg  <- liftIO $ readChan (localCtrlChan node) -    -- [Unified: Table 7, rule nc_forward] +    -- [Unified: Table 7, rule nc_forward]     case destNid (ctrlMsgSignal msg) of-      Just nid' | nid' /= localNodeId node -> +      Just nid' | nid' /= localNodeId node ->         liftIO $ sendBinary node                             (ctrlMsgSender msg)-                            (NodeIdentifier nid') +                            (NodeIdentifier nid')                             WithImplicitReconnect                             msg-      _ -> +      _ ->         return ()      case msg of@@ -483,7 +564,7 @@       NCMsg (ProcessIdentifier from) (Monitor ref) ->         ncEffectMonitor from (monitorRefIdent ref) (Just ref)       NCMsg (ProcessIdentifier from) (Unlink them) ->-        ncEffectUnlink from them +        ncEffectUnlink from them       NCMsg (ProcessIdentifier from) (Unmonitor ref) ->         ncEffectUnmonitor from ref       NCMsg _from (Died ident reason) ->@@ -496,27 +577,33 @@         ncEffectWhereIs from label       NCMsg from (NamedSend label msg') ->         ncEffectNamedSend from label msg'+      NCMsg (ProcessIdentifier from) (Kill to reason) ->+        ncEffectKill from to reason+      NCMsg (ProcessIdentifier from) (Exit to reason) ->+        ncEffectExit from to reason+      NCMsg (ProcessIdentifier from) (GetInfo pid) ->+        ncEffectGetInfo from pid       unexpected ->         error $ "nodeController: unexpected message " ++ show unexpected  -- [Unified: Table 10]-ncEffectMonitor :: ProcessId        -- ^ Who's watching? +ncEffectMonitor :: ProcessId        -- ^ Who's watching?                 -> Identifier       -- ^ Who's being watched?                 -> Maybe MonitorRef -- ^ 'Nothing' to link                 -> NC () ncEffectMonitor from them mRef = do-  node <- ask -  shouldLink <- -    if not (isLocal node them) +  node <- ask+  shouldLink <-+    if not (isLocal node them)       then return True       else isValidLocalIdentifier them   case (shouldLink, isLocal node (ProcessIdentifier from)) of     (True, _) ->  -- [Unified: first rule]       case mRef of         Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)-        Nothing  -> modify' $ linksFor them ^: Set.insert from +        Nothing  -> modify' $ linksFor them ^: Set.insert from     (False, True) -> -- [Unified: second rule]-      notifyDied from them DiedUnknownId mRef +      notifyDied from them DiedUnknownId mRef     (False, False) -> -- [Unified: third rule]       -- TODO: this is the right sender according to the Unified semantics,       -- but perhaps having 'them' as the sender would make more sense@@ -525,7 +612,7 @@                           (NodeIdentifier $ localNodeId node)                           (NodeIdentifier $ processNodeId from)                           WithImplicitReconnect-        NCMsg  +        NCMsg           { ctrlMsgSender = NodeIdentifier (localNodeId node)           , ctrlMsgSignal = Died them DiedUnknownId           }@@ -533,22 +620,22 @@ -- [Unified: Table 11] ncEffectUnlink :: ProcessId -> Identifier -> NC () ncEffectUnlink from them = do-  node <- ask -  when (isLocal node (ProcessIdentifier from)) $ +  node <- ask+  when (isLocal node (ProcessIdentifier from)) $     case them of-      ProcessIdentifier pid -> -        postAsMessage from $ DidUnlinkProcess pid -      NodeIdentifier nid -> +      ProcessIdentifier pid ->+        postAsMessage from $ DidUnlinkProcess pid+      NodeIdentifier nid ->         postAsMessage from $ DidUnlinkNode nid-      SendPortIdentifier cid -> -        postAsMessage from $ DidUnlinkPort cid +      SendPortIdentifier cid ->+        postAsMessage from $ DidUnlinkPort cid   modify' $ linksFor them ^: Set.delete from  -- [Unified: Table 11] ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC () ncEffectUnmonitor from ref = do-  node <- ask -  when (isLocal node (ProcessIdentifier from)) $ +  node <- ask+  when (isLocal node (ProcessIdentifier from)) $     postAsMessage from $ DidUnmonitor ref   modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref) @@ -556,6 +643,7 @@ ncEffectDied :: Identifier -> DiedReason -> NC () ncEffectDied ident reason = do   node <- ask+  traceNotifyDied node ident reason   (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))   (affectedMons,  unaffectedMons)  <- gets (splitNotif ident . (^. monitors)) @@ -563,41 +651,40 @@    let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False -  forM_ (Map.toList affectedLinks) $ \(them, uss) -> +  forM_ (Map.toList affectedLinks) $ \(them, uss) ->     forM_ uss $ \us ->-      when (localOnly <= isLocal node (ProcessIdentifier us)) $ +      when (localOnly <= isLocal node (ProcessIdentifier us)) $         notifyDied us them reason Nothing    forM_ (Map.toList affectedMons) $ \(them, refs) ->     forM_ refs $ \(us, ref) ->       when (localOnly <= isLocal node (ProcessIdentifier us)) $-        notifyDied us them reason (Just ref)   +        notifyDied us them reason (Just ref)    modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)    modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)    remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=-      mapM (\(pid,nidlist) -> +      mapM (\(pid,nidlist) ->         case ident `impliesDeathOf` ProcessIdentifier pid of-           True -> +           True ->               do forM_ nidlist $ \(nid,_) ->-                   when (not $ isLocal node (NodeIdentifier nid)) +                   when (not $ isLocal node (NodeIdentifier nid))                       (forwardNameDeath node nid)                  return Nothing            False -> return $ Just (pid,nidlist)  )   modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))-    where +    where        forwardNameDeath node nid =                    liftIO $ sendBinary node                              (NodeIdentifier $ localNodeId node)                              (NodeIdentifier $ nid)                              WithImplicitReconnect                              NCMsg-                             { ctrlMsgSender = NodeIdentifier (localNodeId node) +                             { ctrlMsgSender = NodeIdentifier (localNodeId node)                              , ctrlMsgSignal = Died ident reason-                             }              -+                             }  -- [Unified: Table 13] ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()@@ -612,9 +699,9 @@   pid' <- liftIO $ forkProcess node proc   liftIO $ sendMessage node                        (NodeIdentifier (localNodeId node))-                       (ProcessIdentifier pid) +                       (ProcessIdentifier pid)                        WithImplicitReconnect-                       (DidSpawn ref pid') +                       (DidSpawn ref pid')  -- Unified semantics does not explicitly describe how to implement 'register', -- but mentions it's "very similar to nsend" (Table 14)@@ -629,7 +716,7 @@            return $ isJust currentVal          Just thepid -> -- register request            do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)-              return $ (isNothing currentVal /= reregistration) && +              return $ (isNothing currentVal /= reregistration) &&                 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )   if isLocal node (NodeIdentifier atnode)      then do when (isOk) $@@ -637,10 +724,10 @@                   updateRemote node currentVal mPid              liftIO $ sendMessage node                        (NodeIdentifier (localNodeId node))-                       (ProcessIdentifier from) +                       (ProcessIdentifier from)                        WithImplicitReconnect                        (RegisterReply label isOk)-     else let operation = +     else let operation =                  case reregistration of                     True -> flip decList                     False -> flip incList@@ -666,7 +753,7 @@             decList ((atag,1):xs) tag | atag == tag = xs             decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs             decList (x:xs) tag = x:decList xs tag-            forward node to reg = +            forward node to reg =               when (not $ isLocal node (NodeIdentifier to)) $                     liftIO $ sendBinary node                                         (ProcessIdentifier from)@@ -685,23 +772,84 @@   mPid <- gets (^. registeredHereFor label)   liftIO $ sendMessage node                        (NodeIdentifier (localNodeId node))-                       (ProcessIdentifier from) +                       (ProcessIdentifier from)                        WithImplicitReconnect                        (WhereIsReply label mPid)  -- [Unified: Table 14] ncEffectNamedSend :: Identifier -> String -> Message -> NC () ncEffectNamedSend from label msg = do-  node <- ask  +  node <- ask   mPid <- gets (^. registeredHereFor label)   -- If mPid is Nothing, we just ignore the named send (as per Table 14)-  forM_ mPid $ \pid -> +  forM_ mPid $ \pid ->     liftIO $ sendPayload node                          from-                         (ProcessIdentifier pid) +                         (ProcessIdentifier pid)                          NoImplicitReconnect-                         (messageToPayload msg) +                         (messageToPayload msg) +-- [Issue #69]+ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()+ncEffectKill from to reason = do+  node <- ask+  when (isLocal node (ProcessIdentifier to)) $+    throwException to $ ProcessKillException from reason++-- [Issue #69]+ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()+ncEffectExit from to reason = do+  node <- ask+  when (isLocal node (ProcessIdentifier to)) $+    throwException to $ ProcessExitException from reason++-- [Issue #89]+ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()+ncEffectGetInfo from pid =+  let lpid = processLocalId pid+      them = (ProcessIdentifier pid)+  in do+  node <- ask+  mProc <- liftIO $+            withMVar (localState node) $ return . (^. localProcessWithId lpid)+  case mProc of+    Nothing   -> dispatch (isLocal node (ProcessIdentifier from))+                          from node (ProcessInfoNone DiedUnknownId)+    Just _    -> do+      itsLinks    <- gets (^. linksFor    them)+      itsMons     <- gets (^. monitorsFor them)+      registered  <- gets (^. registeredHere)++      let reg = registeredNames registered+      dispatch (isLocal node (ProcessIdentifier from))+               from+               node+               ProcessInfo {+                   infoNode               = (processNodeId pid)+                 , infoRegisteredNames    = reg+                   -- we cannot populate this field+                 , infoMessageQueueLength = Nothing+                 , infoMonitors       = Set.toList itsMons+                 , infoLinks          = Set.toList itsLinks+                 }+  where dispatch :: (Serializable a, Show a)+                 => Bool+                 -> ProcessId+                 -> LocalNode+                 -> a+                 -> NC ()+        dispatch True  dest _    pInfo = postAsMessage dest $ pInfo+        dispatch False dest node pInfo = do+            liftIO $ sendMessage node+                                 (NodeIdentifier (localNodeId node))+                                 (ProcessIdentifier dest)+                                 WithImplicitReconnect+                                 pInfo++        registeredNames = Map.foldlWithKey (\ks k v -> if v == pid+                                                 then (k:ks)+                                                 else ks) []+ -------------------------------------------------------------------------------- -- Auxiliary                                                                  -- --------------------------------------------------------------------------------@@ -712,20 +860,20 @@            -> Maybe MonitorRef  -- ^ 'Nothing' for linking            -> NC () notifyDied dest src reason mRef = do-  node <- ask +  node <- ask   case (isLocal node (ProcessIdentifier dest), mRef, src) of     (True, Just ref, ProcessIdentifier pid) ->-      postAsMessage dest $ ProcessMonitorNotification ref pid reason +      postAsMessage dest $ ProcessMonitorNotification ref pid reason     (True, Just ref, NodeIdentifier nid) ->       postAsMessage dest $ NodeMonitorNotification ref nid reason     (True, Just ref, SendPortIdentifier cid) ->       postAsMessage dest $ PortMonitorNotification ref cid reason     (True, Nothing, ProcessIdentifier pid) ->-      throwException dest $ ProcessLinkException pid reason +      throwException dest $ ProcessLinkException pid reason     (True, Nothing, NodeIdentifier pid) ->-      throwException dest $ NodeLinkException pid reason +      throwException dest $ NodeLinkException pid reason     (True, Nothing, SendPortIdentifier pid) ->-      throwException dest $ PortLinkException pid reason +      throwException dest $ PortLinkException pid reason     (False, _, _) ->       -- The change in sender comes from [Unified: Table 10]       liftIO $ sendBinary node@@ -733,30 +881,33 @@                           (NodeIdentifier $ processNodeId dest)                           WithImplicitReconnect         NCMsg-          { ctrlMsgSender = NodeIdentifier (localNodeId node) +          { ctrlMsgSender = NodeIdentifier (localNodeId node)           , ctrlMsgSignal = Died src reason           }-      + -- | [Unified: Table 8] destNid :: ProcessSignal -> Maybe NodeId-destNid (Link ident)    = Just $ nodeOf ident-destNid (Unlink ident)  = Just $ nodeOf ident-destNid (Monitor ref)   = Just $ nodeOf (monitorRefIdent ref)-destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)-destNid (Spawn _ _)     = Nothing -destNid (Register _ _ _ _) = Nothing-destNid (WhereIs _)     = Nothing-destNid (NamedSend _ _) = Nothing+destNid (Link ident)        = Just $ nodeOf ident+destNid (Unlink ident)      = Just $ nodeOf ident+destNid (Monitor ref)       = Just $ nodeOf (monitorRefIdent ref)+destNid (Unmonitor ref)     = Just $ nodeOf (monitorRefIdent ref)+destNid (Spawn _ _)         = Nothing+destNid (Register _ _ _ _)  = Nothing+destNid (WhereIs _)         = Nothing+destNid (NamedSend _ _)     = Nothing -- We don't need to forward 'Died' signals; if monitoring/linking is setup, -- then when a local process dies the monitoring/linking machinery will take -- care of notifying remote nodes-destNid (Died _ _) = Nothing +destNid (Died _ _)          = Nothing+destNid (Kill pid _)        = Just $ processNodeId pid+destNid (Exit pid _)        = Just $ processNodeId pid+destNid (GetInfo pid)       = Just $ processNodeId pid  -- | Check if a process is local to our own node-isLocal :: LocalNode -> Identifier -> Bool +isLocal :: LocalNode -> Identifier -> Bool isLocal nid ident = nodeOf ident == localNodeId nid --- | Lookup a local closure +-- | Lookup a local closure unClosure :: Typeable a => Closure a -> NC (Either String a) unClosure closure = do   rtable <- remoteTable <$> ask@@ -766,13 +917,13 @@ isValidLocalIdentifier :: Identifier -> NC Bool isValidLocalIdentifier ident = do   node <- ask-  liftIO . withMVar (localState node) $ \nSt -> +  liftIO . withMVar (localState node) $ \nSt ->     case ident of       NodeIdentifier nid ->         return $ nid == localNodeId node       ProcessIdentifier pid -> do         let mProc = nSt ^. localProcessWithId (processLocalId pid)-        return $ isJust mProc +        return $ isJust mProc       SendPortIdentifier cid -> do         let pid   = sendPortProcessId cid             mProc = nSt ^. localProcessWithId (processLocalId pid)@@ -787,24 +938,25 @@ --------------------------------------------------------------------------------  postAsMessage :: Serializable a => ProcessId -> a -> NC ()-postAsMessage pid = postMessage pid . createMessage  +postAsMessage pid = postMessage pid . createMessage  postMessage :: ProcessId -> Message -> NC ()-postMessage pid msg = withLocalProc pid $ \p -> enqueue (processQueue p) msg+postMessage pid msg = do+  withLocalProc pid $ \p -> enqueue (processQueue p) msg  throwException :: Exception e => ProcessId -> e -> NC ()-throwException pid e = withLocalProc pid $ \p -> +throwException pid e = withLocalProc pid $ \p ->   throwTo (processThread p) e -withLocalProc :: ProcessId -> (LocalProcess -> IO ()) -> NC () +withLocalProc :: ProcessId -> (LocalProcess -> IO ()) -> NC () withLocalProc pid p = do-  node <- ask -  liftIO $ do +  node <- ask+  liftIO $ do     -- By [Unified: table 6, rule missing_process] messages to dead processes     -- can silently be dropped     let lpid = processLocalId pid     mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)-    forM_ mProc p +    forM_ mProc p  -------------------------------------------------------------------------------- -- Accessors                                                                  --@@ -841,23 +993,23 @@ -- There is a hierarchy between identifiers: failure of a node implies failure -- of all processes on that node, and failure of a process implies failure of -- all typed channels to that process. In other words, if 'ident' refers to a--- node, then the /should trigger/ set will include +-- node, then the /should trigger/ set will include -- -- * the notifications for the node specifically--- * the notifications for processes on that node, and --- * the notifications for typed channels to processes on that node. +-- * the notifications for processes on that node, and+-- * the notifications for typed channels to processes on that node. -- -- Similarly, if 'ident' refers to a process, the /should trigger/ set will--- include +-- include ----- * the notifications for that process specifically and +-- * the notifications for that process specifically and -- * the notifications for typed channels to that process. -- -- See https://github.com/haskell/containers/issues/14 for the bang on _v. splitNotif :: Identifier            -> Map Identifier a            -> (Map Identifier a, Map Identifier a)-splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k) +splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)  -------------------------------------------------------------------------------- -- Auxiliary                                                                  --
src/Control/Distributed/Process/Serializable.hs view
@@ -1,4 +1,4 @@-module Control.Distributed.Process.Serializable +module Control.Distributed.Process.Serializable   ( Serializable   , encodeFingerprint   , decodeFingerprint@@ -35,29 +35,29 @@  -- | Encode type representation as a bytestring encodeFingerprint :: Fingerprint -> ByteString-encodeFingerprint fp = +encodeFingerprint fp =   -- Since all CH nodes will run precisely the same binary, we don't have to   -- worry about cross-arch issues here (like endianness)-  BSI.unsafeCreate sizeOfFingerprint $ \p -> pokeByteOff p 0 fp +  BSI.unsafeCreate sizeOfFingerprint $ \p -> pokeByteOff p 0 fp --- | Decode a bytestring into a fingerprint. Throws an IO exception on failure +-- | Decode a bytestring into a fingerprint. Throws an IO exception on failure decodeFingerprint :: ByteString -> Fingerprint decodeFingerprint bs-  | BS.length bs /= sizeOfFingerprint = +  | BS.length bs /= sizeOfFingerprint =       throw $ userError "decodeFingerprint: Invalid length"   | otherwise = BSI.inlinePerformIO $ do       let (fp, offset, _) = BSI.toForeignPtr bs-      withForeignPtr fp $ \p -> peekByteOff p offset +      withForeignPtr fp $ \p -> peekByteOff p offset  -- | Size of a fingerprint sizeOfFingerprint :: Int sizeOfFingerprint = sizeOf (undefined :: Fingerprint) --- | The fingerprint of the typeRep of the argument +-- | The fingerprint of the typeRep of the argument fingerprint :: Typeable a => a -> Fingerprint fingerprint a = let TypeRep fp _ _ = typeOf a in fp  -- | Show fingerprint (for debugging purposes) showFingerprint :: Fingerprint -> ShowS-showFingerprint (Fingerprint hi lo) = +showFingerprint (Fingerprint hi lo) =   showString "(" . showHex hi . showString "," . showHex lo . showString ")"
tests/TestCH.hs view
@@ -1,15 +1,14 @@-module Main where +module Main where  #if ! MIN_VERSION_base(4,6,0) import Prelude hiding (catch) #endif - import Data.Binary (Binary(..)) import Data.Typeable (Typeable) import Data.Foldable (forM_) import Control.Concurrent (forkIO, threadDelay, myThreadId, throwTo, ThreadId)-import Control.Concurrent.MVar +import Control.Concurrent.MVar   ( MVar   , newEmptyMVar   , putMVar@@ -22,16 +21,17 @@ import Control.Applicative ((<$>), (<*>), pure, (<|>)) import qualified Network.Transport as NT (Transport, closeEndPoint) import Network.Socket (sClose)-import Network.Transport.TCP +import Network.Transport.TCP   ( createTransportExposeInternals   , TransportInternals(socketBetween)   , defaultTCPParameters   ) import Control.Distributed.Process-import Control.Distributed.Process.Internal.Types -  ( NodeId(nodeAddress) +import Control.Distributed.Process.Internal.Types+  ( NodeId(nodeAddress)   , LocalNode(localEndPoint)-  , RegisterReply(..)+  , ProcessExitException(..)+  , nullProcessId   ) import Control.Distributed.Process.Node import Control.Distributed.Process.Serializable (Serializable)@@ -51,7 +51,7 @@ --------------------------------------------------------------------------------  -- | Like fork, but throw exceptions in the child thread to the parent-forkTry :: IO () -> IO ThreadId +forkTry :: IO () -> IO ThreadId forkTry p = do   tid <- myThreadId   forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))@@ -71,7 +71,7 @@   WhereIsReply _ mPid <- expect   return mPid -data Add       = Add    ProcessId Double Double deriving (Typeable) +data Add       = Add    ProcessId Double Double deriving (Typeable) data Divide    = Divide ProcessId Double Double deriving (Typeable) data DivByZero = DivByZero deriving (Typeable) @@ -102,7 +102,7 @@ monitorOrLink :: Bool            -- ^ 'True' for monitor, 'False' for link               -> ProcessId       -- Process to monitor/link to               -> Maybe (MVar ()) -- MVar to signal on once the monitor has been set up-              -> Process (Maybe MonitorRef) +              -> Process (Maybe MonitorRef) monitorOrLink mOrL pid mSignal = do   result <- if mOrL then Just <$> monitor pid                     else link pid >> return Nothing@@ -116,11 +116,11 @@                    -> Bool            -- 'True' for monitor, 'False' for link                    -> Bool            -- Should we unmonitor?                    -> DiedReason      -- Expected cause of death-                   -> Maybe (MVar ()) -- Signal for 'monitor set up' +                   -> Maybe (MVar ()) -- Signal for 'monitor set up'                    -> MVar ()         -- Signal for successful termination                    -> Process ()-monitorTestProcess theirAddr mOrL un reason monitorSetup done = -  catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup +monitorTestProcess theirAddr mOrL un reason monitorSetup done =+  catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup             case (un, mRef) of               (True, Nothing) -> do                 unlink theirAddr@@ -137,13 +137,13 @@             True <- return $ pid == theirAddr && not mOrL && not un && reason == reason'             liftIO $ putMVar done ()         )-  + -------------------------------------------------------------------------------- -- The tests proper                                                           -- --------------------------------------------------------------------------------  -- | Basic ping test-testPing :: NT.Transport -> Assertion +testPing :: NT.Transport -> Assertion testPing transport = do   serverAddr <- newEmptyMVar   clientDone <- newEmptyMVar@@ -172,15 +172,15 @@    takeMVar clientDone --- | Monitor a process on an unreachable node -testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> Assertion +-- | Monitor a process on an unreachable node+testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> Assertion testMonitorUnreachable transport mOrL un = do   deadProcess <- newEmptyMVar   done <- newEmptyMVar    forkIO $ do     localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode . liftIO $ threadDelay 1000000 +    addr <- forkProcess localNode . liftIO $ threadDelay 1000000     closeLocalNode localNode     putMVar deadProcess addr @@ -188,12 +188,12 @@     localNode <- newLocalNode transport initRemoteTable     theirAddr <- readMVar deadProcess     runProcess localNode $-      monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done +      monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done    takeMVar done  -- | Monitor a process which terminates normally-testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> Assertion +testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> Assertion testMonitorNormalTermination transport mOrL un = do   monitorSetup <- newEmptyMVar   monitoredProcess <- newEmptyMVar@@ -201,20 +201,20 @@    forkIO $ do     localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode $ +    addr <- forkProcess localNode $       liftIO $ readMVar monitorSetup     putMVar monitoredProcess addr    forkIO $ do     localNode <- newLocalNode transport initRemoteTable     theirAddr <- readMVar monitoredProcess-    runProcess localNode $ +    runProcess localNode $       monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done    takeMVar done  -- | Monitor a process which terminates abnormally-testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> Assertion +testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> Assertion testMonitorAbnormalTermination transport mOrL un = do   monitorSetup <- newEmptyMVar   monitoredProcess <- newEmptyMVar@@ -226,19 +226,19 @@     localNode <- newLocalNode transport initRemoteTable     addr <- forkProcess localNode . liftIO $ do       readMVar monitorSetup-      throwIO err +      throwIO err     putMVar monitoredProcess addr    forkIO $ do     localNode <- newLocalNode transport initRemoteTable     theirAddr <- readMVar monitoredProcess-    runProcess localNode $ +    runProcess localNode $       monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done    takeMVar done-    + -- | Monitor a local process that is already dead-testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion +testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion testMonitorLocalDeadProcess transport mOrL un = do   processDead <- newEmptyMVar   processAddr <- newEmptyMVar@@ -258,7 +258,7 @@   takeMVar done  -- | Monitor a remote process that is already dead-testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion +testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion testMonitorRemoteDeadProcess transport mOrL un = do   processDead <- newEmptyMVar   processAddr <- newEmptyMVar@@ -279,7 +279,7 @@   takeMVar done  -- | Monitor a process that becomes disconnected-testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> Assertion +testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> Assertion testMonitorDisconnect transport mOrL un = do   processAddr <- newEmptyMVar   monitorSetup <- newEmptyMVar@@ -287,7 +287,7 @@    forkIO $ do     localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode . liftIO $ threadDelay 1000000 +    addr <- forkProcess localNode . liftIO $ threadDelay 1000000     putMVar processAddr addr     readMVar monitorSetup     NT.closeEndPoint (localEndPoint localNode)@@ -297,18 +297,18 @@     theirAddr <- readMVar processAddr     runProcess localNode $ do       monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done-  +   takeMVar done  -- | Test the math server (i.e., receiveWait)-testMath :: NT.Transport -> Assertion +testMath :: NT.Transport -> Assertion testMath transport = do-  serverAddr <- newEmptyMVar +  serverAddr <- newEmptyMVar   clientDone <- newEmptyMVar    -- Server   forkIO $ do-    localNode <- newLocalNode transport initRemoteTable +    localNode <- newLocalNode transport initRemoteTable     addr <- forkProcess localNode math     putMVar serverAddr addr @@ -320,7 +320,7 @@     runProcess localNode $ do       pid <- getSelfPid       send mathServer (Add pid 1 2)-      3 <- expect :: Process Double  +      3 <- expect :: Process Double       send mathServer (Divide pid 8 2)       4 <- expect :: Process Double       send mathServer (Divide pid 8 0)@@ -330,15 +330,15 @@   takeMVar clientDone  -- | Send first message (i.e. connect) to an already terminated process--- (without monitoring); then send another message to a second process on +-- (without monitoring); then send another message to a second process on -- the same remote node (we're checking that the remote node did not die)-testSendToTerminated :: NT.Transport -> Assertion +testSendToTerminated :: NT.Transport -> Assertion testSendToTerminated transport = do   serverAddr1 <- newEmptyMVar   serverAddr2 <- newEmptyMVar   clientDone <- newEmptyMVar -  forkIO $ do +  forkIO $ do     terminated <- newEmptyMVar     localNode <- newLocalNode transport initRemoteTable     addr1 <- forkProcess localNode $ liftIO $ putMVar terminated ()@@ -356,34 +356,38 @@       send server1 "Hi"       send server2 (Pong pid)       Ping pid' <- expect-      True <- return $ pid' == server2 +      True <- return $ pid' == server2       liftIO $ putMVar clientDone ()    takeMVar clientDone  -- | Test (non-zero) timeout-testTimeout :: NT.Transport -> Assertion +testTimeout :: NT.Transport -> Assertion testTimeout transport = do   localNode <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar+   runProcess localNode $ do     Nothing <- receiveTimeout 1000000 [match (\(Add _ _ _) -> return ())]-    return ()+    liftIO $ putMVar done () +  takeMVar done+ -- | Test zero timeout-testTimeout0 :: NT.Transport -> Assertion +testTimeout0 :: NT.Transport -> Assertion testTimeout0 transport = do   serverAddr <- newEmptyMVar   clientDone <- newEmptyMVar   messagesSent <- newEmptyMVar -  forkIO $ do +  forkIO $ do     localNode <- newLocalNode transport initRemoteTable     addr <- forkProcess localNode $ do       liftIO $ readMVar messagesSent >> threadDelay 1000000       -- Variation on the venerable ping server which uses a zero timeout       -- Since we wait for all messages to be sent before doing this receive,       -- we should nevertheless find the right message immediately-      Just partner <- receiveTimeout 0 [match (\(Pong partner) -> return partner)] +      Just partner <- receiveTimeout 0 [match (\(Pong partner) -> return partner)]       self <- getSelfPid       send partner (Ping self)     putMVar serverAddr addr@@ -398,13 +402,13 @@       replicateM_ 10000 $ send server "Irrelevant message"       send server (Pong pid)       liftIO $ putMVar messagesSent ()-      Ping _ <- expect +      Ping _ <- expect       liftIO $ putMVar clientDone ()    takeMVar clientDone  -- | Test typed channels-testTypedChannels :: NT.Transport -> Assertion +testTypedChannels :: NT.Transport -> Assertion testTypedChannels transport = do   serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))   clientDone <- newEmptyMVar@@ -413,7 +417,7 @@     localNode <- newLocalNode transport initRemoteTable     forkProcess localNode $ do       (serverSendPort, rport) <- newChan-      liftIO $ putMVar serverChannel serverSendPort +      liftIO $ putMVar serverChannel serverSendPort       (clientSendPort, i) <- receiveChan rport       sendChan clientSendPort (even i)     return ()@@ -423,14 +427,14 @@     serverSendPort <- readMVar serverChannel     runProcess localNode $ do       (clientSendPort, rport) <- newChan-      sendChan serverSendPort (clientSendPort, 5) +      sendChan serverSendPort (clientSendPort, 5)       False <- receiveChan rport       liftIO $ putMVar clientDone ()-      -  takeMVar clientDone  +  takeMVar clientDone+ -- | Test merging receive ports-testMergeChannels :: NT.Transport -> Assertion +testMergeChannels :: NT.Transport -> Assertion testMergeChannels transport = do     localNode <- newLocalNode transport initRemoteTable     testFlat localNode True          "aaabbbccc"@@ -443,13 +447,13 @@     testBlocked localNode False   where     -- Single layer of merging-    testFlat :: LocalNode -> Bool -> String -> IO () +    testFlat :: LocalNode -> Bool -> String -> IO ()     testFlat localNode biased expected = do       done <- newEmptyMVar       forkProcess localNode $ do-        rs  <- mapM charChannel "abc" -        m   <- mergePorts biased rs -        xs  <- replicateM 9 $ receiveChan m +        rs  <- mapM charChannel "abc"+        m   <- mergePorts biased rs+        xs  <- replicateM 9 $ receiveChan m         True <- return $ xs == expected         liftIO $ putMVar done ()       takeMVar done@@ -462,7 +466,7 @@         rss  <- mapM (mapM charChannel) ["abc", "def", "ghi"]         ms   <- mapM (mergePorts biasedInner) rss         m    <- mergePorts biasedOuter ms-        xs   <- replicateM (9 * 3) $ receiveChan m +        xs   <- replicateM (9 * 3) $ receiveChan m         True <- return $ xs == expected         liftIO $ putMVar done ()       takeMVar done@@ -470,12 +474,12 @@     -- Test that if no messages are (immediately) available, the scheduler makes no difference     testBlocked :: LocalNode -> Bool -> IO ()     testBlocked localNode biased = do-      vs <- replicateM 3 newEmptyMVar +      vs <- replicateM 3 newEmptyMVar       done <- newEmptyMVar        forkProcess localNode $ do-        [sa, sb, sc] <- liftIO $ mapM readMVar vs -        mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan) +        [sa, sb, sc] <- liftIO $ mapM readMVar vs+        mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan)           [ -- a, b, c             (sa, 'a')           , (sb, 'b')@@ -504,8 +508,8 @@        forkProcess localNode $ do         (ss, rs) <- unzip <$> replicateM 3 newChan-        liftIO $ mapM_ (uncurry putMVar) $ zip vs ss -        m  <- mergePorts biased rs +        liftIO $ mapM_ (uncurry putMVar) $ zip vs ss+        m  <- mergePorts biased rs         xs <- replicateM (6 * 3) $ receiveChan m         True <- return $ xs == "abcacbbacbcacabcba"         liftIO $ putMVar done ()@@ -514,18 +518,19 @@      mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)     mergePorts True  = mergePortsBiased-    mergePorts False = mergePortsRR +    mergePorts False = mergePortsRR      charChannel :: Char -> Process (ReceivePort Char)     charChannel c = do       (sport, rport) <- newChan-      replicateM_ 3 $ sendChan sport c +      replicateM_ 3 $ sendChan sport c       liftIO $ threadDelay 10000 -- Make sure messages have been sent       return rport -testTerminate :: NT.Transport -> Assertion +testTerminate :: NT.Transport -> Assertion testTerminate transport = do   localNode <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar    pid <- forkProcess localNode $ do     liftIO $ threadDelay 100000@@ -534,12 +539,15 @@   runProcess localNode $ do     ref <- monitor pid     ProcessMonitorNotification ref' pid' (DiedException ex) <- expect-    True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException -    return ()+    True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException+    liftIO $ putMVar done () -testMonitorNode :: NT.Transport -> Assertion +  takeMVar done++testMonitorNode :: NT.Transport -> Assertion testMonitorNode transport = do   [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable+  done <- newEmptyMVar    closeLocalNode node1 @@ -547,9 +555,11 @@     ref <- monitorNode (localNodeId node1)     NodeMonitorNotification ref' nid DiedDisconnect <- expect     True <- return $ ref == ref' && nid == localNodeId node1-    return ()+    liftIO $ putMVar done () -testMonitorChannel :: NT.Transport -> Assertion +  takeMVar done++testMonitorChannel :: NT.Transport -> Assertion testMonitorChannel transport = do     [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable     gotNotification <- newEmptyMVar@@ -558,7 +568,7 @@       sport <- expect :: Process (SendPort ())       ref <- monitorPort sport       PortMonitorNotification ref' port' reason <- expect-      -- reason might be DiedUnknownId if the receive port is GCed before the +      -- reason might be DiedUnknownId if the receive port is GCed before the       -- monitor is established (TODO: not sure that this is reasonable)       return $ ref' == ref && port' == sendPortId sport && (reason == DiedNormal || reason == DiedUnknownId)       liftIO $ putMVar gotNotification ()@@ -570,47 +580,54 @@      takeMVar gotNotification -testRegistry :: NT.Transport -> Assertion +testRegistry :: NT.Transport -> Assertion testRegistry transport = do   node <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar -  pingServer <- forkProcess node ping +  pingServer <- forkProcess node ping    runProcess node $ do     register "ping" pingServer     Just pid <- whereis "ping"-    True <- return $ pingServer == pid +    True <- return $ pingServer == pid     us <- getSelfPid     nsend "ping" (Pong us)-    Ping pid' <- expect +    Ping pid' <- expect     True <- return $ pingServer == pid'-    return ()+    liftIO $ putMVar done () -testRemoteRegistry :: NT.Transport -> Assertion +  takeMVar done++testRemoteRegistry :: NT.Transport -> Assertion testRemoteRegistry transport = do   node1 <- newLocalNode transport initRemoteTable   node2 <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar -  pingServer <- forkProcess node1 ping +  pingServer <- forkProcess node1 ping    runProcess node2 $ do     let nid1 = localNodeId node1     registerRemoteAsync nid1 "ping" pingServer-    receiveWait [ +    receiveWait [        matchIf (\(RegisterReply label' _) -> "ping" == label')                (\(RegisterReply _ _) -> return ()) ]      Just pid <- whereisRemote nid1 "ping"-    True <- return $ pingServer == pid +    True <- return $ pingServer == pid     us <- getSelfPid     nsendRemote nid1 "ping" (Pong us)-    Ping pid' <- expect +    Ping pid' <- expect     True <- return $ pingServer == pid'-    return ()+    liftIO $ putMVar done () -testSpawnLocal :: NT.Transport -> Assertion +  takeMVar done++testSpawnLocal :: NT.Transport -> Assertion testSpawnLocal transport = do   node <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar    runProcess node $ do     us <- getSelfPid@@ -624,9 +641,12 @@       send us ()      send pid sport-    expect+    () <- expect+    liftIO $ putMVar done () -testReconnect :: NT.Transport -> TransportInternals -> Assertion +  takeMVar done++testReconnect :: NT.Transport -> TransportInternals -> Assertion testReconnect transport transportInternals = do   [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable   let nid1 = localNodeId node1@@ -651,40 +671,40 @@     send them "message 1" >> liftIO (threadDelay 100000)      -- Simulate network failure-    liftIO $ do +    liftIO $ do       sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)       sClose sock       threadDelay 10000      -- Should not arrive-    send them "message 2" +    send them "message 2"      -- Should arrive     reconnect them-    send them "message 3" +    send them "message 3"      liftIO $ takeMVar sendTestOk      {-      - Test that there *is* implicit reconnect on node controller messages      -}-    +     us <- getSelfPid     registerRemoteAsync nid1 "a" us -- registerRemote is asynchronous     receiveWait [         matchIf (\(RegisterReply label' _) -> "a" == label')                 (\(RegisterReply _ _) -> return ()) ] -    Just _  <- whereisRemote nid1 "a" -      +    Just _  <- whereisRemote nid1 "a" +     -- Simulate network failure-    liftIO $ do +    liftIO $ do       sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)       sClose sock       threadDelay 10000 -    -- This will happen due to implicit reconnect +    -- This will happen due to implicit reconnect     registerRemoteAsync nid1 "b" us     receiveWait [         matchIf (\(RegisterReply label' _) -> "b" == label')@@ -699,27 +719,27 @@     -- Check     Nothing  <- whereisRemote nid1 "a"  -- this will fail because the name is removed when the node is disconnected     Just _  <- whereisRemote nid1 "b"  -- this will suceed because the value is set after thereconnect-    Just _  <- whereisRemote nid1 "c" +    Just _  <- whereisRemote nid1 "c"      liftIO $ putMVar registerTestOk ()    takeMVar registerTestOk --- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server +-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server -- in between-testMatchAny :: NT.Transport -> Assertion +testMatchAny :: NT.Transport -> Assertion testMatchAny transport = do-  proxyAddr <- newEmptyMVar +  proxyAddr <- newEmptyMVar   clientDone <- newEmptyMVar    -- Math server   forkIO $ do-    localNode <- newLocalNode transport initRemoteTable +    localNode <- newLocalNode transport initRemoteTable     mathServer <- forkProcess localNode math-    proxyServer <- forkProcess localNode $ forever $ do +    proxyServer <- forkProcess localNode $ forever $ do       msg <- receiveWait [ matchAny return ]       forward msg mathServer-    putMVar proxyAddr proxyServer +    putMVar proxyAddr proxyServer    -- Client   forkIO $ do@@ -729,7 +749,7 @@     runProcess localNode $ do       pid <- getSelfPid       send mathServer (Add pid 1 2)-      3 <- expect :: Process Double  +      3 <- expect :: Process Double       send mathServer (Divide pid 8 2)       4 <- expect :: Process Double       send mathServer (Divide pid 8 0)@@ -738,12 +758,127 @@    takeMVar clientDone +-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server+-- in between, however we block 'Divide' requests ....+testMatchAnyHandle :: NT.Transport -> Assertion+testMatchAnyHandle transport = do+  proxyAddr <- newEmptyMVar+  clientDone <- newEmptyMVar++  -- Math server+  forkIO $ do+    localNode <- newLocalNode transport initRemoteTable+    mathServer <- forkProcess localNode math+    proxyServer <- forkProcess localNode $ forever $ do+        receiveWait [+            matchAny (maybeForward mathServer)+          ]+    putMVar proxyAddr proxyServer++  -- Client+  forkIO $ do+    localNode <- newLocalNode transport initRemoteTable+    mathServer <- readMVar proxyAddr++    runProcess localNode $ do+      pid <- getSelfPid+      send mathServer (Add pid 1 2)+      3 <- expect :: Process Double+      send mathServer (Divide pid 8 2)+      Nothing <- (expectTimeout 100000) :: Process (Maybe Double)+      liftIO $ putMVar clientDone ()++  takeMVar clientDone+  where maybeForward :: ProcessId -> AbstractMessage -> Process (Maybe ())+        maybeForward s msg =+            maybeHandleMessage msg (\m@(Add _ _ _) -> send s m)++testMatchAnyNoHandle :: NT.Transport -> Assertion+testMatchAnyNoHandle transport = do+  addr <- newEmptyMVar+  clientDone <- newEmptyMVar+  serverDone <- newEmptyMVar++  -- Math server+  forkIO $ do+    localNode <- newLocalNode transport initRemoteTable+    server <- forkProcess localNode $ forever $ do+        receiveWait [+          matchAnyIf+            -- the condition has type `Add -> Bool`+            (\(Add _ _ _) -> True)+            -- the match `AbstractMessage -> Process ()` will succeed!+            (\m -> do+              -- `String -> Process ()` does *not* match the input types however+              r <- (maybeHandleMessage m (\(_ :: String) -> die "NONSENSE" ))+              case r of+                Nothing -> return ()+                Just _  -> die "NONSENSE")+          ]+        -- we *must* have removed the message from our mailbox though!!!+        Nothing <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]+        liftIO $ putMVar serverDone ()+    putMVar addr server++  -- Client+  forkIO $ do+    localNode <- newLocalNode transport initRemoteTable+    server <- readMVar addr++    runProcess localNode $ do+      pid <- getSelfPid+      send server (Add pid 1 2)+      -- we only care about the client having sent a message, so we're done+      liftIO $ putMVar clientDone ()++  takeMVar clientDone+  takeMVar serverDone++-- | Test 'matchAnyIf'. We provide an /echo/ server, but it ignores requests+-- unless the text body @/= "bar"@ - this case should time out rather than+-- removing the message from the process mailbox.+testMatchAnyIf :: NT.Transport -> Assertion+testMatchAnyIf transport = do+  echoAddr <- newEmptyMVar+  clientDone <- newEmptyMVar++  -- echo server+  forkIO $ do+    localNode <- newLocalNode transport initRemoteTable+    echoServer <- forkProcess localNode $ forever $ do+        receiveWait [+            matchAnyIf (\(_ :: ProcessId, (s :: String)) -> s /= "bar")+                       handleMessage+          ]+    putMVar echoAddr echoServer++  -- Client+  forkIO $ do+    localNode <- newLocalNode transport initRemoteTable+    server <- readMVar echoAddr++    runProcess localNode $ do+      pid <- getSelfPid+      send server (pid, "foo")+      "foo" <- expect+      send server (pid, "baz")+      "baz" <- expect+      send server (pid, "bar")+      Nothing <- (expectTimeout 100000) :: Process (Maybe Double)+      liftIO $ putMVar clientDone ()++  takeMVar clientDone+  where handleMessage :: AbstractMessage -> Process (Maybe ())+        handleMessage msg =+          maybeHandleMessage msg (\(pid :: ProcessId, (m :: String))+                                  -> do { send pid m; return () })+ -- Test 'receiveChanTimeout'-testReceiveChanTimeout :: NT.Transport -> Assertion +testReceiveChanTimeout :: NT.Transport -> Assertion testReceiveChanTimeout transport = do   done <- newEmptyMVar   sendPort <- newEmptyMVar-  +   forkTry $ do     localNode <- newLocalNode transport initRemoteTable     runProcess localNode $ do@@ -751,7 +886,7 @@       (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)       liftIO $ putMVar sendPort sp -      -- Wait for a message with a delay. No message arrives, we should get Nothing after 1 second +      -- Wait for a message with a delay. No message arrives, we should get Nothing after 1 second       Nothing <- receiveChanTimeout 1000000 rp        -- Wait for a message with a delay again. Now a message arrives after 0.5 seconds@@ -762,7 +897,7 @@        -- Again, but now there is a message available       liftIO $ threadDelay 1000000-      Just False <- receiveChanTimeout 0 rp +      Just False <- receiveChanTimeout 0 rp        liftIO $ putMVar done () @@ -810,9 +945,9 @@       7 <- receiveChan rp2        -- Test Alternative instance-      +       sendChan spInt 3-      sendChan spBool True +      sendChan spBool True        let rp3 = (even <$> rpInt) <|> rpBool @@ -820,14 +955,14 @@       True <- receiveChan rp3        -- Test Monad instance-      +       sendChan spBool True       sendChan spBool False       sendChan spInt 5        let rp4 :: ReceivePort Int           rp4 = do b <- rpBool-                   if b +                   if b                      then rpInt                      else return 7 @@ -838,14 +973,158 @@    takeMVar done +testKillLocal :: NT.Transport -> Assertion+testKillLocal transport = do+  localNode <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar++  pid <- forkProcess localNode $ do+    liftIO $ threadDelay 1000000++  runProcess localNode $ do+    ref <- monitor pid+    us <- getSelfPid+    kill pid "TestKill"+    ProcessMonitorNotification ref' pid' (DiedException ex) <- expect+    True <- return $ ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill"+    liftIO $ putMVar done ()++  takeMVar done++testKillRemote :: NT.Transport -> Assertion+testKillRemote transport = do+  node1 <- newLocalNode transport initRemoteTable+  node2 <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar++  pid <- forkProcess node1 $ do+    liftIO $ threadDelay 1000000++  runProcess node2 $ do+    ref <- monitor pid+    us <- getSelfPid+    kill pid "TestKill"+    ProcessMonitorNotification ref' pid' (DiedException reason) <- expect+    True <- return $ ref == ref' && pid == pid' && reason == "killed-by=" ++ show us ++ ",reason=TestKill"+    liftIO $ putMVar done ()++  takeMVar done++testCatchesExit :: NT.Transport -> Assertion+testCatchesExit transport = do+  localNode <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar++  _ <- forkProcess localNode $ do+      (die ("foobar", 123 :: Int))+      `catchesExit` [+           (\_ m -> maybeHandleMessage m (\(_ :: String) -> return ()))+         , (\_ m -> maybeHandleMessage m (\(_ :: Maybe Int) -> return ()))+         , (\_ m -> maybeHandleMessage m (\(_ :: String, _ :: Int)+                    -> (liftIO $ putMVar done ()) >> return ()))+         ]++  takeMVar done++testCatches :: NT.Transport -> Assertion+testCatches transport = do+  localNode <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar++  _ <- forkProcess localNode $ do+    node <- getSelfNode+    (liftIO $ throwIO (ProcessLinkException (nullProcessId node) DiedNormal))+    `catches` [+        Handler (\(ProcessLinkException _ _) -> liftIO $ putMVar done ())+      ]++  takeMVar done++testDie :: NT.Transport -> Assertion+testDie transport = do+  localNode <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar++  _ <- forkProcess localNode $ do+      (die ("foobar", 123 :: Int))+      `catchExit` \_from reason -> do+        -- TODO: should verify that 'from' has the right value+        True <- return $ reason == ("foobar", 123 :: Int)+        liftIO $ putMVar done ()++  takeMVar done++testPrettyExit :: NT.Transport -> Assertion+testPrettyExit transport = do+  localNode <- newLocalNode transport initRemoteTable+  done <- newEmptyMVar++  _ <- forkProcess localNode $ do+      (die "timeout")+      `catch` \ex@(ProcessExitException from _) ->+        let expected = "exit-from=" ++ (show from)+        in do+          True <- return $ (show ex) == expected+          liftIO $ putMVar done ()++  takeMVar done++testExitLocal :: NT.Transport -> Assertion+testExitLocal transport = do+  localNode <- newLocalNode transport initRemoteTable+  supervisedDone <- newEmptyMVar+  supervisorDone <- newEmptyMVar++  pid <- forkProcess localNode $ do+    (liftIO $ threadDelay 100000)+      `catchExit` \_from reason -> do+        -- TODO: should verify that 'from' has the right value+        True <- return $ reason == "TestExit"+        liftIO $ putMVar supervisedDone ()++  runProcess localNode $ do+    ref <- monitor pid+    exit pid "TestExit"+    -- This time the client catches the exception, so it dies normally+    ProcessMonitorNotification ref' pid' DiedNormal <- expect+    True <- return $ ref == ref' && pid == pid'+    liftIO $ putMVar supervisorDone ()++  takeMVar supervisedDone+  takeMVar supervisorDone++testExitRemote :: NT.Transport -> Assertion+testExitRemote transport = do+  node1 <- newLocalNode transport initRemoteTable+  node2 <- newLocalNode transport initRemoteTable+  supervisedDone <- newEmptyMVar+  supervisorDone <- newEmptyMVar++  pid <- forkProcess node1 $ do+    (liftIO $ threadDelay 100000)+      `catchExit` \_from reason -> do+        -- TODO: should verify that 'from' has the right value+        True <- return $ reason == "TestExit"+        liftIO $ putMVar supervisedDone ()++  runProcess node2 $ do+    ref <- monitor pid+    exit pid "TestExit"+    ProcessMonitorNotification ref' pid' DiedNormal <- expect+    True <- return $ ref == ref' && pid == pid'+    liftIO $ putMVar supervisorDone ()++  takeMVar supervisedDone+  takeMVar supervisorDone+ tests :: (NT.Transport, TransportInternals)  -> [Test]-tests (transport, transportInternals) = [ +tests (transport, transportInternals) = [     testGroup "Basic features" [         testCase "Ping"                (testPing                transport)-      , testCase "Math"                (testMath                transport) +      , testCase "Math"                (testMath                transport)       , testCase "Timeout"             (testTimeout             transport)       , testCase "Timeout0"            (testTimeout0            transport)-      , testCase "SendToTerminated"    (testSendToTerminated    transport) +      , testCase "SendToTerminated"    (testSendToTerminated    transport)       , testCase "TypedChannnels"      (testTypedChannels       transport)       , testCase "MergeChannels"       (testMergeChannels       transport)       , testCase "Terminate"           (testTerminate           transport)@@ -853,14 +1132,25 @@       , testCase "RemoteRegistry"      (testRemoteRegistry      transport)       , testCase "SpawnLocal"          (testSpawnLocal          transport)       , testCase "MatchAny"            (testMatchAny            transport)+      , testCase "MatchAnyHandle"      (testMatchAnyHandle      transport)+      , testCase "MatchAnyNoHandle"    (testMatchAnyNoHandle    transport)+      , testCase "MatchAnyIf"          (testMatchAnyIf          transport)       , testCase "ReceiveChanTimeout"  (testReceiveChanTimeout  transport)       , testCase "ReceiveChanFeatures" (testReceiveChanFeatures transport)+      , testCase "KillLocal"           (testKillLocal           transport)+      , testCase "KillRemote"          (testKillRemote          transport)+      , testCase "Die"                 (testDie                 transport)+      , testCase "PrettyExit"          (testPrettyExit          transport)+      , testCase "CatchesExit"         (testCatchesExit         transport)+      , testCase "Catches"             (testCatches             transport)+      , testCase "ExitLocal"           (testExitLocal           transport)+      , testCase "ExitRemote"          (testExitRemote          transport)       ]   , testGroup "Monitoring and Linking" [       -- Monitoring processes       --       -- The "missing" combinations in the list below don't make much sense, as-      -- we cannot guarantee that the monitor reply or link exception will not +      -- we cannot guarantee that the monitor reply or link exception will not       -- happen before the unmonitor or unlink       testCase "MonitorUnreachable"           (testMonitorUnreachable         transport True  False)     , testCase "MonitorNormalTermination"     (testMonitorNormalTermination   transport True  False)
tests/TestClosure.hs view
@@ -6,7 +6,7 @@ import Control.Monad (join, replicateM, forever, replicateM_, void) import Control.Exception (IOException, throw) import Control.Concurrent (forkIO, threadDelay)-import Control.Concurrent.MVar +import Control.Concurrent.MVar   ( MVar   , newEmptyMVar   , readMVar@@ -18,12 +18,12 @@ import Control.Applicative ((<$>)) import System.Random (randomIO) import Network.Transport (Transport)-import Network.Transport.TCP +import Network.Transport.TCP   ( createTransportExposeInternals   , defaultTCPParameters   , TransportInternals(socketBetween)   )-import Network.Socket (sClose)  +import Network.Socket (sClose) import Control.Distributed.Process import Control.Distributed.Process.Closure import Control.Distributed.Process.Node@@ -46,7 +46,7 @@  factorial :: Int -> Process Int factorial 0 = return 1-factorial n = (n *) <$> factorial (n - 1) +factorial n = (n *) <$> factorial (n - 1)  addInt :: Int -> Int -> Int addInt x y = x + y@@ -95,7 +95,7 @@   ix <- randomIO   return (xs !! (ix `mod` length xs)) -remotableDecl [ +remotableDecl [     [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;         dfib (_, reply, 0) = sendChan reply 0         dfib (_, reply, 1) = sendChan reply 1@@ -117,33 +117,33 @@ staticQuintuple = $(mkStatic 'quintuple)  factorialClosure :: Int -> Closure (Process Int)-factorialClosure = $(mkClosure 'factorial) +factorialClosure = $(mkClosure 'factorial)  addIntClosure :: Int -> Closure (Int -> Int)-addIntClosure = $(mkClosure 'addInt) +addIntClosure = $(mkClosure 'addInt)  putIntClosure :: Int -> Closure (MVar Int -> IO ())-putIntClosure = $(mkClosure 'putInt) +putIntClosure = $(mkClosure 'putInt)  sendPidClosure :: ProcessId -> Closure (Process ())-sendPidClosure = $(mkClosure 'sendPid) +sendPidClosure = $(mkClosure 'sendPid)  sendFac :: Int -> ProcessId -> Closure (Process ())-sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid +sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid  factorialOf :: Closure (Int -> Process Int) factorialOf = staticClosure $(mkStatic 'factorial)  factorial' :: Int -> Closure (Process Int)-factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf +factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf  waitClosure :: Int -> Closure (Process ())-waitClosure = $(mkClosure 'wait) +waitClosure = $(mkClosure 'wait)  simulateNetworkFailure :: TransportInternals -> NodeId -> NodeId -> Process () simulateNetworkFailure transportInternals fr to = liftIO $ do   threadDelay 10000-  sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to) +  sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to)   sClose sock   threadDelay 10000 @@ -160,87 +160,87 @@     liftIO $ putMVar done ()   takeMVar done -testBind :: Transport -> RemoteTable -> Assertion +testBind :: Transport -> RemoteTable -> Assertion testBind transport rtable = do   node <- newLocalNode transport rtable   done <- newEmptyMVar   runProcess node $ do     us <- getSelfPid-    join . unClosure $ sendFac 6 us -    (720 :: Int) <- expect +    join . unClosure $ sendFac 6 us+    (720 :: Int) <- expect     liftIO $ putMVar done ()   takeMVar done -testSendPureClosure :: Transport -> RemoteTable -> Assertion +testSendPureClosure :: Transport -> RemoteTable -> Assertion testSendPureClosure transport rtable = do   serverAddr <- newEmptyMVar   serverDone <- newEmptyMVar -  forkIO $ do -    node <- newLocalNode transport rtable +  forkIO $ do+    node <- newLocalNode transport rtable     addr <- forkProcess node $ do       cl <- expect       fn <- unClosure cl :: Process (Int -> Int)       13 <- return $ fn 6-      liftIO $ putMVar serverDone () -    putMVar serverAddr addr +      liftIO $ putMVar serverDone ()+    putMVar serverAddr addr    forkIO $ do-    node <- newLocalNode transport rtable +    node <- newLocalNode transport rtable     theirAddr <- readMVar serverAddr     runProcess node $ send theirAddr (addIntClosure 7)    takeMVar serverDone -testSendIOClosure :: Transport -> RemoteTable -> Assertion +testSendIOClosure :: Transport -> RemoteTable -> Assertion testSendIOClosure transport rtable = do   serverAddr <- newEmptyMVar   serverDone <- newEmptyMVar -  forkIO $ do -    node <- newLocalNode transport rtable +  forkIO $ do+    node <- newLocalNode transport rtable     addr <- forkProcess node $ do       cl <- expect       io <- unClosure cl :: Process (MVar Int -> IO ())-      liftIO $ do +      liftIO $ do         someMVar <- newEmptyMVar-        io someMVar +        io someMVar         5 <- readMVar someMVar-        putMVar serverDone () -    putMVar serverAddr addr +        putMVar serverDone ()+    putMVar serverAddr addr    forkIO $ do-    node <- newLocalNode transport rtable +    node <- newLocalNode transport rtable     theirAddr <- readMVar serverAddr-    runProcess node $ send theirAddr (putIntClosure 5) +    runProcess node $ send theirAddr (putIntClosure 5)    takeMVar serverDone -testSendProcClosure :: Transport -> RemoteTable -> Assertion +testSendProcClosure :: Transport -> RemoteTable -> Assertion testSendProcClosure transport rtable = do   serverAddr <- newEmptyMVar   clientDone <- newEmptyMVar -  forkIO $ do -    node <- newLocalNode transport rtable +  forkIO $ do+    node <- newLocalNode transport rtable     addr <- forkProcess node $ do       cl <- expect       pr <- unClosure cl :: Process (Int -> Process ())       pr 5-    putMVar serverAddr addr +    putMVar serverAddr addr    forkIO $ do-    node <- newLocalNode transport rtable +    node <- newLocalNode transport rtable     theirAddr <- readMVar serverAddr     runProcess node $ do       pid <- getSelfPid-      send theirAddr (cpSend $(mkStatic 'sdictInt) pid) +      send theirAddr (cpSend $(mkStatic 'sdictInt) pid)       5 <- expect :: Process Int       liftIO $ putMVar clientDone ()    takeMVar clientDone -testSpawn :: Transport -> RemoteTable -> Assertion +testSpawn :: Transport -> RemoteTable -> Assertion testSpawn transport rtable = do   serverNodeAddr <- newEmptyMVar   clientDone <- newEmptyMVar@@ -261,7 +261,7 @@    takeMVar clientDone -testCall :: Transport -> RemoteTable -> Assertion +testCall :: Transport -> RemoteTable -> Assertion testCall transport rtable = do   serverNodeAddr <- newEmptyMVar   clientDone <- newEmptyMVar@@ -279,7 +279,7 @@    takeMVar clientDone -testCallBind :: Transport -> RemoteTable -> Assertion +testCallBind :: Transport -> RemoteTable -> Assertion testCallBind transport rtable = do   serverNodeAddr <- newEmptyMVar   clientDone <- newEmptyMVar@@ -297,7 +297,7 @@    takeMVar clientDone -testSeq :: Transport -> RemoteTable -> Assertion +testSeq :: Transport -> RemoteTable -> Assertion testSeq transport rtable = do   node <- newLocalNode transport rtable   done <- newEmptyMVar@@ -315,7 +315,7 @@ -- child. The supervisor then throws an exception, the child dies because it -- was linked to the supervisor, and the third process notices that the child -- dies.-testSpawnSupervised :: Transport -> RemoteTable -> Assertion +testSpawnSupervised :: Transport -> RemoteTable -> Assertion testSpawnSupervised transport rtable = do     [node1, node2]       <- replicateM 2 $ newLocalNode transport rtable     [superPid, childPid] <- replicateM 2 $ newEmptyMVar@@ -324,7 +324,7 @@     forkProcess node1 $ do       us <- getSelfPid       liftIO $ putMVar superPid us-      (child, _ref) <- spawnSupervised (localNodeId node2) (waitClosure 1000000) +      (child, _ref) <- spawnSupervised (localNodeId node2) (waitClosure 1000000)       liftIO $ do         putMVar childPid child         threadDelay 500000 -- Give the child a chance to link to us@@ -334,8 +334,8 @@       [super, child] <- liftIO $ mapM readMVar [superPid, childPid]       ref <- monitor child       ProcessMonitorNotification ref' pid' (DiedException e) <- expect-      True <- return $ ref' == ref -                    && pid' == child +      True <- return $ ref' == ref+                    && pid' == child                     && e == show (ProcessLinkException super (DiedException (show supervisorDeath)))       liftIO $ putMVar thirdProcessDone () @@ -344,19 +344,19 @@     supervisorDeath :: IOException     supervisorDeath = userError "Supervisor died" -testSpawnInvalid :: Transport -> RemoteTable -> Assertion +testSpawnInvalid :: Transport -> RemoteTable -> Assertion testSpawnInvalid transport rtable = do   node <- newLocalNode transport rtable   done <- newEmptyMVar   forkProcess node $ do     (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)-    ProcessMonitorNotification ref' pid' _reason <- expect +    ProcessMonitorNotification ref' pid' _reason <- expect     -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure-    True <- return $ ref' == ref && pid == pid'  +    True <- return $ ref' == ref && pid == pid'     liftIO $ putMVar done ()   takeMVar done -testClosureExpect :: Transport -> RemoteTable -> Assertion +testClosureExpect :: Transport -> RemoteTable -> Assertion testClosureExpect transport rtable = do   node <- newLocalNode transport rtable   done <- newEmptyMVar@@ -369,15 +369,15 @@     liftIO $ putMVar done ()   takeMVar done -testSpawnChannel :: Transport -> RemoteTable -> Assertion +testSpawnChannel :: Transport -> RemoteTable -> Assertion testSpawnChannel transport rtable = do   done <- newEmptyMVar   [node1, node2] <- replicateM 2 $ newLocalNode transport rtable    forkProcess node1 $ do-    pingServer <- spawnChannel +    pingServer <- spawnChannel                     (sdictSendPort sdictUnit)-                    (localNodeId node2)  +                    (localNodeId node2)                     ($(mkClosure 'typedPingServer) ())     (sendReply, receiveReply) <- newChan     sendChan pingServer sendReply@@ -386,7 +386,7 @@    takeMVar done -testTDict :: Transport -> RemoteTable -> Assertion +testTDict :: Transport -> RemoteTable -> Assertion testTDict transport rtable = do   done <- newEmptyMVar   [node1, node2] <- replicateM 2 $ newLocalNode transport rtable@@ -395,7 +395,7 @@     liftIO $ putMVar done ()   takeMVar done -testFib :: Transport -> RemoteTable -> Assertion +testFib :: Transport -> RemoteTable -> Assertion testFib transport rtable = do   nodes <- replicateM 4 $ newLocalNode transport rtable   done <- newEmptyMVar@@ -408,28 +408,28 @@    takeMVar done -testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> Assertion +testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> Assertion testSpawnReconnect transport rtable transportInternals = do-  [node1, node2] <- replicateM 2 $ newLocalNode transport rtable +  [node1, node2] <- replicateM 2 $ newLocalNode transport rtable   let nid1 = localNodeId node1       nid2 = localNodeId node2-  done <- newEmptyMVar -  iv <- newMVar (0 :: Int) +  done <- newEmptyMVar+  iv <- newMVar (0 :: Int)    incr <- forkProcess node1 $ forever $ do     () <- expect     liftIO $ modifyMVar_ iv (return . (+ 1))    forkProcess node2 $ do-    _pid1 <- spawn nid1 ($(mkClosure 'signal) incr) +    _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)     simulateNetworkFailure transportInternals nid2 nid1-    _pid2 <- spawn nid1 ($(mkClosure 'signal) incr) -    _pid3 <- spawn nid1 ($(mkClosure 'signal) incr) +    _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)+    _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)      liftIO $ threadDelay 100000      count <- liftIO $ takeMVar iv-    True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn' +    True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'      liftIO $ putMVar done () @@ -437,10 +437,10 @@  -- | 'spawn' used to ave a race condition which would be triggered if the -- spawning process terminates immediately after spawning-testSpawnTerminate :: Transport -> RemoteTable -> Assertion +testSpawnTerminate :: Transport -> RemoteTable -> Assertion testSpawnTerminate transport rtable = do-  slave  <- newLocalNode transport rtable -  master <- newLocalNode transport rtable +  slave  <- newLocalNode transport rtable+  master <- newLocalNode transport rtable   masterDone <- newEmptyMVar    runProcess master $ do@@ -476,5 +476,5 @@ main :: IO () main = do   Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters-  let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable +  let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable   defaultMain (tests transport rtable)
+ tests/TestStats.hs view
@@ -0,0 +1,162 @@+{-# LANGUAGE DeriveDataTypeable        #-}+{-# OPTIONS_GHC -fno-warn-orphans      #-}+module Main where++import Control.Concurrent.MVar+  ( MVar+  , newEmptyMVar+  , putMVar+  , takeMVar+  )+import Control.Distributed.Process+import Control.Distributed.Process.Node+  ( forkProcess+  , newLocalNode+  , initRemoteTable+  , closeLocalNode+  , LocalNode)+import Data.Binary()+import Data.Typeable()+import Network.Transport.TCP+import Prelude hiding (catch, log)+import Test.Framework+  ( Test+  , defaultMain+  , testGroup+  )+import Test.HUnit (Assertion)+import Test.HUnit.Base (assertBool)+import Test.Framework.Providers.HUnit (testCase)++-- these utilities have been cribbed from distributed-process-platform+-- we should really find a way to share them...++-- | A mutable cell containing a test result.+type TestResult a = MVar a++delayedAssertion :: (Eq a) => String -> LocalNode -> a ->+                    (TestResult a -> Process ()) -> Assertion+delayedAssertion note localNode expected testProc = do+  result <- newEmptyMVar+  _ <- forkProcess localNode $ testProc result+  assertComplete note result expected++assertComplete :: (Eq a) => String -> MVar a -> a -> IO ()+assertComplete msg mv a = do+  b <- takeMVar mv+  assertBool msg (a == b)++stash :: TestResult a -> a -> Process ()+stash mvar x = liftIO $ putMVar mvar x++------++testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()+testLocalDeadProcessInfo result = do+  pid <- spawnLocal $ do "finish" <- expect; return ()+  mref <- monitor pid+  send pid "finish"+  _ <- receiveWait [+      matchIf (\(ProcessMonitorNotification ref' pid' r) ->+                    ref' == mref && pid' == pid && r == DiedNormal)+              (\p -> return p)+    ]+  getProcessInfo pid >>= stash result++testLocalLiveProcessInfo :: TestResult Bool -> Process ()+testLocalLiveProcessInfo result = do+  self <- getSelfPid+  node <- getSelfNode+  register "foobar" self++  mon <- liftIO $ newEmptyMVar+  -- TODO: we can't get the mailbox's length+  -- mapM (send self) ["hello", "there", "mr", "process"]+  pid <- spawnLocal $ do+       link self+       mRef <- monitor self+       stash mon mRef+       "die" <- expect+       return ()++  monRef <- liftIO $ takeMVar mon++  mpInfo <- getProcessInfo self+  case mpInfo of+    Nothing -> stash result False+    Just p  -> verifyPInfo p pid monRef node+  where verifyPInfo :: ProcessInfo+                    -> ProcessId+                    -> MonitorRef+                    -> NodeId+                    -> Process ()+        verifyPInfo pInfo pid mref node =+          stash result $ infoNode pInfo     == node           &&+                         infoLinks pInfo    == [pid]          &&+                         infoMonitors pInfo == [(pid, mref)]  &&+--                         infoMessageQueueLength pInfo == Just 4 &&+                         infoRegisteredNames pInfo == ["foobar"]++testRemoteLiveProcessInfo :: LocalNode -> Assertion+testRemoteLiveProcessInfo node1 = do+  serverAddr <- liftIO $ newEmptyMVar :: IO (MVar ProcessId)+  liftIO $ launchRemote serverAddr+  serverPid <- liftIO $ takeMVar serverAddr+  withActiveRemote node1 $ \result -> do+    self <- getSelfPid+    link serverPid+    -- our send op shouldn't overtake link or monitor requests AFAICT+    -- so a little table tennis should get us synchronised properly+    send serverPid (self, "ping")+    "pong" <- expect+    pInfo <- getProcessInfo serverPid+    stash result $ pInfo /= Nothing+  where +    launchRemote :: MVar ProcessId -> IO ()+    launchRemote locMV = do+        node2 <- liftIO $ mkNode "8082"+        _ <- liftIO $ forkProcess node2 $ do+            self <- getSelfPid+            liftIO $ putMVar locMV self+            _ <- receiveWait [+                  match (\(pid, "ping") -> send pid "pong") +                ]+            "stop" <- expect+            return ()+        return ()++    withActiveRemote :: LocalNode+                     -> ((TestResult Bool -> Process ()) -> Assertion)+    withActiveRemote n = do+      a <- delayedAssertion "getProcessInfo remotePid failed" n True+      return a++tests :: LocalNode -> IO [Test]+tests node1 = do+  return [+    testGroup "Process Info" [+        testCase "testLocalDeadProcessInfo"+            (delayedAssertion+             "expected dead process-info to be ProcessInfoNone"+             node1 (Nothing) testLocalDeadProcessInfo)+      , testCase "testLocalLiveProcessInfo"+            (delayedAssertion+             "expected process-info to be correctly populated"+             node1 True testLocalLiveProcessInfo)+      , testCase "testRemoveLiveProcessInfo"+                 (testRemoteLiveProcessInfo node1)+    ] ]++mkNode :: String -> IO LocalNode+mkNode port = do+  Right (transport1, _) <- createTransportExposeInternals+                                    "127.0.0.1" port defaultTCPParameters+  newLocalNode transport1 initRemoteTable++main :: IO ()+main = do+  node1 <- mkNode "8081"+  testData <- tests node1+  defaultMain testData+  closeLocalNode node1+  return ()