packages feed

distributed-process 0.4.0.2 → 0.4.1

raw patch · 8 files changed

+623/−319 lines, 8 filesdep +HUnitdep +test-frameworkdep +test-framework-hunitdep ~binaryPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: HUnit, test-framework, test-framework-hunit

Dependency ranges changed: binary

API changes (from Hackage documentation)

- Control.Distributed.Process: registerRemote :: NodeId -> String -> ProcessId -> Process ()
- Control.Distributed.Process: unregisterRemote :: NodeId -> String -> Process ()
- Control.Distributed.Process.Internal.Primitives: registerRemote :: NodeId -> String -> ProcessId -> Process ()
- Control.Distributed.Process.Internal.Primitives: unregisterRemote :: NodeId -> String -> Process ()
- Control.Distributed.Process.Internal.Types: ReceivePortBiased :: [ReceivePort a] -> ReceivePort a
- Control.Distributed.Process.Internal.Types: ReceivePortRR :: (TVar [ReceivePort a]) -> ReceivePort a
- Control.Distributed.Process.Internal.Types: ReceivePortSingle :: (TQueue a) -> ReceivePort a
- Control.Distributed.Process.Internal.Types: data ReceivePort a
+ Control.Distributed.Process: ProcessRegistrationException :: !String -> ProcessRegistrationException
+ Control.Distributed.Process: data ProcessRegistrationException
+ Control.Distributed.Process: receiveChanTimeout :: Serializable a => Int -> ReceivePort a -> Process (Maybe a)
+ Control.Distributed.Process: registerRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
+ Control.Distributed.Process: reregister :: String -> ProcessId -> Process ()
+ Control.Distributed.Process: reregisterRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
+ Control.Distributed.Process: unregisterRemoteAsync :: NodeId -> String -> Process ()
+ Control.Distributed.Process.Internal.Primitives: receiveChanTimeout :: Serializable a => Int -> ReceivePort a -> Process (Maybe a)
+ Control.Distributed.Process.Internal.Primitives: registerRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
+ Control.Distributed.Process.Internal.Primitives: reregister :: String -> ProcessId -> Process ()
+ Control.Distributed.Process.Internal.Primitives: reregisterRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
+ Control.Distributed.Process.Internal.Primitives: unregisterRemoteAsync :: NodeId -> String -> Process ()
+ Control.Distributed.Process.Internal.Types: ProcessRegistrationException :: !String -> ProcessRegistrationException
+ Control.Distributed.Process.Internal.Types: ReceivePort :: STM a -> ReceivePort a
+ Control.Distributed.Process.Internal.Types: RegisterReply :: String -> Bool -> RegisterReply
+ Control.Distributed.Process.Internal.Types: data ProcessRegistrationException
+ Control.Distributed.Process.Internal.Types: data RegisterReply
+ Control.Distributed.Process.Internal.Types: instance Alternative ReceivePort
+ Control.Distributed.Process.Internal.Types: instance Applicative ReceivePort
+ Control.Distributed.Process.Internal.Types: instance Binary RegisterReply
+ Control.Distributed.Process.Internal.Types: instance Exception ProcessRegistrationException
+ Control.Distributed.Process.Internal.Types: instance Functor ReceivePort
+ Control.Distributed.Process.Internal.Types: instance Monad ReceivePort
+ Control.Distributed.Process.Internal.Types: instance Show ProcessRegistrationException
+ Control.Distributed.Process.Internal.Types: instance Show RegisterReply
+ Control.Distributed.Process.Internal.Types: instance Typeable ProcessRegistrationException
+ Control.Distributed.Process.Internal.Types: instance Typeable RegisterReply
+ Control.Distributed.Process.Internal.Types: newtype ReceivePort a
+ Control.Distributed.Process.Internal.Types: receiveSTM :: ReceivePort a -> STM a
- Control.Distributed.Process.Internal.Types: Register :: !String -> !(Maybe ProcessId) -> ProcessSignal
+ Control.Distributed.Process.Internal.Types: Register :: !String -> !NodeId -> !(Maybe ProcessId) -> !Bool -> ProcessSignal

Files

