packages feed

distributed-process 0.3.0 → 0.3.1

raw patch · 11 files changed

+342/−193 lines, 11 filesdep +distributed-processdep ~distributed-staticdep ~ghc-primdep ~template-haskellPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: distributed-process

Dependency ranges changed: distributed-static, ghc-prim, template-haskell

API changes (from Hackage documentation)

- Control.Distributed.Process: Closure :: Static (ByteString -> a) -> ByteString -> Closure a
+ Control.Distributed.Process: closure :: Static (ByteString -> a) -> ByteString -> Closure a
+ Control.Distributed.Process: reconnect :: ProcessId -> Process ()
+ Control.Distributed.Process: reconnectNode :: NodeId -> Process ()
+ Control.Distributed.Process: reconnectPort :: SendPort a -> Process ()
+ Control.Distributed.Process.Internal.Node: reconnect :: LocalNode -> Identifier -> Identifier -> IO ()
+ Control.Distributed.Process.Internal.Primitives: reconnect :: ProcessId -> Process ()
+ Control.Distributed.Process.Internal.Primitives: reconnectNode :: NodeId -> Process ()
+ Control.Distributed.Process.Internal.Primitives: reconnectPort :: SendPort a -> Process ()
+ Control.Distributed.Process.Internal.StrictMVar: data StrictMVar a
+ Control.Distributed.Process.Internal.StrictMVar: modifyMVar :: StrictMVar a -> (a -> IO (a, b)) -> IO b
+ Control.Distributed.Process.Internal.StrictMVar: modifyMVar_ :: StrictMVar a -> (a -> IO a) -> IO ()
+ Control.Distributed.Process.Internal.StrictMVar: newEmptyMVar :: IO (StrictMVar a)
+ Control.Distributed.Process.Internal.StrictMVar: newMVar :: a -> IO (StrictMVar a)
+ Control.Distributed.Process.Internal.StrictMVar: putMVar :: StrictMVar a -> a -> IO ()
+ Control.Distributed.Process.Internal.StrictMVar: takeMVar :: StrictMVar a -> IO a
+ Control.Distributed.Process.Internal.StrictMVar: withMVar :: StrictMVar a -> (a -> IO b) -> IO b
+ Control.Distributed.Process.Internal.Types: Reconnect :: Identifier -> ProcessSignal
- Control.Distributed.Process.Internal.Types: LocalNode :: NodeId -> EndPoint -> MVar LocalNodeState -> Chan NCMsg -> RemoteTable -> LocalNode
+ Control.Distributed.Process.Internal.Types: LocalNode :: NodeId -> EndPoint -> StrictMVar LocalNodeState -> Chan NCMsg -> RemoteTable -> LocalNode
- Control.Distributed.Process.Internal.Types: LocalNodeState :: Map LocalProcessId LocalProcess -> Int32 -> Int32 -> Map (Identifier, Identifier) Connection -> LocalNodeState
+ Control.Distributed.Process.Internal.Types: LocalNodeState :: !Map LocalProcessId LocalProcess -> !Int32 -> !Int32 -> !Map (Identifier, Identifier) Connection -> LocalNodeState
- Control.Distributed.Process.Internal.Types: LocalProcess :: CQueue Message -> ProcessId -> MVar LocalProcessState -> ThreadId -> LocalNode -> LocalProcess
+ Control.Distributed.Process.Internal.Types: LocalProcess :: CQueue Message -> ProcessId -> StrictMVar LocalProcessState -> ThreadId -> LocalNode -> LocalProcess
- Control.Distributed.Process.Internal.Types: LocalProcessId :: Int32 -> Int32 -> LocalProcessId
+ Control.Distributed.Process.Internal.Types: LocalProcessId :: {-# UNPACK #-} !Int32 -> {-# UNPACK #-} !Int32 -> LocalProcessId
- Control.Distributed.Process.Internal.Types: LocalProcessState :: Int32 -> Int32 -> Int32 -> Map LocalSendPortId TypedChannel -> LocalProcessState
+ Control.Distributed.Process.Internal.Types: LocalProcessState :: !Int32 -> !Int32 -> !Int32 -> !Map LocalSendPortId TypedChannel -> LocalProcessState
- Control.Distributed.Process.Internal.Types: MonitorRef :: Identifier -> Int32 -> MonitorRef
+ Control.Distributed.Process.Internal.Types: MonitorRef :: !Identifier -> !Int32 -> MonitorRef
- Control.Distributed.Process.Internal.Types: ProcessId :: NodeId -> LocalProcessId -> ProcessId
+ Control.Distributed.Process.Internal.Types: ProcessId :: !NodeId -> {-# UNPACK #-} !LocalProcessId -> ProcessId
- Control.Distributed.Process.Internal.Types: SendPortId :: ProcessId -> LocalSendPortId -> SendPortId
+ Control.Distributed.Process.Internal.Types: SendPortId :: {-# UNPACK #-} !ProcessId -> {-# UNPACK #-} !LocalSendPortId -> SendPortId
- Control.Distributed.Process.Internal.Types: _channelCounter :: LocalProcessState -> Int32
+ Control.Distributed.Process.Internal.Types: _channelCounter :: LocalProcessState -> !Int32
- Control.Distributed.Process.Internal.Types: _localConnections :: LocalNodeState -> Map (Identifier, Identifier) Connection
+ Control.Distributed.Process.Internal.Types: _localConnections :: LocalNodeState -> !Map (Identifier, Identifier) Connection
- Control.Distributed.Process.Internal.Types: _localPidCounter :: LocalNodeState -> Int32
+ Control.Distributed.Process.Internal.Types: _localPidCounter :: LocalNodeState -> !Int32
- Control.Distributed.Process.Internal.Types: _localPidUnique :: LocalNodeState -> Int32
+ Control.Distributed.Process.Internal.Types: _localPidUnique :: LocalNodeState -> !Int32
- Control.Distributed.Process.Internal.Types: _localProcesses :: LocalNodeState -> Map LocalProcessId LocalProcess
+ Control.Distributed.Process.Internal.Types: _localProcesses :: LocalNodeState -> !Map LocalProcessId LocalProcess
- Control.Distributed.Process.Internal.Types: _monitorCounter :: LocalProcessState -> Int32
+ Control.Distributed.Process.Internal.Types: _monitorCounter :: LocalProcessState -> !Int32
- Control.Distributed.Process.Internal.Types: _spawnCounter :: LocalProcessState -> Int32
+ Control.Distributed.Process.Internal.Types: _spawnCounter :: LocalProcessState -> !Int32
- Control.Distributed.Process.Internal.Types: _typedChannels :: LocalProcessState -> Map LocalSendPortId TypedChannel
+ Control.Distributed.Process.Internal.Types: _typedChannels :: LocalProcessState -> !Map LocalSendPortId TypedChannel
- Control.Distributed.Process.Internal.Types: localState :: LocalNode -> MVar LocalNodeState
+ Control.Distributed.Process.Internal.Types: localState :: LocalNode -> StrictMVar LocalNodeState
- Control.Distributed.Process.Internal.Types: lpidCounter :: LocalProcessId -> Int32
+ Control.Distributed.Process.Internal.Types: lpidCounter :: LocalProcessId -> {-# UNPACK #-} !Int32
- Control.Distributed.Process.Internal.Types: lpidUnique :: LocalProcessId -> Int32
+ Control.Distributed.Process.Internal.Types: lpidUnique :: LocalProcessId -> {-# UNPACK #-} !Int32
- Control.Distributed.Process.Internal.Types: monitorRefCounter :: MonitorRef -> Int32
+ Control.Distributed.Process.Internal.Types: monitorRefCounter :: MonitorRef -> !Int32
- Control.Distributed.Process.Internal.Types: monitorRefIdent :: MonitorRef -> Identifier
+ Control.Distributed.Process.Internal.Types: monitorRefIdent :: MonitorRef -> !Identifier
- Control.Distributed.Process.Internal.Types: processLocalId :: ProcessId -> LocalProcessId
+ Control.Distributed.Process.Internal.Types: processLocalId :: ProcessId -> {-# UNPACK #-} !LocalProcessId
- Control.Distributed.Process.Internal.Types: processNodeId :: ProcessId -> NodeId
+ Control.Distributed.Process.Internal.Types: processNodeId :: ProcessId -> !NodeId
- Control.Distributed.Process.Internal.Types: processState :: LocalProcess -> MVar LocalProcessState
+ Control.Distributed.Process.Internal.Types: processState :: LocalProcess -> StrictMVar LocalProcessState
- Control.Distributed.Process.Internal.Types: sendPortLocalId :: SendPortId -> LocalSendPortId
+ Control.Distributed.Process.Internal.Types: sendPortLocalId :: SendPortId -> {-# UNPACK #-} !LocalSendPortId
- Control.Distributed.Process.Internal.Types: sendPortProcessId :: SendPortId -> ProcessId
+ Control.Distributed.Process.Internal.Types: sendPortProcessId :: SendPortId -> {-# UNPACK #-} !ProcessId

Files

distributed-process.cabal view
@@ -1,5 +1,5 @@ Name:          distributed-process -Version:       0.3.0+Version:       0.3.1 Cabal-Version: >=1.8 Build-Type:    Simple License:       BSD3 @@ -29,6 +29,10 @@   Location: https://github.com/haskell-distributed/distributed-process   SubDir:   distributed-process +flag th+  description: Build with Template Haskell support+  default: True+ Library   Build-Depends:     base >= 4.4 && < 5,                      binary >= 0.5 && < 0.6,@@ -41,10 +45,9 @@                      containers >= 0.4 && < 0.6,                      old-locale >= 1.0 && < 1.1,                      time >= 1.2 && < 1.5,-                     template-haskell >= 2.6 && < 2.8,                      random >= 1.0 && < 1.1,-                     ghc-prim >= 0.2 && < 0.3,-                     distributed-static >= 0.1 && < 0.2,+                     ghc-prim >= 0.2 && < 0.4,+                     distributed-static >= 0.2 && < 0.3,                      rank1dynamic >= 0.1 && < 0.2,                      syb >= 0.3 && < 0.4   Exposed-modules:   Control.Distributed.Process,@@ -54,9 +57,9 @@                      Control.Distributed.Process.Internal.Primitives,                      Control.Distributed.Process.Internal.CQueue,                      Control.Distributed.Process.Internal.Types,-                     Control.Distributed.Process.Internal.Closure.TH,                      Control.Distributed.Process.Internal.Closure.BuiltIn,                      Control.Distributed.Process.Internal.Node+                     Control.Distributed.Process.Internal.StrictMVar   Extensions:        RankNTypes,                      ScopedTypeVariables,                      FlexibleInstances,@@ -65,75 +68,46 @@                      GADTs,                      GeneralizedNewtypeDeriving,                      DeriveDataTypeable,-                     CPP+                     CPP,+                     BangPatterns   ghc-options:       -Wall   HS-Source-Dirs:    src+  if flag(th)+     Build-Depends:   template-haskell >= 2.6 && < 2.9+     Exposed-modules: Control.Distributed.Process.Internal.Closure.TH+     CPP-Options:     -DTemplateHaskellSupport  Test-Suite TestCH   Type:              exitcode-stdio-1.0   Main-Is:           TestCH.hs   Build-Depends:     base >= 4.4 && < 5,-                     binary >= 0.5 && < 0.6,-                     network-transport >= 0.2 && < 0.3,-                     network-transport-tcp >= 0.2 && < 0.3,-                     stm >= 2.3 && < 2.5,-                     transformers >= 0.2 && < 0.4,-                     mtl >= 2.0 && < 2.2,-                     data-accessor >= 0.2 && < 0.3,-                     bytestring >= 0.9 && < 0.11,-                     containers >= 0.4 && < 0.6,-                     old-locale >= 1.0 && < 1.1,-                     time >= 1.2 && < 1.5,-                     template-haskell >= 2.6 && < 2.8,                      random >= 1.0 && < 1.1,-                     ghc-prim >= 0.2 && < 0.3,                      ansi-terminal >= 0.5 && < 0.6,-                     distributed-static >= 0.1 && < 0.2,-                     rank1dynamic >= 0.1 && < 0.2-  Extensions:        RankNTypes,+                     distributed-process >= 0.3 && < 0.4,+                     network-transport >= 0.2 && < 0.3,+                     network-transport-tcp >= 0.2 && < 0.3,+                     binary >= 0.5 && < 0.6+  Other-modules:     TestAuxiliary+  Extensions:        CPP,                      ScopedTypeVariables,-                     FlexibleInstances,-                     UndecidableInstances,-                     ExistentialQuantification,-                     GADTs,-                     GeneralizedNewtypeDeriving,                      DeriveDataTypeable,-                     CPP+                     GeneralizedNewtypeDeriving   ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind -  HS-Source-Dirs:    tests src+  HS-Source-Dirs:    tests   Test-Suite TestClosure   Type:              exitcode-stdio-1.0   Main-Is:           TestClosure.hs   Build-Depends:     base >= 4.4 && < 5,-                     binary >= 0.5 && < 0.6,-                     network-transport >= 0.2 && < 0.3,-                     network-transport-tcp >= 0.2 && < 0.3,-                     stm >= 2.3 && < 2.5,-                     transformers >= 0.2 && < 0.4,-                     mtl >= 2.0 && < 2.2,-                     data-accessor >= 0.2 && < 0.3,-                     bytestring >= 0.9 && < 0.11,-                     containers >= 0.4 && < 0.6,-                     old-locale >= 1.0 && < 1.1,-                     time >= 1.2 && < 1.5,-                     template-haskell >= 2.6 && < 2.8,                      random >= 1.0 && < 1.1,-                     ghc-prim >= 0.2 && < 0.3,                      ansi-terminal >= 0.5 && < 0.6,-                     distributed-static >= 0.1 && < 0.2,-                     rank1dynamic >= 0.1 && < 0.2,-                     syb >= 0.3 && < 0.4+                     distributed-static >= 0.2 && < 0.3,+                     distributed-process >= 0.3 && < 0.4,+                     network-transport >= 0.2 && < 0.3,+                     network-transport-tcp >= 0.2 && < 0.3,+                     bytestring >= 0.9 && < 0.11   Other-modules:     TestAuxiliary-  Extensions:        RankNTypes,-                     ScopedTypeVariables,-                     FlexibleInstances,-                     UndecidableInstances,-                     ExistentialQuantification,-                     GADTs,-                     GeneralizedNewtypeDeriving,-                     DeriveDataTypeable,-                     TemplateHaskell,-                     CPP+  Extensions:        CPP, +                     ScopedTypeVariables   ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind -  HS-Source-Dirs:    tests src+  HS-Source-Dirs:    tests 
src/Control/Distributed/Process.hs view
@@ -63,7 +63,8 @@   , PortMonitorNotification(..)   , DiedReason(..)     -- * Closures-  , Closure(..)+  , Closure+  , closure   , Static   , unClosure   , RemoteTable@@ -98,6 +99,10 @@     -- * Local versions of 'spawn'    , spawnLocal   , spawnChannelLocal+    -- * Reconnecting+  , reconnect+  , reconnectNode+  , reconnectPort   ) where  #if ! MIN_VERSION_base(4,6,0)@@ -109,7 +114,8 @@ import Control.Applicative ((<$>)) import Control.Monad.Reader (ask) import Control.Distributed.Static -  ( Closure(..)+  ( Closure+  , closure   , Static   , RemoteTable   )@@ -206,6 +212,10 @@     -- Auxiliary API   , expectTimeout   , spawnAsync+    -- Reconnecting+  , reconnect+  , reconnectNode+  , reconnectPort   ) import Control.Distributed.Process.Serializable (Serializable) import Control.Distributed.Process.Node (forkProcess)@@ -294,14 +304,14 @@                    `seqCP` proc   mPid <- receiveWait      [ matchIf (\(DidSpawn ref _) -> ref == sRef)-              (\(DidSpawn _ pid) -> return $ Just pid)+              (\(DidSpawn _ pid) -> return $ Right pid)     , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef)-              (\_ -> return Nothing)+              (\(NodeMonitorNotification _ _ err) -> return $ Left err)     ]   unmonitor mRef   case mPid of-    Nothing  -> fail "spawn: remote node failed"-    Just pid -> send pid () >> return pid+    Left err  -> fail $ "spawn: remote node failed: " ++ show err+    Right pid -> send pid () >> return pid  -- | Spawn a process and link to it --@@ -333,7 +343,7 @@ call :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (Process a) -> Process a call dict nid proc = do    us <- getSelfPid-  (_, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)+  (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)   -- We are guaranteed to receive the reply before the monitor notification   -- (if a reply is sent at all)   -- NOTE: This might not be true if we switch to unreliable delivery.@@ -343,8 +353,17 @@               (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))     ]   case mResult of-    Right a  -> unmonitor mRef >> return a-    Left err -> fail $ "call: remote process died: " ++ show err +    Right a  -> do+      -- 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   -- | Spawn a child process, have the child link to the parent and the parent -- monitor the child
src/Control/Distributed/Process/Closure.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | /Towards Haskell in the Cloud/ (Epstein et al., Haskell Symposium 2011) -- proposes a new type construct called 'static' that characterizes values that -- are known statically. Cloud Haskell uses the@@ -156,14 +157,8 @@ --      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 -  ( -- * Template Haskell support for creating static values and closures -    remotable-  , mkStatic-  , mkClosure-  , functionSDict-  , functionTDict-    -- * Serialization dictionaries (and their static versions)-  , SerializableDict(..)+  ( -- * Serialization dictionaries (and their static versions)+    SerializableDict(..)   , staticDecode   , sdictUnit   , sdictProcessId@@ -181,16 +176,17 @@   , cpSend   , cpExpect   , cpNewChan-  ) where --import Control.Distributed.Process.Serializable (SerializableDict(..))-import Control.Distributed.Process.Internal.Closure.TH -  ( remotable+#ifdef TemplateHaskellSupport +    -- * Template Haskell support for creating static values and closures +  , remotable   , mkStatic+  , mkClosure   , functionSDict   , functionTDict-  , mkClosure-  )+#endif+  ) where ++import Control.Distributed.Process.Serializable (SerializableDict(..)) import Control.Distributed.Process.Internal.Closure.BuiltIn   ( -- Static dictionaries and associated operations     staticDecode@@ -211,3 +207,12 @@   , cpExpect   , cpNewChan   )+#ifdef TemplateHaskellSupport +import Control.Distributed.Process.Internal.Closure.TH +  ( remotable+  , mkStatic+  , functionSDict+  , functionTDict+  , mkClosure+  )+#endif
src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs view
@@ -33,7 +33,8 @@   , Static   , staticLabel   , staticApply-  , Closure(Closure)+  , Closure+  , closure   , closureApplyStatic   , closureApply   , staticCompose@@ -159,7 +160,7 @@ -- | 'CP' version of 'Control.Monad.return' returnCP :: forall a. Serializable a           => Static (SerializableDict a) -> a -> Closure (Process a)-returnCP dict x = Closure decoder (encode x)+returnCP dict x = closure decoder (encode x)   where     decoder :: Static (ByteString -> Process a)     decoder = returnProcessStatic@@ -193,14 +194,14 @@  -- | '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"  -- | 'CP' version of 'unlink' cpUnlink :: ProcessId -> Closure (Process ())-cpUnlink = Closure (unlinkStatic `staticCompose` decodeProcessIdStatic) . encode+cpUnlink = closure (unlinkStatic `staticCompose` decodeProcessIdStatic) . encode   where     unlinkStatic :: Static (ProcessId -> Process ())     unlinkStatic = staticLabel "$unlink"@@ -208,7 +209,7 @@ -- | 'CP' version of 'send' cpSend :: forall a. Typeable a         => Static (SerializableDict a) -> ProcessId -> CP a () -cpSend dict pid = Closure decoder (encode pid)+cpSend dict pid = closure decoder (encode pid)   where     decoder :: Static (ByteString -> a -> Process ())     decoder = (sendDictStatic `staticApply` dict)
src/Control/Distributed/Process/Internal/Closure/TH.hs view
@@ -52,7 +52,7 @@   , registerStatic   , Static   , staticLabel-  , Closure(Closure)+  , closure   , staticCompose   ) import Control.Distributed.Process.Internal.Types (Process)@@ -97,7 +97,7 @@  mkClosure :: Name -> Q Exp mkClosure n = -  [|   Closure ($(mkStatic n) `staticCompose` staticDecode $(functionSDict n)) +  [|   closure ($(mkStatic n) `staticCompose` staticDecode $(functionSDict n))       . encode   |] 
src/Control/Distributed/Process/Internal/Node.hs view
@@ -3,13 +3,14 @@     sendPayload   , sendBinary   , sendMessage+  , reconnect   ) where  import Data.Accessor ((^.), (^=)) import Data.Binary (Binary, encode) import qualified Data.ByteString.Lazy as BSL (toChunks) import qualified Data.ByteString as BSS (ByteString)-import Control.Concurrent.MVar (withMVar, modifyMVar_)+import Control.Distributed.Process.Internal.StrictMVar (withMVar, modifyMVar_) import Control.Concurrent.Chan (writeChan) import Control.Monad (unless) import qualified Network.Transport as NT @@ -18,6 +19,7 @@   , defaultConnectHints   , connect   , Reliability(ReliableOrdered)+  , close   ) import Control.Distributed.Process.Internal.Types    ( LocalNode(localState, localEndPoint, localCtrlChan)@@ -44,8 +46,8 @@     Just conn -> do       didSend <- NT.send conn payload       case didSend of-        Left _  -> return False-        Right _ -> return True +        Left _err -> return False +        Right ()  -> return True      Nothing -> return False   unless didSend $     writeChan (localCtrlChan node) NCMsg@@ -89,3 +91,13 @@       Nothing   -> setupConnBetween node from to    where     nodeState = localState node++reconnect :: LocalNode -> Identifier -> Identifier -> IO ()+reconnect node from to =+  modifyMVar_ (localState node) $ \st -> +    case st ^. localConnectionBetween from to of+      Nothing -> +        return st+      Just conn -> do+        NT.close conn +        return (localConnectionBetween from to ^= Nothing $ st)
src/Control/Distributed/Process/Internal/Primitives.hs view
@@ -59,6 +59,10 @@   , unlinkPort   , monitorNode   , monitorPort+    -- * Reconnecting+  , reconnect+  , reconnectNode+  , reconnectPort   ) where  #if ! MIN_VERSION_base(4,6,0)@@ -74,7 +78,7 @@ import Control.Applicative ((<$>)) import Control.Exception (Exception, throwIO, SomeException) import qualified Control.Exception as Ex (catch, mask)-import Control.Concurrent.MVar (modifyMVar)+import Control.Distributed.Process.Internal.StrictMVar (modifyMVar) import Control.Concurrent.Chan (writeChan) import Control.Concurrent.STM    ( STM@@ -346,7 +350,7 @@  -- | Lift 'Control.Exception.bracket' bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c-bracket before after thing = do+bracket before after thing =   mask $ \restore -> do     a <- before     r <- restore (thing a) `onException` after a@@ -505,6 +509,44 @@   case Static.unclosure rtable closure of     Left err -> fail $ "Could not resolve closure: " ++ err      Right x  -> return x++--------------------------------------------------------------------------------+-- Reconnecting                                                               --+--------------------------------------------------------------------------------++-- | Cloud Haskell provides the illusion of connection-less, reliable, ordered+-- 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 +-- 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 +-- indicates that it is acceptable to attempt to reconnect to B using the+-- 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,+-- then receives a monitor notification that its connection to B has been lost,+-- calls 'reconnect' and then sends m3, it is possible that B will receive m1+-- and m3 but not m2.+--+-- Note that 'reconnect' does not mean /reconnect now/ but rather /it is okay+-- to attempt to reconnect on the next send/. In particular, if no further+-- communication attempts are made to B then A can use reconnect to clean up+-- its connection to B.+reconnect :: ProcessId -> Process ()+reconnect = +  sendCtrlMsg Nothing . Reconnect . ProcessIdentifier ++-- | Reconnect to a node. See 'reconnect' for more information.+reconnectNode :: NodeId -> Process ()+reconnectNode = +  sendCtrlMsg Nothing . Reconnect . NodeIdentifier ++-- | Reconnect to a sendport. See 'reconnect' for more information.+reconnectPort :: SendPort a -> Process ()+reconnectPort = +  sendCtrlMsg Nothing . Reconnect . SendPortIdentifier . sendPortId  -------------------------------------------------------------------------------- -- Auxiliary functions                                                        --
+ src/Control/Distributed/Process/Internal/StrictMVar.hs view
@@ -0,0 +1,51 @@+-- | Like Control.Concurrent.MVar.Strict but reduce to HNF, not NF+module Control.Distributed.Process.Internal.StrictMVar +  ( StrictMVar+  , newEmptyMVar+  , newMVar+  , takeMVar+  , putMVar+  , withMVar+  , modifyMVar_+  , modifyMVar+  ) where++import Control.Applicative ((<$>))+import Control.Monad ((>=>))+import Control.Exception (evaluate)+import qualified Control.Concurrent.MVar as MVar +  ( MVar+  , newEmptyMVar+  , newMVar+  , takeMVar+  , putMVar+  , withMVar+  , modifyMVar_+  , modifyMVar+  )++newtype StrictMVar a = StrictMVar (MVar.MVar a)++newEmptyMVar :: IO (StrictMVar a)+newEmptyMVar = StrictMVar <$> MVar.newEmptyMVar++newMVar :: a -> IO (StrictMVar a)+newMVar x = evaluate x >> StrictMVar <$> MVar.newMVar x++takeMVar :: StrictMVar a -> IO a+takeMVar (StrictMVar v) = MVar.takeMVar v++putMVar :: StrictMVar a -> a -> IO ()+putMVar (StrictMVar v) x = evaluate x >> MVar.putMVar v x++withMVar :: StrictMVar a -> (a -> IO b) -> IO b+withMVar (StrictMVar v) = MVar.withMVar v++modifyMVar_ :: StrictMVar a -> (a -> IO a) -> IO ()+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)+  where+    evaluateFst :: (a, b) -> IO (a, b)+    evaluateFst (x, y) = evaluate x >> return (x, y)
src/Control/Distributed/Process/Internal/Types.hs view
@@ -77,7 +77,6 @@ import Control.Category ((>>>)) import Control.Exception (Exception) import Control.Concurrent (ThreadId)-import Control.Concurrent.MVar (MVar) import Control.Concurrent.Chan (Chan) import Control.Concurrent.STM (TChan, TVar) import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)@@ -94,6 +93,7 @@   , showFingerprint   ) import Control.Distributed.Process.Internal.CQueue (CQueue)+import Control.Distributed.Process.Internal.StrictMVar (StrictMVar) import Control.Distributed.Static (RemoteTable, Closure)  -- import Control.Distributed.Process.Internal.Dynamic (Dynamic) @@ -113,17 +113,17 @@ -- | 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 -  { lpidUnique  :: Int32-  , lpidCounter :: Int32+  { lpidUnique  :: {-# UNPACK #-} !Int32+  , lpidCounter :: {-# UNPACK #-} !Int32   }   deriving (Eq, Ord, Typeable, Show)  -- | Process identifier data ProcessId = ProcessId    { -- | The ID of the node the process is running on-    processNodeId  :: NodeId+    processNodeId  :: !NodeId     -- | Node-local identifier for the process-  , processLocalId :: LocalProcessId +  , processLocalId :: {-# UNPACK #-} !LocalProcessId    }   deriving (Eq, Ord, Typeable) @@ -159,7 +159,7 @@     -- | The network endpoint associated with this node    , localEndPoint :: NT.EndPoint      -- | Local node state -  , localState :: MVar LocalNodeState+  , localState :: StrictMVar LocalNodeState     -- | Channel for the node controller   , localCtrlChan :: Chan NCMsg     -- | Runtime lookup table for supporting closures@@ -169,17 +169,17 @@  -- | Local node state data LocalNodeState = LocalNodeState -  { _localProcesses   :: Map LocalProcessId LocalProcess-  , _localPidCounter  :: Int32-  , _localPidUnique   :: Int32-  , _localConnections :: Map (Identifier, Identifier) NT.Connection+  { _localProcesses   :: !(Map LocalProcessId LocalProcess)+  , _localPidCounter  :: !Int32+  , _localPidUnique   :: !Int32+  , _localConnections :: !(Map (Identifier, Identifier) NT.Connection)   }  -- | Processes running on our local node data LocalProcess = LocalProcess    { processQueue  :: CQueue Message    , processId     :: ProcessId-  , processState  :: MVar LocalProcessState+  , processState  :: StrictMVar LocalProcessState   , processThread :: ThreadId   , processNode   :: LocalNode   }@@ -190,10 +190,10 @@  -- | Local process state data LocalProcessState = LocalProcessState-  { _monitorCounter :: Int32-  , _spawnCounter   :: Int32-  , _channelCounter :: Int32-  , _typedChannels  :: Map LocalSendPortId TypedChannel +  { _monitorCounter :: !Int32+  , _spawnCounter   :: !Int32+  , _channelCounter :: !Int32+  , _typedChannels  :: !(Map LocalSendPortId TypedChannel)   }  -- | The Cloud Haskell 'Process' type@@ -214,9 +214,9 @@ -- to create a SendPort. data SendPortId = SendPortId {     -- | The ID of the process that will receive messages sent on this port-    sendPortProcessId :: ProcessId+    sendPortProcessId :: {-# UNPACK #-} !ProcessId     -- | Process-local ID of the channel-  , sendPortLocalId   :: LocalSendPortId+  , sendPortLocalId   :: {-# UNPACK #-} !LocalSendPortId   }   deriving (Eq, Ord) @@ -279,9 +279,9 @@ -- | MonitorRef is opaque for regular Cloud Haskell processes  data MonitorRef = MonitorRef    { -- | ID of the entity to be monitored-    monitorRefIdent   :: Identifier+    monitorRefIdent   :: !Identifier     -- | Unique to distinguish multiple monitor requests by the same process-  , monitorRefCounter :: Int32+  , monitorRefCounter :: !Int32   }   deriving (Eq, Ord, Show) @@ -384,6 +384,7 @@   | WhereIs String   | Register String (Maybe ProcessId) -- Nothing to unregister   | NamedSend String Message+  | Reconnect Identifier   deriving Show  --------------------------------------------------------------------------------@@ -428,6 +429,7 @@   put (WhereIs label)       = putWord8 6 >> put label   put (Register label pid)  = putWord8 7 >> put label >> put pid   put (NamedSend label msg) = putWord8 8 >> put label >> put (messageToPayload msg) +  put (Reconnect dest)      = putWord8 9 >> put dest   get = do     header <- getWord8     case header of@@ -440,6 +442,7 @@       6 -> WhereIs <$> get       7 -> Register <$> get <*> get       8 -> NamedSend <$> get <*> (payloadToMessage <$> get)+      9 -> Reconnect <$> get       _ -> fail "ProcessSignal.get: invalid"  instance Binary DiedReason where
src/Control/Distributed/Process/Node.hs view
@@ -25,23 +25,24 @@   , toList   , partitionWithKey   , filterWithKey+  , elems   )-import qualified Data.List as List (delete, (\\)) import Data.Set (Set)-import qualified Data.Set as Set (empty, insert, delete, member, (\\), fromList)+import qualified Data.Set as Set (empty, insert, delete, member, filter) import Data.Foldable (forM_) import Data.Maybe (isJust) import Data.Typeable (Typeable) import Control.Category ((>>>)) import Control.Applicative ((<$>))-import Control.Monad (void, when, forever)+import Control.Monad (void, when) import Control.Monad.IO.Class (MonadIO, liftIO)-import Control.Monad.State (MonadState, StateT, evalStateT, gets, modify)+import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)+import qualified Control.Monad.State.Strict as StateT (get, put) import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask) import Control.Exception (throwIO, SomeException, Exception, throwTo) import qualified Control.Exception as Exception (catch) import Control.Concurrent (forkIO)-import Control.Concurrent.MVar +import Control.Distributed.Process.Internal.StrictMVar   ( newMVar    , withMVar   , modifyMVar@@ -64,12 +65,17 @@   , address   , closeEndPoint   , ConnectionId+  , Connection+  , close   ) import Data.Accessor (Accessor, accessor, (^.), (^=), (^:)) import qualified Data.Accessor.Container as DAC (mapDefault, mapMaybe) import System.Random (randomIO) import Control.Distributed.Static (RemoteTable, Closure)-import qualified Control.Distributed.Static as Static (unclosure)+import qualified Control.Distributed.Static as Static +  ( unclosure+  , initRemoteTable+  ) import Control.Distributed.Process.Internal.Types    ( NodeId(..)   , LocalProcessId(..)@@ -85,6 +91,7 @@   , localPidCounter   , localPidUnique   , localProcessWithId+  , localConnections   , MonitorRef(..)   , ProcessMonitorNotification(..)   , NodeMonitorNotification(..)@@ -115,10 +122,10 @@   ( sendBinary   , sendMessage   , sendPayload+  , reconnect   ) import Control.Distributed.Process.Internal.Primitives (expect, register, finally) import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)-import qualified Control.Distributed.Static as Static (initRemoteTable)  -------------------------------------------------------------------------------- -- Initialization                                                             --@@ -159,11 +166,16 @@   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'+ -- | 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+  logger <- forkProcess node . forever' $ do     (time, pid, string) <- expect :: Process (String, ProcessId, String)     liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string    runProcess node $ register "logger" logger@@ -184,75 +196,86 @@  -- | Spawn a new process on a local node forkProcess :: LocalNode -> Process () -> IO ProcessId-forkProcess node proc = modifyMVar (localState node) $ \st -> do-  let lpid  = LocalProcessId { lpidCounter = st ^. localPidCounter-                             , lpidUnique  = st ^. localPidUnique-                             }-  let pid   = ProcessId { processNodeId  = localNodeId node -                        , processLocalId = lpid-                        }-  pst <- newMVar LocalProcessState { _monitorCounter = 0-                                   , _spawnCounter   = 0-                                   , _channelCounter = 0-                                   , _typedChannels  = Map.empty-                                   }-  queue <- newCQueue-  (_, lproc) <- fixIO $ \ ~(tid, _) -> do-    let lproc = LocalProcess { processQueue  = queue-                             , processId     = pid-                             , processState  = pst -                             , processThread = tid-                             , processNode   = node-                             }-    tid' <- forkIO $ do-      reason <- Exception.catch -        (runLocalProcess lproc proc >> return DiedNormal)-        (return . DiedException . (show :: SomeException -> String))-      -- [Unified: Table 4, rules termination and exiting]-      modifyMVar_ (localState node) $ -        return . (localProcessWithId lpid ^= Nothing)-      writeChan (localCtrlChan node) NCMsg -        { ctrlMsgSender = ProcessIdentifier pid -        , ctrlMsgSignal = Died (ProcessIdentifier pid) reason -        }-    return (tid', lproc)+forkProcess node proc = modifyMVar (localState node) startProcess+  where+    startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)+    startProcess st = do+      let lpid  = LocalProcessId { lpidCounter = st ^. localPidCounter+                                 , lpidUnique  = st ^. localPidUnique+                                 }+      let pid   = ProcessId { processNodeId  = localNodeId node +                            , processLocalId = lpid+                            }+      pst <- newMVar LocalProcessState { _monitorCounter = 0+                                       , _spawnCounter   = 0+                                       , _channelCounter = 0+                                       , _typedChannels  = Map.empty+                                       }+      queue <- newCQueue+      (_, lproc) <- fixIO $ \ ~(tid, _) -> do+        let lproc = LocalProcess { processQueue  = queue+                                 , processId     = pid+                                 , processState  = pst +                                 , processThread = tid+                                 , processNode   = node+                                 }+        tid' <- forkIO $ do+          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 +            }+        return (tid', lproc) -  if lpidCounter lpid == maxBound-    then do-      newUnique <- randomIO-      return ( (localProcessWithId lpid ^= Just lproc)-             . (localPidCounter ^= 0)-             . (localPidUnique ^= newUnique)-             $ st-             , pid-             )-    else-      return ( (localProcessWithId lpid ^= Just lproc)-             . (localPidCounter ^: (+ 1))+      if lpidCounter lpid == maxBound+        then do+          newUnique <- randomIO+          return ( (localProcessWithId lpid ^= Just lproc)+                 . (localPidCounter ^= 0)+                 . (localPidUnique ^= newUnique)+                 $ st+                 , pid+                 )+        else+          return ( (localProcessWithId lpid ^= Just lproc)+                 . (localPidCounter ^: (+ 1))+                 $ st+                 , pid +                 )++    cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState+    cleanupProcess pid st = do+      let pid' = ProcessIdentifier pid +      let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)+      mapM_ NT.close (Map.elems affected)+      return $ (localProcessWithId (processLocalId pid) ^= Nothing)+             . (localConnections ^= unaffected)              $ st-             , pid -             )  handleIncomingMessages :: LocalNode -> IO ()-handleIncomingMessages node = go [] Map.empty Map.empty Set.empty+handleIncomingMessages node = go Set.empty Map.empty Map.empty Set.empty   where-    go :: [NT.ConnectionId] -- ^ Connections whose purpose we don't yet know +    go :: Set NT.ConnectionId -- ^ Connections whose purpose we don't yet know         -> Map NT.ConnectionId LocalProcess -- ^ Connections to local processes        -> Map NT.ConnectionId TypedChannel -- ^ Connections to typed channels        -> Set NT.ConnectionId              -- ^ Connections to our controller        -> IO () -    go uninitConns procs chans ctrls = do+    go !uninitConns !procs !chans !ctrls = do       event <- NT.receive endpoint       case event of         NT.ConnectionOpened cid _rel _theirAddr ->           -- TODO: Check if _rel is ReliableOrdered, and if not, treat as           -- (**) below.-          go (cid : uninitConns) procs chans ctrls +          go (Set.insert cid uninitConns) procs chans ctrls          NT.Received cid payload ->            case ( Map.lookup cid procs                 , Map.lookup cid chans                , cid `Set.member` ctrls-               , cid `elem` uninitConns+               , cid `Set.member` uninitConns                ) of             (Just proc, _, _, _) -> do               let msg = payloadToMessage payload@@ -272,7 +295,7 @@                   mProc <- withMVar state $ return . (^. localProcessWithId lpid)                    case mProc of                     Just proc -> -                      go (List.delete cid uninitConns) +                      go (Set.delete cid uninitConns)                           (Map.insert cid proc procs)                          chans                          ctrls@@ -284,7 +307,7 @@                       -- remote node as having died, and we should close                       -- incoming connections (this requires a Transport layer                       -- extension). (**)-                      go (List.delete cid uninitConns) procs chans ctrls+                      go (Set.delete cid uninitConns) procs chans ctrls                 SendPortIdentifier chId -> do                   let lcid = sendPortLocalId chId                       lpid = processLocalId (sendPortProcessId chId)@@ -294,20 +317,20 @@                       mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)                       case mChannel of                         Just channel ->-                          go (List.delete cid uninitConns)+                          go (Set.delete cid uninitConns)                              procs                              (Map.insert cid channel chans)                              ctrls                         Nothing ->                           -- Unknown typed channel                           -- TODO (**) above-                          go (List.delete cid uninitConns) procs chans ctrls+                          go (Set.delete cid uninitConns) procs chans ctrls                     Nothing ->                       -- Unknown process                       -- TODO (**) above-                      go (List.delete cid uninitConns) procs chans ctrls+                      go (Set.delete cid uninitConns) procs chans ctrls                 NodeIdentifier _ ->-                  go (List.delete cid uninitConns)+                  go (Set.delete cid uninitConns)                      procs                      chans                      (Set.insert cid ctrls)@@ -316,7 +339,7 @@               -- TODO (**) above                go uninitConns procs chans ctrls         NT.ConnectionClosed cid -> -          go (List.delete cid uninitConns) +          go (Set.delete cid uninitConns)               (Map.delete cid procs)              (Map.delete cid chans)              (Set.delete cid ctrls)@@ -327,10 +350,11 @@             { ctrlMsgSender = nid             , ctrlMsgSignal = Died nid DiedDisconnect             }-          go (uninitConns List.\\ cids)-             (Map.filterWithKey (\k _ -> k `notElem` cids) procs)-             (Map.filterWithKey (\k _ -> k `notElem` cids) chans)-             (ctrls Set.\\ Set.fromList cids)+          let notRemoved k = k `notElem` cids+          go (Set.filter notRemoved uninitConns)+             (Map.filterWithKey (const . notRemoved) procs)+             (Map.filterWithKey (const . notRemoved) chans)+             (Set.filter notRemoved ctrls)         NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost Nothing _) _) ->           -- TODO: We should treat an asymetrical connection loss (incoming           -- connection broken, but outgoing connection still potentially ok)@@ -365,11 +389,11 @@  data NCState = NCState    {  -- Mapping from remote processes to linked local processes -    _links    :: Map Identifier (Set ProcessId) +    _links    :: !(Map Identifier (Set ProcessId))      -- Mapping from remote processes to monitoring local processes-  , _monitors :: Map Identifier (Set (ProcessId, MonitorRef))+  , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))      -- Process registry-  , _registry :: Map String ProcessId+  , _registry :: !(Map String ProcessId)   }  newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }@@ -389,7 +413,7 @@ nodeController :: NC () nodeController = do   node <- ask -  forever $ do+  forever' $ do     msg  <- liftIO $ readChan (localCtrlChan node)      -- [Unified: Table 7, rule nc_forward] @@ -421,6 +445,8 @@         ncEffectWhereIs from label       NCMsg from (NamedSend label msg') ->         ncEffectNamedSend from label msg'+      NCMsg from (Reconnect to) ->+        ncEffectReconnect from to       unexpected ->         error $ "nodeController: unexpected message " ++ show unexpected @@ -438,8 +464,8 @@   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 +        Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)+        Nothing  -> modify' $ linksFor them ^: Set.insert from      (False, True) -> -- [Unified: second rule]       notifyDied from them DiedUnknownId mRef      (False, False) -> -- [Unified: third rule]@@ -466,7 +492,7 @@         postAsMessage from $ DidUnlinkNode nid       SendPortIdentifier cid ->          postAsMessage from $ DidUnlinkPort cid -  modify $ linksFor them ^: Set.delete from+  modify' $ linksFor them ^: Set.delete from  -- [Unified: Table 11] ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()@@ -474,7 +500,7 @@   node <- ask    when (isLocal node (ProcessIdentifier from)) $      postAsMessage from $ DidUnmonitor ref-  modify $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)+  modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)  -- [Unified: Table 12] ncEffectDied :: Identifier -> DiedReason -> NC ()@@ -495,7 +521,7 @@       when (localOnly <= isLocal node (ProcessIdentifier us)) $         notifyDied us them reason (Just ref) -  modify $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)+  modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)  -- [Unified: Table 13] ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()@@ -517,7 +543,7 @@ -- but mentions it's "very similar to nsend" (Table 14) ncEffectRegister :: String -> Maybe ProcessId -> NC () ncEffectRegister label mPid = -  modify $ registryFor 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)@@ -544,6 +570,12 @@                          (ProcessIdentifier pid)                           (messageToPayload msg)  +-- Reconnecting+ncEffectReconnect :: Identifier -> Identifier -> NC ()+ncEffectReconnect from to = do+  node <- ask+  liftIO $ reconnect node from to+ -------------------------------------------------------------------------------- -- Auxiliary                                                                  -- --------------------------------------------------------------------------------@@ -589,6 +621,7 @@ destNid (Register _ _)  = Nothing destNid (WhereIs _)     = Nothing destNid (NamedSend _ _) = Nothing+destNid (Reconnect _)   = 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@@ -691,7 +724,7 @@ splitNotif :: Identifier            -> Map Identifier a            -> (Map Identifier a, Map Identifier a)-splitNotif ident = Map.partitionWithKey (const . impliesDeathOf ident)+splitNotif ident = Map.partitionWithKey (\k !v -> impliesDeathOf ident k)   -- | Does the death of one entity (node, project, channel) imply the death -- of another?@@ -712,3 +745,11 @@   cid' == cid _ `impliesDeathOf` _ =   False++--------------------------------------------------------------------------------+-- Strict evaluation of the state                                             --+--------------------------------------------------------------------------------++-- | Modify and evaluate the state+modify' :: MonadState s m => (s -> s) -> m ()+modify' f = StateT.get >>= \s -> StateT.put $! f s
tests/TestClosure.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE TemplateHaskell #-} module Main where  import Data.ByteString.Lazy (empty)@@ -290,7 +291,7 @@   node <- newLocalNode transport rtable   done <- newEmptyMVar   forkProcess node $ do-    (pid, ref) <- spawnMonitor (localNodeId node) (Closure (staticLabel "ThisDoesNotExist") empty)+    (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)     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'