distributed-process.cabal view
@@ -1,5 +1,5 @@ Name:          distributed-process -Version:       0.4.0.2+Version:       0.4.1 Cabal-Version: >=1.8 Build-Type:    Simple License:       BSD3 @@ -39,7 +39,7 @@  Library   Build-Depends:     base >= 4.4 && < 5,-                     binary >= 0.5 && < 0.6,+                     binary >= 0.5 && < 0.7,                      network-transport >= 0.3 && < 0.4,                      stm >= 2.3 && < 2.5,                      transformers >= 0.2 && < 0.4,@@ -93,9 +93,11 @@                      distributed-process,                      network-transport >= 0.3 && < 0.4,                      network-transport-tcp >= 0.3 && < 0.4,-                     binary >= 0.5 && < 0.6,-                     network >= 2.3 && < 2.5-  Other-modules:     TestAuxiliary+                     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   Extensions:        CPP,                      ScopedTypeVariables,                      DeriveDataTypeable,@@ -114,8 +116,10 @@                      network-transport >= 0.3 && < 0.4,                      network-transport-tcp >= 0.3 && < 0.4,                      bytestring >= 0.9 && < 0.11,-                     network >= 2.3 && < 2.5-  Other-modules:     TestAuxiliary+                     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,                       ScopedTypeVariables   ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind @@ -127,7 +131,7 @@                      distributed-process,                      network-transport-tcp >= 0.3 && < 0.4,                      bytestring >= 0.9 && < 0.11,-                     binary >= 0.5 && < 0.6+                     binary >= 0.5 && < 0.7   else     buildable: False   Main-Is:           benchmarks/Throughput.hs@@ -140,7 +144,7 @@                      distributed-process,                      network-transport-tcp >= 0.3 && < 0.4,                      bytestring >= 0.9 && < 0.11,-                     binary >= 0.5 && < 0.6+                     binary >= 0.5 && < 0.7   else     buildable: False   Main-Is:           benchmarks/Latency.hs@@ -153,7 +157,7 @@                      distributed-process,                      network-transport-tcp >= 0.3 && < 0.4,                      bytestring >= 0.9 && < 0.11,-                     binary >= 0.5 && < 0.6+                     binary >= 0.5 && < 0.7   else     buildable: False   Main-Is:           benchmarks/Channels.hs@@ -166,7 +170,7 @@                      distributed-process,                      network-transport-tcp >= 0.3 && < 0.4,                      bytestring >= 0.9 && < 0.11,-                     binary >= 0.5 && < 0.6+                     binary >= 0.5 && < 0.7   else     buildable: False   Main-Is:           benchmarks/Spawns.hs
src/Control/Distributed/Process.hs view
@@ -19,6 +19,7 @@     -- * Basic messaging   , send    , expect+  , expectTimeout     -- * Channels   , ReceivePort   , SendPort@@ -26,6 +27,7 @@   , newChan   , sendChan   , receiveChan+  , receiveChanTimeout   , mergePortsBiased   , mergePortsRR     -- * Advanced messaging@@ -42,6 +44,7 @@   , call   , terminate   , ProcessTerminationException(..)+  , ProcessRegistrationException(..)   , SpawnRef   , getSelfPid   , getSelfNode@@ -75,11 +78,13 @@   , say     -- * Registry   , register+  , reregister   , unregister   , whereis   , nsend-  , registerRemote-  , unregisterRemote+  , registerRemoteAsync+  , reregisterRemoteAsync+  , unregisterRemoteAsync   , whereisRemoteAsync   , nsendRemote   , WhereIsReply(..)@@ -91,7 +96,6 @@   , bracket_   , finally     -- * Auxiliary API-  , expectTimeout   , spawnAsync   , spawnSupervised   , spawnLink@@ -134,6 +138,7 @@   , ProcessLinkException(..)   , NodeLinkException(..)   , PortLinkException(..)+  , ProcessRegistrationException(..)   , DiedReason(..)   , SpawnRef(..)   , DidSpawn(..)@@ -196,11 +201,13 @@   , say     -- Registry   , register+  , reregister   , unregister   , whereis   , nsend-  , registerRemote-  , unregisterRemote+  , registerRemoteAsync+  , reregisterRemoteAsync+  , unregisterRemoteAsync   , whereisRemoteAsync   , nsendRemote     -- Closures@@ -215,6 +222,7 @@   , finally     -- Auxiliary API   , expectTimeout+  , receiveChanTimeout   , spawnAsync     -- Reconnecting   , reconnect
src/Control/Distributed/Process/Internal/Primitives.hs view
@@ -35,11 +35,13 @@   , say     -- * Registry   , register+  , reregister   , unregister   , whereis   , nsend-  , registerRemote-  , unregisterRemote+  , registerRemoteAsync+  , reregisterRemoteAsync+  , unregisterRemoteAsync   , whereisRemoteAsync   , nsendRemote     -- * Closures@@ -54,6 +56,7 @@   , finally     -- * Auxiliary API   , expectTimeout+  , receiveChanTimeout   , spawnAsync   , linkNode   , linkPort@@ -74,6 +77,8 @@ import Data.Time.Clock (getCurrentTime) import Data.Time.Format (formatTime) import System.Locale (defaultTimeLocale)+import System.Timeout (timeout)+import Control.Monad (when) import Control.Monad.Reader (ask) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Applicative ((<$>))@@ -87,6 +92,7 @@ import Control.Concurrent.Chan (writeChan) import Control.Concurrent.STM    ( STM+  , TVar   , atomically   , orElse   , newTVar@@ -124,6 +130,8 @@   , DidUnlinkNode(..)   , DidUnlinkPort(..)   , WhereIsReply(..)+  , RegisterReply(..)+  , ProcessRegistrationException(..)   , createMessage   , runLocalProcess   , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)@@ -179,7 +187,7 @@       let sport = SendPort cid        chan  <- liftIO newTQueueIO       chan' <- mkWeakTQueue chan $ finalizer (processState proc) lcid-      let rport = ReceivePortSingle chan+      let rport = ReceivePort $ readTQueue chan       let tch   = TypedChannel chan'        return ( (channelCounter ^: (+ 1))              . (typedChannelWithId lcid ^= Just tch)@@ -188,7 +196,7 @@              )   where     finalizer :: StrictMVar LocalProcessState -> LocalSendPortId -> IO ()-    finalizer processState lcid = modifyMVar_ processState $ +    finalizer st lcid = modifyMVar_ st $        return . (typedChannelWithId lcid ^= Nothing)  -- | Send a message on a typed channel@@ -204,34 +212,40 @@ -- | Wait for a message on a typed channel receiveChan :: Serializable a => ReceivePort a -> Process a receiveChan = liftIO . atomically . receiveSTM -  where-    receiveSTM :: ReceivePort a -> STM a-    receiveSTM (ReceivePortSingle c) = -      readTQueue c-    receiveSTM (ReceivePortBiased ps) =-      foldr1 orElse (map receiveSTM ps)-    receiveSTM (ReceivePortRR psVar) = do-      ps <- readTVar psVar-      a  <- foldr1 orElse (map receiveSTM ps)-      writeTVar psVar (rotate ps)-      return a -    rotate :: [a] -> [a]-    rotate []     = []-    rotate (x:xs) = xs ++ [x]+-- | 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 $ +  (Just <$> receiveSTM ch) `orElse` return Nothing+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)-mergePortsBiased = return . ReceivePortBiased +mergePortsBiased = return . ReceivePort. foldr1 orElse . map receiveSTM  -- | Like 'mergePortsBiased', but with a round-robin scheduler (rather than -- left-biased) mergePortsRR :: Serializable a => [ReceivePort a] -> Process (ReceivePort a)-mergePortsRR ps = liftIO . atomically $ ReceivePortRR <$> newTVar ps+mergePortsRR = \ps -> do+    psVar <- liftIO . atomically $ newTVar (map receiveSTM ps)+    return $ ReceivePort (rr psVar) +  where+    rotate :: [a] -> [a]+    rotate []     = []+    rotate (x:xs) = xs ++ [x] +    rr :: TVar [STM a] -> STM a+    rr psVar = do+      ps <- readTVar psVar+      a  <- foldr1 orElse ps +      writeTVar psVar (rotate ps)+      return a+ -------------------------------------------------------------------------------- -- Advanced messaging                                                         --  --------------------------------------------------------------------------------@@ -328,6 +342,25 @@  -- | Link to a remote process (asynchronous) --+-- When process A links to process B (that is, process A calls+-- @link pidB@) then an asynchronous exception will be thrown to process A+-- when process B terminates (normally or abnormally), or when process A gets+-- disconnected from process B. Although it is /technically/ possible to catch+-- these exceptions, chances are if you find yourself trying to do so you should+-- probably be using 'monitor' rather than 'link'. In particular, code such as+--+-- > link pidB   -- Link to process B+-- > expect      -- Wait for a message from process B+-- > 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 +-- be terminated too, depending on whether the exception is thrown before or+-- after the 'unlink' (i.e., this code has a race condition).+--+-- Linking is all-or-nothing: A is either linked to B, or it's not. A second+-- call to 'link' has no effect.+-- -- Note that 'link' provides unidirectional linking (see 'spawnSupervised'). -- Linking makes no distinction between normal and abnormal termination of -- the remote process.@@ -335,6 +368,17 @@ link = sendCtrlMsg Nothing . Link . ProcessIdentifier  -- | Monitor another process (asynchronous)+--+-- 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+-- 'expect'); the notification includes a reason ('DiedNormal', 'DiedException',+-- 'DiedDisconnect', etc.).+--+-- Every call to 'monitor' returns a new monitor reference 'MonitorRef'; if+-- 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  @@ -429,7 +473,7 @@  -- | Like 'expect' but with a timeout expectTimeout :: forall a. Serializable a => Int -> Process (Maybe a)-expectTimeout timeout = receiveTimeout timeout [match return] +expectTimeout n = receiveTimeout n [match return]   -- | Asynchronous version of 'spawn' -- @@ -500,28 +544,68 @@ --------------------------------------------------------------------------------  -- | Register a process with the local registry (asynchronous).+-- This version will wait until a response is gotten from the+-- management process. The name must not already be registered.+-- The process need not be on this node.+-- A bad registration will result in a 'ProcessRegistrationException' -- -- The process to be registered does not have to be local itself. register :: String -> ProcessId -> Process ()-register label pid = -  sendCtrlMsg Nothing (Register label (Just pid))+register = registerImpl False +-- | Like 'register', but will replace an existing registration.+-- The name must already be registered.+reregister :: String -> ProcessId -> Process ()+reregister = registerImpl True++registerImpl :: Bool -> String -> ProcessId -> Process ()+registerImpl force label pid = do+  mynid <- getSelfNode+  sendCtrlMsg Nothing (Register label mynid (Just pid) force)+  receiveWait [ matchIf (\(RegisterReply label' _) -> label == label')+                        (\(RegisterReply _ ok) -> handleRegistrationReply label ok)+              ]+ -- | Register a process with a remote registry (asynchronous). -- -- The process to be registered does not have to live on the same remote node.-registerRemote :: NodeId -> String -> ProcessId -> Process ()-registerRemote nid label pid = -  sendCtrlMsg (Just nid) (Register label (Just pid)) +-- Reply wil come in the form of a 'RegisterReply' message+--+-- See comments in 'whereisRemoteAsync'+registerRemoteAsync :: NodeId -> String -> ProcessId -> Process ()+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) + -- | Remove a process from the local registry (asynchronous).+-- This version will wait until a response is gotten from the+-- management process. The name must already be registered. unregister :: String -> Process ()-unregister label = -  sendCtrlMsg Nothing (Register label Nothing)+unregister label = do+  mynid <- getSelfNode+  sendCtrlMsg Nothing (Register label mynid Nothing False)+  receiveWait [ matchIf (\(RegisterReply label' _) -> label == label')+                        (\(RegisterReply _ ok) -> handleRegistrationReply label ok)+              ] +-- | Deal with the result from an attempted registration or unregistration+-- by throwing an exception if necessary+handleRegistrationReply :: String -> Bool -> Process ()+handleRegistrationReply label ok = +  when (not ok) $+     liftIO $ throwIO $ ProcessRegistrationException label+ -- | Remove a process from a remote registry (asynchronous).-unregisterRemote :: NodeId -> String -> Process ()-unregisterRemote nid label =-  sendCtrlMsg (Just nid) (Register label Nothing)+-- +-- Reply wil come in the form of a 'RegisterReply' message+--+-- See comments in 'whereisRemoteAsync'+unregisterRemoteAsync :: NodeId -> String -> Process ()+unregisterRemoteAsync nid label =+  sendCtrlMsg (Just nid) (Register label nid Nothing False)  -- | Query the local process registry whereis :: String -> Process (Maybe ProcessId)
src/Control/Distributed/Process/Internal/Types.hs view
@@ -39,6 +39,7 @@   , ProcessLinkException(..)   , NodeLinkException(..)   , PortLinkException(..)+  , ProcessRegistrationException(..)   , DiedReason(..)   , DidUnmonitor(..)   , DidUnlinkProcess(..)@@ -47,6 +48,7 @@   , SpawnRef(..)   , DidSpawn(..)   , WhereIsReply(..)+  , RegisterReply(..)     -- * Node controller internal data types    , NCMsg(..)   , ProcessSignal(..)@@ -82,9 +84,9 @@ import Control.Exception (Exception) import Control.Concurrent (ThreadId) import Control.Concurrent.Chan (Chan)-import Control.Concurrent.STM (TVar)+import Control.Concurrent.STM (STM) import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)-import Control.Applicative (Applicative, (<$>), (<*>))+import Control.Applicative (Applicative, Alternative, (<$>), (<*>)) import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT) import Control.Monad.IO.Class (MonadIO) import Control.Distributed.Process.Serializable @@ -262,6 +264,13 @@   deriving (Typeable, Binary, Show, Eq, Ord)  -- | The receive end of a typed channel (not serializable)+--+-- Note that 'ReceivePort' implements 'Functor', 'Applicative', 'Alternative'+-- and 'Monad'. This is especially useful when merging receive ports.+newtype ReceivePort a = ReceivePort { receiveSTM :: STM a }+  deriving (Typeable, Functor, Applicative, Alternative, Monad)++{- data ReceivePort a =      -- | A single receive port     ReceivePortSingle (TQueue a)@@ -270,6 +279,7 @@     -- | A round-robin combination of receive ports   | ReceivePortRR (TVar [ReceivePort a])    deriving Typeable+-}  -------------------------------------------------------------------------------- -- Messages                                                                   --@@ -351,9 +361,17 @@     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 = +    ProcessRegistrationException !String+  deriving (Typeable, Show)+ instance Exception ProcessLinkException instance Exception NodeLinkException instance Exception PortLinkException+instance Exception ProcessRegistrationException  -- | Why did a process die? data DiedReason = @@ -398,6 +416,10 @@ data WhereIsReply = WhereIsReply String (Maybe ProcessId)   deriving (Show, Typeable) +-- | (Asynchronous) reply from 'register' and 'unregister'+data RegisterReply = RegisterReply String Bool+  deriving (Show, Typeable)+ -------------------------------------------------------------------------------- -- Node controller internal data types                                        -- --------------------------------------------------------------------------------@@ -418,7 +440,7 @@   | Died Identifier !DiedReason   | Spawn !(Closure (Process ())) !SpawnRef    | WhereIs !String-  | Register !String !(Maybe ProcessId) -- Use 'Nothing' to unregister+  | Register !String !NodeId !(Maybe ProcessId) !Bool -- Use 'Nothing' to unregister, use True to force reregister   | NamedSend !String !Message   deriving Show @@ -462,7 +484,7 @@   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 pid)  = putWord8 7 >> put label >> put pid+  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)    get = do     header <- getWord8@@ -474,7 +496,7 @@       4 -> Died <$> get <*> get       5 -> Spawn <$> get <*> get       6 -> WhereIs <$> get-      7 -> Register <$> get <*> get+      7 -> Register <$> get <*> get <*> get <*> get       8 -> NamedSend <$> get <*> (payloadToMessage <$> get)       _ -> fail "ProcessSignal.get: invalid" @@ -517,6 +539,10 @@ instance Binary WhereIsReply where   put (WhereIsReply label mPid) = put label >> put mPid   get = WhereIsReply <$> get <*> get++instance Binary RegisterReply where+  put (RegisterReply label ok) = put label >> put ok +  get = RegisterReply <$> get <*> get  -------------------------------------------------------------------------------- -- Accessors                                                                  --
src/Control/Distributed/Process/Node.hs view
@@ -24,6 +24,8 @@ import qualified Data.Map as Map    ( empty   , toList+  , fromList+  , filter   , partitionWithKey   , elems   , filterWithKey@@ -31,7 +33,7 @@ import Data.Set (Set) import qualified Data.Set as Set (empty, insert, delete, member) import Data.Foldable (forM_)-import Data.Maybe (isJust)+import Data.Maybe (isJust, isNothing, catMaybes) import Data.Typeable (Typeable) import Control.Category ((>>>)) import Control.Applicative ((<$>))@@ -118,6 +120,7 @@   , nodeOf   , SendPortId(..)   , typedChannelWithId+  , RegisterReply(..)   , WhereIsReply(..)   , messageToPayload   , payloadToMessage@@ -437,8 +440,9 @@     _links    :: !(Map Identifier (Set ProcessId))      -- Mapping from remote processes to monitoring local processes   , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))-     -- Process registry-  , _registry :: !(Map String ProcessId)+     -- Process registry: names and where they live, mapped to the PIDs+  , _registeredHere :: !(Map String ProcessId)+  , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])   }  newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }@@ -447,7 +451,8 @@ initNCState :: NCState initNCState = NCState { _links    = Map.empty                       , _monitors = Map.empty-                      , _registry = Map.empty+                      , _registeredHere = Map.empty+                      , _registeredOnNodes = Map.empty                       }  --------------------------------------------------------------------------------@@ -485,8 +490,8 @@         ncEffectDied ident reason       NCMsg (ProcessIdentifier from) (Spawn proc ref) ->         ncEffectSpawn from proc ref-      NCMsg _from (Register label pid) ->-        ncEffectRegister label pid+      NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->+        ncEffectRegister from label atnode pid force       NCMsg (ProcessIdentifier from) (WhereIs label) ->         ncEffectWhereIs from label       NCMsg from (NamedSend label msg') ->@@ -554,6 +559,8 @@   (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))   (affectedMons,  unaffectedMons)  <- gets (splitNotif ident . (^. monitors)) +--  _registry :: !(Map (String,NodeId) ProcessId)+   let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False    forM_ (Map.toList affectedLinks) $ \(them, uss) -> @@ -564,10 +571,34 @@   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) -> +        case ident `impliesDeathOf` ProcessIdentifier pid of+           True -> +              do forM_ nidlist $ \(nid,_) ->+                   when (not $ isLocal node (NodeIdentifier nid)) +                      (forwardNameDeath node nid)+                 return Nothing+           False -> return $ Just (pid,nidlist)  )+  modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))+    where +       forwardNameDeath node nid =+                   liftIO $ sendBinary node+                             (NodeIdentifier $ localNodeId node)+                             (NodeIdentifier $ nid)+                             WithImplicitReconnect+                             NCMsg+                             { ctrlMsgSender = NodeIdentifier (localNodeId node) +                             , ctrlMsgSignal = Died ident reason+                             }              ++ -- [Unified: Table 13] ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC () ncEffectSpawn pid cProc ref = do@@ -587,18 +618,71 @@  -- Unified semantics does not explicitly describe how to implement 'register', -- but mentions it's "very similar to nsend" (Table 14)-ncEffectRegister :: String -> Maybe ProcessId -> NC ()-ncEffectRegister label mPid = -  modify' $ registryFor label ^= mPid-  -- An acknowledgement is not necessary. If we want a synchronous register,-  -- it suffices to send a whereis requiry immediately after the register-  -- (that may not suffice if we do decide for unreliable messaging instead)+-- We send a response indicated if the operation is invalid+ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()+ncEffectRegister from label atnode mPid reregistration = do+  node <- ask+  currentVal <- gets (^. registeredHereFor label)+  isOk <-+       case mPid of+         Nothing -> -- unregister request+           return $ isJust currentVal+         Just thepid -> -- register request+           do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)+              return $ (isNothing currentVal /= reregistration) && +                (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )+  if isLocal node (NodeIdentifier atnode)+     then do when (isOk) $+               do modify' $ registeredHereFor label ^= mPid+                  updateRemote node currentVal mPid+             liftIO $ sendMessage node+                       (NodeIdentifier (localNodeId node))+                       (ProcessIdentifier from) +                       WithImplicitReconnect+                       (RegisterReply label isOk)+     else let operation = +                 case reregistration of+                    True -> flip decList+                    False -> flip incList+           in case mPid of+                Nothing -> return ()+                Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)+      where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =+              do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)+                 forward node (processNodeId newval) (Register label atnode (Just newval) False)+            updateRemote node Nothing (Just newval) =+                 forward node (processNodeId newval) (Register label atnode (Just newval) False)+            updateRemote node (Just oldval) Nothing =+                 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)+            updateRemote _ _ _ = return ()+            maybeify f Nothing = unmaybeify $ f []+            maybeify f (Just x) = unmaybeify $ f x+            unmaybeify [] = Nothing+            unmaybeify x = Just x+            incList [] tag = [(tag,1)]+            incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs+            incList (x:xs) tag = x : incList xs tag+            decList [] _ = []+            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 = +              when (not $ isLocal node (NodeIdentifier to)) $+                    liftIO $ sendBinary node+                                        (ProcessIdentifier from)+                                        (NodeIdentifier to)+                                        WithImplicitReconnect+                                        NCMsg+                                         { ctrlMsgSender = ProcessIdentifier from+                                         , ctrlMsgSignal = reg+                                         } + -- Unified semantics does not explicitly describe 'whereis' ncEffectWhereIs :: ProcessId -> String -> NC () ncEffectWhereIs from label = do   node <- ask-  mPid <- gets (^. registryFor label)+  mPid <- gets (^. registeredHereFor label)   liftIO $ sendMessage node                        (NodeIdentifier (localNodeId node))                        (ProcessIdentifier from) @@ -608,9 +692,9 @@ -- [Unified: Table 14] ncEffectNamedSend :: Identifier -> String -> Message -> NC () ncEffectNamedSend from label msg = do-  mPid <- gets (^. registryFor label)+  node <- ask  +  mPid <- gets (^. registeredHereFor label)   -- If mPid is Nothing, we just ignore the named send (as per Table 14)-  node <- ask   forM_ mPid $ \pid ->      liftIO $ sendPayload node                          from@@ -660,7 +744,7 @@ destNid (Monitor ref)   = Just $ nodeOf (monitorRefIdent ref) destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref) destNid (Spawn _ _)     = Nothing -destNid (Register _ _)  = Nothing+destNid (Register _ _ _ _) = Nothing destNid (WhereIs _)     = Nothing destNid (NamedSend _ _) = Nothing -- We don't need to forward 'Died' signals; if monitoring/linking is setup,@@ -732,17 +816,23 @@ monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef))) monitors = accessor _monitors (\ms st -> st { _monitors = ms }) -registry :: Accessor NCState (Map String ProcessId)-registry = accessor _registry (\ry st -> st { _registry = ry })+registeredHere :: Accessor NCState (Map String ProcessId)+registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry }) +registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])+registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })+ linksFor :: Identifier -> Accessor NCState (Set ProcessId) linksFor ident = links >>> DAC.mapDefault Set.empty ident  monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef)) monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident -registryFor :: String -> Accessor NCState (Maybe ProcessId)-registryFor ident = registry >>> DAC.mapMaybe ident+registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)+registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident++registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])+registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident  -- | @splitNotif ident@ splits a notifications map into those -- notifications that should trigger when 'ident' fails and those links that
− tests/TestAuxiliary.hs
@@ -1,75 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-module TestAuxiliary ( -- Running tests-                       runTest-                     , runTests-                       -- Writing tests-                     , forkTry-                     , trySome-                     , randomThreadDelay-                     ) where---#if ! MIN_VERSION_base(4,6,0)-import Prelude hiding (catch)-#endif--import Control.Concurrent (myThreadId, forkIO, ThreadId, throwTo, threadDelay)-import Control.Monad (liftM2, unless)-import Control.Exception (SomeException, try, catch)-import System.Timeout (timeout)-import System.IO (stdout, hFlush)-import System.Console.ANSI ( SGR(SetColor, Reset)-                           , Color(Red, Green)-                           , ConsoleLayer(Foreground)-                           , ColorIntensity(Vivid)-                           , setSGR-                           )-import System.Random (randomIO)---- | Like fork, but throw exceptions in the child thread to the parent-forkTry :: IO () -> IO ThreadId -forkTry p = do-  tid <- myThreadId-  forkIO $ catch p (\e -> throwTo tid (e :: SomeException))---- | Like try, but specialized to SomeException-trySome :: IO a -> IO (Either SomeException a)-trySome = try---- | Run the given test, catching timeouts and exceptions-runTest :: String -> IO () -> IO Bool -runTest description test = do-  putStr $ "Running " ++ show description ++ ": "-  hFlush stdout-  done <- try . timeout 60000000 $ test -- 60 seconds-  case done of-    Left err        -> failed $ "(exception: " ++ show (err :: SomeException) ++ ")" -    Right Nothing   -> failed "(timeout)"-    Right (Just ()) -> ok -  where-    failed :: String -> IO Bool -    failed err = do-      setSGR [SetColor Foreground Vivid Red]-      putStr "failed "-      setSGR [Reset]-      putStrLn err-      return False--    ok :: IO Bool -    ok = do-      setSGR [SetColor Foreground Vivid Green]-      putStrLn "ok"-      setSGR [Reset]-      return True---- | Run a bunch of tests and throw an exception if any fails -runTests :: [(String, IO ())] -> IO ()-runTests tests = do-  success <- foldr (liftM2 (&&) . uncurry runTest) (return True) tests-  unless success $ fail "Some tests failed"---- | Random thread delay between 0 and the specified max -randomThreadDelay :: Int -> IO ()-randomThreadDelay maxDelay = do-  delay <- randomIO :: IO Int-  threadDelay (delay `mod` maxDelay) 
tests/TestCH.hs view
@@ -4,10 +4,11 @@ import Prelude hiding (catch) #endif + import Data.Binary (Binary(..)) import Data.Typeable (Typeable) import Data.Foldable (forM_)-import Control.Concurrent (forkIO, threadDelay)+import Control.Concurrent (forkIO, threadDelay, myThreadId, throwTo, ThreadId) import Control.Concurrent.MVar    ( MVar   , newEmptyMVar@@ -16,8 +17,9 @@   , readMVar   ) import Control.Monad (replicateM_, replicateM, forever)-import Control.Exception (throwIO)-import Control.Applicative ((<$>), (<*>))+import Control.Exception (SomeException, throwIO)+import qualified Control.Exception as Ex (catch)+import Control.Applicative ((<$>), (<*>), pure, (<|>)) import qualified Network.Transport as NT (Transport, closeEndPoint) import Network.Socket (sClose) import Network.Transport.TCP @@ -29,17 +31,31 @@ import Control.Distributed.Process.Internal.Types    ( NodeId(nodeAddress)    , LocalNode(localEndPoint)+  , RegisterReply(..)   ) import Control.Distributed.Process.Node import Control.Distributed.Process.Serializable (Serializable)-import TestAuxiliary +import Test.HUnit (Assertion)+import Test.Framework (Test, defaultMain, testGroup)+import Test.Framework.Providers.HUnit (testCase)+ newtype Ping = Ping ProcessId   deriving (Typeable, Binary, Show)  newtype Pong = Pong ProcessId   deriving (Typeable, Binary, Show) +--------------------------------------------------------------------------------+-- Supporting definitions                                                     --+--------------------------------------------------------------------------------++-- | Like fork, but throw exceptions in the child thread to the parent+forkTry :: IO () -> IO ThreadId +forkTry p = do+  tid <- myThreadId+  forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))+ -- | The ping server from the paper ping :: Process () ping = do@@ -55,35 +71,32 @@   WhereIsReply _ mPid <- expect   return mPid --- | Basic ping test-testPing :: NT.Transport -> IO ()-testPing transport = do-  serverAddr <- newEmptyMVar-  clientDone <- newEmptyMVar--  -- Server-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode ping-    putMVar serverAddr addr--  -- Client-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    pingServer <- readMVar serverAddr+data Add       = Add    ProcessId Double Double deriving (Typeable) +data Divide    = Divide ProcessId Double Double deriving (Typeable)+data DivByZero = DivByZero deriving (Typeable) -    let numPings = 10000+instance Binary Add where+  put (Add pid x y) = put pid >> put x >> put y+  get = Add <$> get <*> get <*> get -    runProcess localNode $ do-      pid <- getSelfPid-      replicateM_ numPings $ do-        send pingServer (Pong pid)-        Ping _ <- expect-        return ()+instance Binary Divide where+  put (Divide pid x y) = put pid >> put x >> put y+  get = Divide <$> get <*> get <*> get -    putMVar clientDone ()+instance Binary DivByZero where+  put DivByZero = return ()+  get = return DivByZero -  takeMVar clientDone+-- The math server from the paper+math :: Process ()+math = do+  receiveWait+    [ match (\(Add pid x y) -> send pid (x + y))+    , matchIf (\(Divide _   _ y) -> y /= 0)+              (\(Divide pid x y) -> send pid (x / y))+    , match (\(Divide pid _ _) -> send pid DivByZero)+    ]+  math  -- | Monitor or link to a remote node monitorOrLink :: Bool            -- ^ 'True' for monitor, 'False' for link@@ -125,9 +138,42 @@             liftIO $ putMVar done ()         )   +--------------------------------------------------------------------------------+-- The tests proper                                                           --+-------------------------------------------------------------------------------- +-- | Basic ping test+testPing :: NT.Transport -> Assertion +testPing transport = do+  serverAddr <- newEmptyMVar+  clientDone <- newEmptyMVar++  -- Server+  forkIO $ do+    localNode <- newLocalNode transport initRemoteTable+    addr <- forkProcess localNode ping+    putMVar serverAddr addr++  -- Client+  forkIO $ do+    localNode <- newLocalNode transport initRemoteTable+    pingServer <- readMVar serverAddr++    let numPings = 10000++    runProcess localNode $ do+      pid <- getSelfPid+      replicateM_ numPings $ do+        send pingServer (Pong pid)+        Ping _ <- expect+        return ()++    putMVar clientDone ()++  takeMVar clientDone+ -- | Monitor a process on an unreachable node -testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> IO ()+testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> Assertion  testMonitorUnreachable transport mOrL un = do   deadProcess <- newEmptyMVar   done <- newEmptyMVar@@ -147,7 +193,7 @@   takeMVar done  -- | Monitor a process which terminates normally-testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> IO ()+testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> Assertion  testMonitorNormalTermination transport mOrL un = do   monitorSetup <- newEmptyMVar   monitoredProcess <- newEmptyMVar@@ -168,7 +214,7 @@   takeMVar done  -- | Monitor a process which terminates abnormally-testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> IO ()+testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> Assertion  testMonitorAbnormalTermination transport mOrL un = do   monitorSetup <- newEmptyMVar   monitoredProcess <- newEmptyMVar@@ -192,7 +238,7 @@   takeMVar done      -- | Monitor a local process that is already dead-testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> IO ()+testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion  testMonitorLocalDeadProcess transport mOrL un = do   processDead <- newEmptyMVar   processAddr <- newEmptyMVar@@ -212,7 +258,7 @@   takeMVar done  -- | Monitor a remote process that is already dead-testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> IO ()+testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion  testMonitorRemoteDeadProcess transport mOrL un = do   processDead <- newEmptyMVar   processAddr <- newEmptyMVar@@ -233,7 +279,7 @@   takeMVar done  -- | Monitor a process that becomes disconnected-testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> IO ()+testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> Assertion  testMonitorDisconnect transport mOrL un = do   processAddr <- newEmptyMVar   monitorSetup <- newEmptyMVar@@ -254,34 +300,8 @@      takeMVar done -data Add       = Add    ProcessId Double Double deriving (Typeable) -data Divide    = Divide ProcessId Double Double deriving (Typeable)-data DivByZero = DivByZero deriving (Typeable)--instance Binary Add where-  put (Add pid x y) = put pid >> put x >> put y-  get = Add <$> get <*> get <*> get--instance Binary Divide where-  put (Divide pid x y) = put pid >> put x >> put y-  get = Divide <$> get <*> get <*> get--instance Binary DivByZero where-  put DivByZero = return ()-  get = return DivByZero--math :: Process ()-math = do-  receiveWait-    [ match (\(Add pid x y) -> send pid (x + y))-    , matchIf (\(Divide _   _ y) -> y /= 0)-              (\(Divide pid x y) -> send pid (x / y))-    , match (\(Divide pid _ _) -> send pid DivByZero)-    ]-  math- -- | Test the math server (i.e., receiveWait)-testMath :: NT.Transport -> IO ()+testMath :: NT.Transport -> Assertion  testMath transport = do   serverAddr <- newEmptyMVar    clientDone <- newEmptyMVar@@ -312,7 +332,7 @@ -- | Send first message (i.e. connect) to an already terminated process -- (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 -> IO ()+testSendToTerminated :: NT.Transport -> Assertion  testSendToTerminated transport = do   serverAddr1 <- newEmptyMVar   serverAddr2 <- newEmptyMVar@@ -342,7 +362,7 @@   takeMVar clientDone  -- | Test (non-zero) timeout-testTimeout :: NT.Transport -> IO ()+testTimeout :: NT.Transport -> Assertion  testTimeout transport = do   localNode <- newLocalNode transport initRemoteTable   runProcess localNode $ do@@ -350,7 +370,7 @@     return ()  -- | Test zero timeout-testTimeout0 :: NT.Transport -> IO ()+testTimeout0 :: NT.Transport -> Assertion  testTimeout0 transport = do   serverAddr <- newEmptyMVar   clientDone <- newEmptyMVar@@ -384,7 +404,7 @@   takeMVar clientDone  -- | Test typed channels-testTypedChannels :: NT.Transport -> IO ()+testTypedChannels :: NT.Transport -> Assertion  testTypedChannels transport = do   serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))   clientDone <- newEmptyMVar@@ -410,7 +430,7 @@   takeMVar clientDone   -- | Test merging receive ports-testMergeChannels :: NT.Transport -> IO ()+testMergeChannels :: NT.Transport -> Assertion  testMergeChannels transport = do     localNode <- newLocalNode transport initRemoteTable     testFlat localNode True          "aaabbbccc"@@ -424,29 +444,34 @@   where     -- Single layer of merging     testFlat :: LocalNode -> Bool -> String -> IO () -    testFlat localNode biased expected = -      runProcess localNode $ do+    testFlat localNode biased expected = do+      done <- newEmptyMVar+      forkProcess localNode $ do         rs  <- mapM charChannel "abc"          m   <- mergePorts biased rs          xs  <- replicateM 9 $ receiveChan m          True <- return $ xs == expected-        return ()+        liftIO $ putMVar done ()+      takeMVar done      -- Two layers of merging     testNested :: LocalNode -> Bool -> Bool -> String -> IO ()-    testNested localNode biasedInner biasedOuter expected = -      runProcess localNode $ do+    testNested localNode biasedInner biasedOuter expected = do+      done <- newEmptyMVar+      forkProcess localNode $ do         rss  <- mapM (mapM charChannel) ["abc", "def", "ghi"]         ms   <- mapM (mergePorts biasedInner) rss         m    <- mergePorts biasedOuter ms         xs   <- replicateM (9 * 3) $ receiveChan m          True <- return $ xs == expected-        return ()+        liftIO $ putMVar done ()+      takeMVar done      -- 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 +      done <- newEmptyMVar        forkProcess localNode $ do         [sa, sb, sc] <- liftIO $ mapM readMVar vs @@ -477,14 +502,16 @@           , (sa, 'a')           ] -      runProcess localNode $ do+      forkProcess localNode $ do         (ss, rs) <- unzip <$> replicateM 3 newChan         liftIO $ mapM_ (uncurry putMVar) $ zip vs ss          m  <- mergePorts biased rs          xs <- replicateM (6 * 3) $ receiveChan m         True <- return $ xs == "abcacbbacbcacabcba"-        return ()+        liftIO $ putMVar done () +      takeMVar done+     mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)     mergePorts True  = mergePortsBiased     mergePorts False = mergePortsRR @@ -496,7 +523,7 @@       liftIO $ threadDelay 10000 -- Make sure messages have been sent       return rport -testTerminate :: NT.Transport -> IO ()+testTerminate :: NT.Transport -> Assertion  testTerminate transport = do   localNode <- newLocalNode transport initRemoteTable @@ -510,7 +537,7 @@     True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException      return () -testMonitorNode :: NT.Transport -> IO ()+testMonitorNode :: NT.Transport -> Assertion  testMonitorNode transport = do   [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable @@ -522,7 +549,7 @@     True <- return $ ref == ref' && nid == localNodeId node1     return () -testMonitorChannel :: NT.Transport -> IO ()+testMonitorChannel :: NT.Transport -> Assertion  testMonitorChannel transport = do     [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable     gotNotification <- newEmptyMVar@@ -543,7 +570,7 @@      takeMVar gotNotification -testRegistry :: NT.Transport -> IO ()+testRegistry :: NT.Transport -> Assertion  testRegistry transport = do   node <- newLocalNode transport initRemoteTable @@ -559,7 +586,7 @@     True <- return $ pingServer == pid'     return () -testRemoteRegistry :: NT.Transport -> IO ()+testRemoteRegistry :: NT.Transport -> Assertion  testRemoteRegistry transport = do   node1 <- newLocalNode transport initRemoteTable   node2 <- newLocalNode transport initRemoteTable@@ -568,7 +595,11 @@    runProcess node2 $ do     let nid1 = localNodeId node1-    registerRemote nid1 "ping" pingServer+    registerRemoteAsync nid1 "ping" pingServer+    receiveWait [ +       matchIf (\(RegisterReply label' _) -> "ping" == label')+               (\(RegisterReply _ _) -> return ()) ]+     Just pid <- whereisRemote nid1 "ping"     True <- return $ pingServer == pid      us <- getSelfPid@@ -577,7 +608,7 @@     True <- return $ pingServer == pid'     return () -testSpawnLocal :: NT.Transport -> IO ()+testSpawnLocal :: NT.Transport -> Assertion  testSpawnLocal transport = do   node <- newLocalNode transport initRemoteTable @@ -595,7 +626,7 @@     send pid sport     expect -testReconnect :: NT.Transport -> TransportInternals -> IO ()+testReconnect :: NT.Transport -> TransportInternals -> Assertion  testReconnect transport transportInternals = do   [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable   let nid1 = localNodeId node1@@ -639,8 +670,14 @@      -}          us <- getSelfPid-    registerRemote nid1 "a" us >> liftIO (threadDelay 100000) -- registerRemote is asynchronous+    registerRemoteAsync nid1 "a" us -- registerRemote is asynchronous+    receiveWait [+        matchIf (\(RegisterReply label' _) -> "a" == label')+                (\(RegisterReply _ _) -> return ()) ] +    Just _  <- whereisRemote nid1 "a" +      +     -- Simulate network failure     liftIO $ do        sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)@@ -648,14 +685,20 @@       threadDelay 10000      -- This will happen due to implicit reconnect -    registerRemote nid1 "b" us+    registerRemoteAsync nid1 "b" us+    receiveWait [+        matchIf (\(RegisterReply label' _) -> "b" == label')+                (\(RegisterReply _ _) -> return ()) ]      -- Should happen-    registerRemote nid1 "c" us+    registerRemoteAsync nid1 "c" us+    receiveWait [+        matchIf (\(RegisterReply label' _) -> "c" == label')+                (\(RegisterReply _ _) -> return ()) ]      -- Check-    Just _  <- whereisRemote nid1 "a" -    Just _  <- whereisRemote nid1 "b" +    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"       liftIO $ putMVar registerTestOk ()@@ -664,7 +707,7 @@  -- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server  -- in between-testMatchAny :: NT.Transport -> IO ()+testMatchAny :: NT.Transport -> Assertion  testMatchAny transport = do   proxyAddr <- newEmptyMVar    clientDone <- newEmptyMVar@@ -695,48 +738,157 @@    takeMVar clientDone -main :: IO ()-main = do-  Right (transport, transportInternals) <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters-  runTests -    [ ("Ping",             testPing             transport)-    , ("Math",             testMath             transport) -    , ("Timeout",          testTimeout          transport)-    , ("Timeout0",         testTimeout0         transport)-    , ("SendToTerminated", testSendToTerminated transport) -    , ("TypedChannnels",   testTypedChannels    transport)-    , ("MergeChannels",    testMergeChannels    transport)-    , ("Terminate",        testTerminate        transport)-    , ("Registry",         testRegistry         transport)-    , ("RemoteRegistry",   testRemoteRegistry   transport)-    , ("SpawnLocal",       testSpawnLocal       transport)-    , ("MatchAny",         testMatchAny         transport)+-- Test 'receiveChanTimeout'+testReceiveChanTimeout :: NT.Transport -> Assertion +testReceiveChanTimeout transport = do+  done <- newEmptyMVar+  sendPort <- newEmptyMVar+  +  forkTry $ do+    localNode <- newLocalNode transport initRemoteTable+    runProcess localNode $ do+      -- Create a typed channel+      (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 +      Nothing <- receiveChanTimeout 1000000 rp++      -- Wait for a message with a delay again. Now a message arrives after 0.5 seconds+      Just True <- receiveChanTimeout 1000000 rp++      -- Wait for a message with zero timeout: non-blocking check. No message is available, we get Nothing+      Nothing <- receiveChanTimeout 0 rp++      -- Again, but now there is a message available+      liftIO $ threadDelay 1000000+      Just False <- receiveChanTimeout 0 rp ++      liftIO $ putMVar done ()++  forkTry $ do+    localNode <- newLocalNode transport initRemoteTable+    runProcess localNode $ do+      sp <- liftIO $ readMVar sendPort++      liftIO $ threadDelay 1500000+      sendChan sp True++      liftIO $ threadDelay 500000+      sendChan sp False++  takeMVar done++-- | Test Functor, Applicative, Alternative and Monad instances for ReceiveChan+testReceiveChanFeatures :: NT.Transport -> Assertion+testReceiveChanFeatures transport = do+  done <- newEmptyMVar++  forkTry $ do+    localNode <- newLocalNode transport initRemoteTable+    runProcess localNode $ do+      (spInt,  rpInt)  <- newChan :: Process (SendPort Int, ReceivePort Int)+      (spBool, rpBool) <- newChan :: Process (SendPort Bool, ReceivePort Bool)++      -- Test Functor instance++      sendChan spInt 2+      sendChan spBool False++      rp1 <- mergePortsBiased [even <$> rpInt, rpBool]++      True <- receiveChan rp1+      False <- receiveChan rp1++      -- Test Applicative instance++      sendChan spInt 3+      sendChan spInt 4++      let rp2 = pure (+) <*> rpInt <*> rpInt++      7 <- receiveChan rp2++      -- Test Alternative instance+      +      sendChan spInt 3+      sendChan spBool True ++      let rp3 = (even <$> rpInt) <|> rpBool++      False <- receiveChan rp3+      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 +                     then rpInt+                     else return 7++      5 <- receiveChan rp4+      7 <- receiveChan rp4++      liftIO $ putMVar done ()++  takeMVar done++tests :: (NT.Transport, TransportInternals)  -> [Test]+tests (transport, transportInternals) = [ +    testGroup "Basic features" [+        testCase "Ping"                (testPing                transport)+      , testCase "Math"                (testMath                transport) +      , testCase "Timeout"             (testTimeout             transport)+      , testCase "Timeout0"            (testTimeout0            transport)+      , testCase "SendToTerminated"    (testSendToTerminated    transport) +      , testCase "TypedChannnels"      (testTypedChannels       transport)+      , testCase "MergeChannels"       (testMergeChannels       transport)+      , testCase "Terminate"           (testTerminate           transport)+      , testCase "Registry"            (testRegistry            transport)+      , testCase "RemoteRegistry"      (testRemoteRegistry      transport)+      , testCase "SpawnLocal"          (testSpawnLocal          transport)+      , testCase "MatchAny"            (testMatchAny            transport)+      , testCase "ReceiveChanTimeout"  (testReceiveChanTimeout  transport)+      , testCase "ReceiveChanFeatures" (testReceiveChanFeatures 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        -- happen before the unmonitor or unlink-    , ("MonitorUnreachable",           testMonitorUnreachable         transport True  False)-    , ("MonitorNormalTermination",     testMonitorNormalTermination   transport True  False)-    , ("MonitorAbnormalTermination",   testMonitorAbnormalTermination transport True  False)-    , ("MonitorLocalDeadProcess",      testMonitorLocalDeadProcess    transport True  False)-    , ("MonitorRemoteDeadProcess",     testMonitorRemoteDeadProcess   transport True  False)-    , ("MonitorDisconnect",            testMonitorDisconnect          transport True  False)-    , ("LinkUnreachable",              testMonitorUnreachable         transport False False)-    , ("LinkNormalTermination",        testMonitorNormalTermination   transport False False)-    , ("LinkAbnormalTermination",      testMonitorAbnormalTermination transport False False)-    , ("LinkLocalDeadProcess",         testMonitorLocalDeadProcess    transport False False)-    , ("LinkRemoteDeadProcess",        testMonitorRemoteDeadProcess   transport False False)-    , ("LinkDisconnect",               testMonitorDisconnect          transport False False)-    , ("UnmonitorNormalTermination",   testMonitorNormalTermination   transport True  True)-    , ("UnmonitorAbnormalTermination", testMonitorAbnormalTermination transport True  True)-    , ("UnmonitorDisconnect",          testMonitorDisconnect          transport True  True)-    , ("UnlinkNormalTermination",      testMonitorNormalTermination   transport False True)-    , ("UnlinkAbnormalTermination",    testMonitorAbnormalTermination transport False True)-    , ("UnlinkDisconnect",             testMonitorDisconnect          transport False True)+      testCase "MonitorUnreachable"           (testMonitorUnreachable         transport True  False)+    , testCase "MonitorNormalTermination"     (testMonitorNormalTermination   transport True  False)+    , testCase "MonitorAbnormalTermination"   (testMonitorAbnormalTermination transport True  False)+    , testCase "MonitorLocalDeadProcess"      (testMonitorLocalDeadProcess    transport True  False)+    , testCase "MonitorRemoteDeadProcess"     (testMonitorRemoteDeadProcess   transport True  False)+    , testCase "MonitorDisconnect"            (testMonitorDisconnect          transport True  False)+    , testCase "LinkUnreachable"              (testMonitorUnreachable         transport False False)+    , testCase "LinkNormalTermination"        (testMonitorNormalTermination   transport False False)+    , testCase "LinkAbnormalTermination"      (testMonitorAbnormalTermination transport False False)+    , testCase "LinkLocalDeadProcess"         (testMonitorLocalDeadProcess    transport False False)+    , testCase "LinkRemoteDeadProcess"        (testMonitorRemoteDeadProcess   transport False False)+    , testCase "LinkDisconnect"               (testMonitorDisconnect          transport False False)+    , testCase "UnmonitorNormalTermination"   (testMonitorNormalTermination   transport True  True)+    , testCase "UnmonitorAbnormalTermination" (testMonitorAbnormalTermination transport True  True)+    , testCase "UnmonitorDisconnect"          (testMonitorDisconnect          transport True  True)+    , testCase "UnlinkNormalTermination"      (testMonitorNormalTermination   transport False True)+    , testCase "UnlinkAbnormalTermination"    (testMonitorAbnormalTermination transport False True)+    , testCase "UnlinkDisconnect"             (testMonitorDisconnect          transport False True)       -- Monitoring nodes and channels-    , ("MonitorNode",                  testMonitorNode                transport)-    , ("MonitorChannel",               testMonitorChannel             transport)+    , testCase "MonitorNode"                  (testMonitorNode                transport)+    , testCase "MonitorChannel"               (testMonitorChannel             transport)       -- Reconnect-    , ("Reconnect",                    testReconnect                  transport transportInternals)+    , testCase "Reconnect"                    (testReconnect                  transport transportInternals)     ]+  ]++main :: IO ()+main = do+  Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters+  defaultMain (tests transport)
tests/TestClosure.hs view
@@ -29,8 +29,15 @@ import Control.Distributed.Process.Node import Control.Distributed.Process.Internal.Types (NodeId(nodeAddress)) import Control.Distributed.Static (staticLabel, staticClosure)-import TestAuxiliary +import Test.HUnit (Assertion)+import Test.Framework (Test, defaultMain)+import Test.Framework.Providers.HUnit (testCase)++--------------------------------------------------------------------------------+-- Supporting definitions                                                     --+--------------------------------------------------------------------------------+ quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e) quintuple a b c d e = (a, b, c, d, e) @@ -133,7 +140,18 @@ waitClosure :: Int -> Closure (Process ()) waitClosure = $(mkClosure 'wait)  -testUnclosure :: Transport -> RemoteTable -> IO ()+simulateNetworkFailure :: TransportInternals -> NodeId -> NodeId -> Process ()+simulateNetworkFailure transportInternals fr to = liftIO $ do+  threadDelay 10000+  sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to) +  sClose sock+  threadDelay 10000++--------------------------------------------------------------------------------+-- The tests proper                                                           --+--------------------------------------------------------------------------------++testUnclosure :: Transport -> RemoteTable -> Assertion testUnclosure transport rtable = do   node <- newLocalNode transport rtable   done <- newEmptyMVar@@ -142,7 +160,7 @@     liftIO $ putMVar done ()   takeMVar done -testBind :: Transport -> RemoteTable -> IO ()+testBind :: Transport -> RemoteTable -> Assertion  testBind transport rtable = do   node <- newLocalNode transport rtable   done <- newEmptyMVar@@ -153,7 +171,7 @@     liftIO $ putMVar done ()   takeMVar done -testSendPureClosure :: Transport -> RemoteTable -> IO ()+testSendPureClosure :: Transport -> RemoteTable -> Assertion  testSendPureClosure transport rtable = do   serverAddr <- newEmptyMVar   serverDone <- newEmptyMVar@@ -174,7 +192,7 @@    takeMVar serverDone -testSendIOClosure :: Transport -> RemoteTable -> IO ()+testSendIOClosure :: Transport -> RemoteTable -> Assertion  testSendIOClosure transport rtable = do   serverAddr <- newEmptyMVar   serverDone <- newEmptyMVar@@ -198,7 +216,7 @@    takeMVar serverDone -testSendProcClosure :: Transport -> RemoteTable -> IO ()+testSendProcClosure :: Transport -> RemoteTable -> Assertion  testSendProcClosure transport rtable = do   serverAddr <- newEmptyMVar   clientDone <- newEmptyMVar@@ -222,7 +240,7 @@    takeMVar clientDone -testSpawn :: Transport -> RemoteTable -> IO ()+testSpawn :: Transport -> RemoteTable -> Assertion  testSpawn transport rtable = do   serverNodeAddr <- newEmptyMVar   clientDone <- newEmptyMVar@@ -243,7 +261,7 @@    takeMVar clientDone -testCall :: Transport -> RemoteTable -> IO ()+testCall :: Transport -> RemoteTable -> Assertion  testCall transport rtable = do   serverNodeAddr <- newEmptyMVar   clientDone <- newEmptyMVar@@ -261,7 +279,7 @@    takeMVar clientDone -testCallBind :: Transport -> RemoteTable -> IO ()+testCallBind :: Transport -> RemoteTable -> Assertion  testCallBind transport rtable = do   serverNodeAddr <- newEmptyMVar   clientDone <- newEmptyMVar@@ -279,7 +297,7 @@    takeMVar clientDone -testSeq :: Transport -> RemoteTable -> IO ()+testSeq :: Transport -> RemoteTable -> Assertion  testSeq transport rtable = do   node <- newLocalNode transport rtable   done <- newEmptyMVar@@ -297,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 -> IO ()+testSpawnSupervised :: Transport -> RemoteTable -> Assertion  testSpawnSupervised transport rtable = do     [node1, node2]       <- replicateM 2 $ newLocalNode transport rtable     [superPid, childPid] <- replicateM 2 $ newEmptyMVar@@ -326,7 +344,7 @@     supervisorDeath :: IOException     supervisorDeath = userError "Supervisor died" -testSpawnInvalid :: Transport -> RemoteTable -> IO ()+testSpawnInvalid :: Transport -> RemoteTable -> Assertion  testSpawnInvalid transport rtable = do   node <- newLocalNode transport rtable   done <- newEmptyMVar@@ -338,7 +356,7 @@     liftIO $ putMVar done ()   takeMVar done -testClosureExpect :: Transport -> RemoteTable -> IO ()+testClosureExpect :: Transport -> RemoteTable -> Assertion  testClosureExpect transport rtable = do   node <- newLocalNode transport rtable   done <- newEmptyMVar@@ -351,7 +369,7 @@     liftIO $ putMVar done ()   takeMVar done -testSpawnChannel :: Transport -> RemoteTable -> IO ()+testSpawnChannel :: Transport -> RemoteTable -> Assertion  testSpawnChannel transport rtable = do   done <- newEmptyMVar   [node1, node2] <- replicateM 2 $ newLocalNode transport rtable@@ -368,7 +386,7 @@    takeMVar done -testTDict :: Transport -> RemoteTable -> IO ()+testTDict :: Transport -> RemoteTable -> Assertion  testTDict transport rtable = do   done <- newEmptyMVar   [node1, node2] <- replicateM 2 $ newLocalNode transport rtable@@ -377,14 +395,7 @@     liftIO $ putMVar done ()   takeMVar done -simulateNetworkFailure :: TransportInternals -> NodeId -> NodeId -> Process ()-simulateNetworkFailure transportInternals fr to = liftIO $ do-  threadDelay 10000-  sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to) -  sClose sock-  threadDelay 10000--testFib :: Transport -> RemoteTable -> IO ()+testFib :: Transport -> RemoteTable -> Assertion  testFib transport rtable = do   nodes <- replicateM 4 $ newLocalNode transport rtable   done <- newEmptyMVar@@ -397,7 +408,7 @@    takeMVar done -testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> IO ()+testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> Assertion  testSpawnReconnect transport rtable transportInternals = do   [node1, node2] <- replicateM 2 $ newLocalNode transport rtable    let nid1 = localNodeId node1@@ -426,7 +437,7 @@  -- | 'spawn' used to ave a race condition which would be triggered if the -- spawning process terminates immediately after spawning-testSpawnTerminate :: Transport -> RemoteTable -> IO ()+testSpawnTerminate :: Transport -> RemoteTable -> Assertion  testSpawnTerminate transport rtable = do   slave  <- newLocalNode transport rtable    master <- newLocalNode transport rtable @@ -440,26 +451,30 @@    takeMVar masterDone +tests :: (Transport, TransportInternals) -> RemoteTable -> [Test]+tests (transport, transportInternals) rtable = [+    testCase "Unclosure"       (testUnclosure       transport rtable)+  , testCase "Bind"            (testBind            transport rtable)+  , testCase "SendPureClosure" (testSendPureClosure transport rtable)+  , testCase "SendIOClosure"   (testSendIOClosure   transport rtable)+  , testCase "SendProcClosure" (testSendProcClosure transport rtable)+  , testCase "Spawn"           (testSpawn           transport rtable)+  , testCase "Call"            (testCall            transport rtable)+  , testCase "CallBind"        (testCallBind        transport rtable)+  , testCase "Seq"             (testSeq             transport rtable)+  , testCase "SpawnSupervised" (testSpawnSupervised transport rtable)+  , testCase "SpawnInvalid"    (testSpawnInvalid    transport rtable)+  , testCase "ClosureExpect"   (testClosureExpect   transport rtable)+  , testCase "SpawnChannel"    (testSpawnChannel    transport rtable)+  , testCase "TDict"           (testTDict           transport rtable)+  , testCase "Fib"             (testFib             transport rtable)+  , testCase "SpawnTerminate"  (testSpawnTerminate  transport rtable)+  , testCase "SpawnReconnect"  (testSpawnReconnect  transport rtable transportInternals)+  ]++ main :: IO () main = do-  Right (transport, transportInternals) <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters+  Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters   let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable -  runTests -    [ ("Unclosure",       testUnclosure       transport rtable)-    , ("Bind",            testBind            transport rtable)-    , ("SendPureClosure", testSendPureClosure transport rtable)-    , ("SendIOClosure",   testSendIOClosure   transport rtable)-    , ("SendProcClosure", testSendProcClosure transport rtable)-    , ("Spawn",           testSpawn           transport rtable)-    , ("Call",            testCall            transport rtable)-    , ("CallBind",        testCallBind        transport rtable)-    , ("Seq",             testSeq             transport rtable)-    , ("SpawnSupervised", testSpawnSupervised transport rtable)-    , ("SpawnInvalid",    testSpawnInvalid    transport rtable)-    , ("ClosureExpect",   testClosureExpect   transport rtable)-    , ("SpawnChannel",    testSpawnChannel    transport rtable)-    , ("TDict",           testTDict           transport rtable)-    , ("Fib",             testFib             transport rtable)-    , ("SpawnTerminate",  testSpawnTerminate  transport rtable)-    , ("SpawnReconnect",  testSpawnReconnect  transport rtable transportInternals)-    ]+  defaultMain (tests transport rtable)