packages feed

distributed-process-client-server 0.2.1 → 0.2.2

raw patch · 9 files changed

+344/−12 lines, 9 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Control.Distributed.Process.ManagedProcess.Internal.GenProcess: dequeue :: GenProcess s (Maybe Message)
+ Control.Distributed.Process.ManagedProcess.Internal.GenProcess: eval :: forall s. GenProcess s (ProcessAction s) -> Action s
+ Control.Distributed.Process.ManagedProcess.Internal.GenProcess: processQueue :: GenProcess s [Message]
+ Control.Distributed.Process.ManagedProcess.Internal.Types: ProcessExpression :: (GenProcess s (ProcessAction s)) -> ProcessAction s
+ Control.Distributed.Process.ManagedProcess.Server.Gen: act :: forall s. GenProcess s () -> Action s
+ Control.Distributed.Process.ManagedProcess.Server.Gen: addUserTimer :: Timer -> Message -> GenProcess s TimerKey
+ Control.Distributed.Process.ManagedProcess.Server.Gen: become :: forall s. ProcessDefinition s -> GenProcess s (ProcessAction s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: continue :: GenProcess s (ProcessAction s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: currentTimeout :: GenProcess s Delay
+ Control.Distributed.Process.ManagedProcess.Server.Gen: data GenProcess s a
+ Control.Distributed.Process.ManagedProcess.Server.Gen: dequeue :: GenProcess s (Maybe Message)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: drainTimeout :: GenProcess s Delay
+ Control.Distributed.Process.ManagedProcess.Server.Gen: enqueue :: forall s. Message -> GenProcess s ()
+ Control.Distributed.Process.ManagedProcess.Server.Gen: eval :: forall s. GenProcess s (ProcessAction s) -> Action s
+ Control.Distributed.Process.ManagedProcess.Server.Gen: evalAfter :: forall s m. (Serializable m) => TimeInterval -> m -> s -> Action s
+ Control.Distributed.Process.ManagedProcess.Server.Gen: getAndModifyState :: (ProcessState s -> (ProcessState s, a)) -> GenProcess s a
+ Control.Distributed.Process.ManagedProcess.Server.Gen: gets :: forall s a. (ProcessState s -> a) -> GenProcess s a
+ Control.Distributed.Process.ManagedProcess.Server.Gen: haltNoReply :: forall s r. Serializable r => ExitReason -> GenProcess s (ProcessReply r s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: hibernate :: TimeInterval -> GenProcess s (ProcessAction s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: lift :: Process a -> GenProcess s a
+ Control.Distributed.Process.ManagedProcess.Server.Gen: modifyState :: (ProcessState s -> ProcessState s) -> GenProcess s ()
+ Control.Distributed.Process.ManagedProcess.Server.Gen: noReply :: (Serializable r) => ProcessAction s -> GenProcess s (ProcessReply r s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: peek :: GenProcess s (Maybe Message)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: precvLoop :: PrioritisedProcessDefinition s -> s -> Delay -> Process ExitReason
+ Control.Distributed.Process.ManagedProcess.Server.Gen: processDefinition :: GenProcess s (ProcessDefinition s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: processFilters :: GenProcess s ([DispatchFilter s])
+ Control.Distributed.Process.ManagedProcess.Server.Gen: processQueue :: GenProcess s [Message]
+ Control.Distributed.Process.ManagedProcess.Server.Gen: processState :: GenProcess s s
+ Control.Distributed.Process.ManagedProcess.Server.Gen: processUnhandledMsgPolicy :: GenProcess s UnhandledMessagePolicy
+ Control.Distributed.Process.ManagedProcess.Server.Gen: push :: forall s. Message -> GenProcess s ()
+ Control.Distributed.Process.ManagedProcess.Server.Gen: recvLoop :: ProcessDefinition s -> s -> Delay -> Process ExitReason
+ Control.Distributed.Process.ManagedProcess.Server.Gen: reject :: forall r s. String -> GenProcess s (ProcessReply r s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: rejectWith :: forall r m s. (Show r) => r -> GenProcess s (ProcessReply m s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: removeUserTimer :: TimerKey -> GenProcess s ()
+ Control.Distributed.Process.ManagedProcess.Server.Gen: reply :: forall r s. (Serializable r) => r -> GenProcess s (ProcessReply r s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: replyWith :: forall r s. (Serializable r) => r -> ProcessAction s -> GenProcess s (ProcessReply r s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: runAfter :: forall s m. (Serializable m) => TimeInterval -> m -> GenProcess s ()
+ Control.Distributed.Process.ManagedProcess.Server.Gen: setProcessState :: s -> GenProcess s ()
+ Control.Distributed.Process.ManagedProcess.Server.Gen: setUserTimeout :: Delay -> GenProcess s ()
+ Control.Distributed.Process.ManagedProcess.Server.Gen: stop :: ExitReason -> GenProcess s (ProcessAction s)
+ Control.Distributed.Process.ManagedProcess.Server.Gen: systemTimeout :: GenProcess s Timer
+ Control.Distributed.Process.ManagedProcess.Server.Gen: timeoutAfter :: Delay -> GenProcess s (ProcessAction s)

Files

distributed-process-client-server.cabal view
@@ -1,5 +1,5 @@ name:           distributed-process-client-server-version:        0.2.1+version:        0.2.2 cabal-version:  >=1.8 build-type:     Simple license:        BSD3@@ -56,9 +56,11 @@                    Control.Distributed.Process.ManagedProcess.Server,                    Control.Distributed.Process.ManagedProcess.Server.Priority,                    Control.Distributed.Process.ManagedProcess.Server.Restricted,+                   Control.Distributed.Process.ManagedProcess.Server.Gen,                    Control.Distributed.Process.ManagedProcess.Timer,                    Control.Distributed.Process.ManagedProcess.Internal.Types,                    Control.Distributed.Process.ManagedProcess.Internal.GenProcess+  other-modules:   Control.Distributed.Process.ManagedProcess.Internal.PriorityQueue  test-suite ManagedProcessTests   type:            exitcode-stdio-1.0
src/Control/Distributed/Process/ManagedProcess.hs view
@@ -368,6 +368,45 @@ -- abruptly. Once again, supervision hierarchies are a better way to ensure -- consistent cleanup occurs when valued resources are held by a process. --+-- [Filters, pre-processing, and safe handlers]+--+-- A prioritised process can take advantage of filters, which enable the server+-- to pre-process messages, reject them (based on the message itself, or the+-- server's state), and mark classes of message as requiring /safe/ handling.+--+-- Assuming a 'PrioritisedProcessDefinition' that holds its state as an 'Int',+-- here are some simple applications of filters:+--+-- >   let rejectUnchecked =+-- >        rejectApi Foo :: Int -> P.Message String String -> Process (Filter Int)+-- >+-- >     filters = [+-- >       store  (+1)+-- >     , ensure (>0)+-- >+-- >     , check $ api_ (\(s :: String) -> return $ "checked-" `isInfixOf` s) rejectUnchecked+-- >     , check $ info (\_ (_ :: MonitorRef, _ :: ProcessId) -> return False) $ reject Foo+-- >     , refuse ((> 10) :: Int -> Bool)+-- >     ]+--+-- We can store/update our state, ensure our state is in a valid condition,+-- check api and info messages, and refuse messages using simple predicates.+-- Messages cannot be modified by filters, not can reply data.+--+-- A 'safe' filter is a means to instruct the prioritised managed process loop+-- not to dequeue the current message from the internal priority queue until a+-- handler has successfully matched and run against it (without an exception,+-- either synchronous or asynchronous) to completion. Messages marked thus, will+-- remain in the priority queue even in the face of exit signals, which means that+-- if the server process code handles and swallows them, it will begin re-processing+-- the last message a second time.+--+-- It is important to recognise that the 'safe' filter does not act like a+-- transaction. There are no checkpoints, nor facilities for rolling back actions+-- on failure. If an exit signal terminates a handler for a message marked as+-- 'safe' and an exit handler catches and swallows it, the handler (and all prior+-- filters too) will be re-run in its entireity.+-- -- [Special Clients: Control Channels] -- -- For advanced users and those requiring very low latency, a prioritised@@ -461,7 +500,6 @@ -- via STM. This is true even when client(s) and server(s) reside on the same -- local node. ----- -- A server wishing to receive data via STM can do so using the @handleExternal@ -- API. By way of example, here is a simple echo server implemented using STM: --@@ -597,21 +635,25 @@   , defaultProcessWithPriorities   , statelessProcess   , statelessInit-    -- * Server side callbacks-  , module Control.Distributed.Process.ManagedProcess.Server-    -- * Control channels+  -- * Control channels   , ControlChannel()   , ControlPort()   , newControlChan   , channelControlPort+    -- * Server side callbacks+  , module Control.Distributed.Process.ManagedProcess.Server     -- * Prioritised mailboxes   , module P+    -- * Low level and internal APIs & Process implementation+  , module Gen   ) where  import Control.Distributed.Process hiding (call, Message) import Control.Distributed.Process.ManagedProcess.Client import Control.Distributed.Process.ManagedProcess.Server+import qualified Control.Distributed.Process.ManagedProcess.Server.Restricted as R import qualified Control.Distributed.Process.ManagedProcess.Server.Priority as P hiding (reject)+import qualified Control.Distributed.Process.ManagedProcess.Internal.GenProcess as Gen import Control.Distributed.Process.ManagedProcess.Internal.GenProcess import Control.Distributed.Process.ManagedProcess.Internal.Types hiding (runProcess) import Control.Distributed.Process.Extras (ExitReason(..))
src/Control/Distributed/Process/ManagedProcess/Internal/GenProcess.hs view
@@ -21,6 +21,7 @@   , processDefinition   , processFilters   , processUnhandledMsgPolicy+  , processQueue   , gets   , getAndModifyState   , modifyState@@ -30,8 +31,10 @@   , peek   , push   , enqueue+  , dequeue   , addUserTimer   , removeUserTimer+  , eval   , act   , runAfter   , evalAfter@@ -80,11 +83,12 @@   , matchRun   ) import Control.Distributed.Process.ManagedProcess.Internal.Types hiding (Message)-import qualified Control.Distributed.Process.Extras.Internal.Queue.PriorityQ as Q+import qualified Control.Distributed.Process.ManagedProcess.Internal.PriorityQueue as Q   ( empty   , dequeue   , enqueue   , peek+  , toList   ) import Control.Distributed.Process.Extras   ( ExitReason(..)@@ -201,6 +205,10 @@ processUnhandledMsgPolicy :: GenProcess s UnhandledMessagePolicy processUnhandledMsgPolicy = gets (unhandledMessagePolicy . procDef) +-- | Returns a /read only view/ on the internal priority queue.+processQueue :: GenProcess s [Message]+processQueue = gets internalQ >>= return . Q.toList+ -- | The @Timer@ for the system timeout. See @drainTimeout@. systemTimeout :: GenProcess s Timer systemTimeout = gets sysTimeout@@ -246,6 +254,10 @@ act = return . ProcessActivity {-# WARNING act "This interface is intended for internal use only" #-} +-- | Evaluate an expression in the 'GenProcess' monad.+eval :: forall s . GenProcess s (ProcessAction s) -> Action s+eval = return . ProcessExpression+ -- | Starts a timer and adds it as a /user timeout/. runAfter :: forall s m . (Serializable m) => TimeInterval -> m -> GenProcess s () runAfter d m = do@@ -418,6 +430,7 @@      nextAction :: ProcessAction s -> GenProcess s ExitReason     nextAction ac+      | ProcessExpression expr   <- ac = expr >>= nextAction       | ProcessActivity  act'    <- ac = act' >> recvQueue       | ProcessSkip              <- ac = recvQueue       | ProcessContinue  ps'     <- ac = recvQueueAux ps'@@ -731,6 +744,7 @@         (ProcessStopping s' r)    -> handleStop (LastKnown s') r >> return (r :: ExitReason)         (ProcessBecome   d' s')   -> recvLoop d' s' recvDelay         (ProcessActivity _)       -> die $ "recvLoop.InvalidState - ProcessActivityNotSupported"+        (ProcessExpression _)     -> die $ "recvLoop.InvalidState - ProcessExpressionNotSupported"   where     matchAux :: UnhandledMessagePolicy              -> s
+ src/Control/Distributed/Process/ManagedProcess/Internal/PriorityQueue.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE BangPatterns       #-}+module Control.Distributed.Process.ManagedProcess.Internal.PriorityQueue where++-- NB: we might try this with a skewed binomial heap at some point,+-- but for now, we'll use this module from the fingertree package+import qualified Data.PriorityQueue.FingerTree as PQ+import qualified Data.Foldable as F (toList)+import Data.PriorityQueue.FingerTree (PQueue)++newtype PriorityQ k a = PriorityQ { q :: PQueue k a }++{-# INLINE empty #-}+empty :: Ord k => PriorityQ k v+empty = PriorityQ $ PQ.empty++{-# INLINE isEmpty #-}+isEmpty :: Ord k => PriorityQ k v -> Bool+isEmpty = PQ.null . q++{-# INLINE singleton #-}+singleton :: Ord k => k -> a -> PriorityQ k a+singleton !k !v = PriorityQ $ PQ.singleton k v++{-# INLINE enqueue #-}+enqueue :: Ord k => k -> v -> PriorityQ k v -> PriorityQ k v+enqueue !k !v p = PriorityQ (PQ.add k v $ q p)++{-# INLINE dequeue #-}+dequeue :: Ord k => PriorityQ k v -> Maybe (v, PriorityQ k v)+dequeue p = maybe Nothing (\(v, pq') -> Just (v, pq')) $+              case (PQ.minView (q p)) of+                Nothing     -> Nothing+                Just (v, q') -> Just (v, PriorityQ $ q')++{-# INLINE peek #-}+peek :: Ord k => PriorityQ k v -> Maybe v+peek p = maybe Nothing (\(v, _) -> Just v) $ dequeue p++{-# INLINE toList #-}+toList :: Ord k => PriorityQ k a -> [a]+toList = F.toList . q
src/Control/Distributed/Process/ManagedProcess/Internal/Types.hs view
@@ -90,7 +90,7 @@   , Routable(..)   , NFSerializable   )-import Control.Distributed.Process.Extras.Internal.Queue.PriorityQ+import Control.Distributed.Process.ManagedProcess.Internal.PriorityQueue   ( PriorityQ   ) import Control.Distributed.Process.Extras.Internal.Types@@ -313,6 +313,7 @@ data ProcessAction s =     ProcessSkip   | ProcessActivity  (GenProcess s ()) -- ^ run the given activity+  | ProcessExpression (GenProcess s (ProcessAction s)) -- ^ evaluate an expression   | ProcessContinue  s              -- ^ continue with (possibly new) state   | ProcessTimeout   Delay        s -- ^ timeout if no messages are received   | ProcessHibernate TimeInterval s -- ^ hibernate for /delay/
src/Control/Distributed/Process/ManagedProcess/Server.hs view
@@ -190,6 +190,9 @@ hibernate_ :: StatelessHandler s TimeInterval hibernate_ d = return . ProcessHibernate d +-- | The server loop will execute against the supplied 'ProcessDefinition', allowing+-- the process to change its behaviour (in terms of message handlers, exit handling,+-- termination, unhandled message policy, etc) become :: forall s . ProcessDefinition s -> s -> Action s become def st = return $ ProcessBecome def st 
+ src/Control/Distributed/Process/ManagedProcess/Server/Gen.hs view
@@ -0,0 +1,181 @@+{-# LANGUAGE ExistentialQuantification  #-}+{-# LANGUAGE ScopedTypeVariables        #-}+{-# LANGUAGE PatternGuards              #-}+{-# LANGUAGE RecordWildCards            #-}+{-# LANGUAGE EmptyDataDecls             #-}+{-# LANGUAGE StandaloneDeriving         #-}+{-# LANGUAGE AllowAmbiguousTypes        #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  Control.Distributed.Process.ManagedProcess.Server.Priority+-- Copyright   :  (c) Tim Watson 2012 - 2017+-- License     :  BSD3 (see the file LICENSE)+--+-- Maintainer  :  Tim Watson <watson.timothy@gmail.com>+-- Stability   :  experimental+-- Portability :  non-portable (requires concurrency)+--+-- The Server Portion of the /Managed Process/ API, as presented by the+-- 'GenProcess' monad. These functions are generally intended for internal+-- use, but the API is relatively stable and therefore they have been re-exported+-- here for general use. Note that if you modify a process' internal state+-- (especially that of the internal priority queue) then you are responsible for+-- any alteratoin that makes to the semantics of your processes behaviour.+--+-- See "Control.Distributed.Process.ManagedProcess.Internal.GenProcess"+-----------------------------------------------------------------------------+module Control.Distributed.Process.ManagedProcess.Server.Gen+  ( -- * Server actions+    reply+  , replyWith+  , noReply+  , continue+  , timeoutAfter+  , hibernate+  , stop+  , reject+  , rejectWith+  , become+  , haltNoReply+  , lift+  , Gen.recvLoop+  , Gen.precvLoop+  , Gen.currentTimeout+  , Gen.systemTimeout+  , Gen.drainTimeout+  , Gen.processState+  , Gen.processDefinition+  , Gen.processFilters+  , Gen.processUnhandledMsgPolicy+  , Gen.processQueue+  , Gen.gets+  , Gen.getAndModifyState+  , Gen.modifyState+  , Gen.setUserTimeout+  , Gen.setProcessState+  , GenProcess+  , Gen.peek+  , Gen.push+  , Gen.enqueue+  , Gen.dequeue+  , Gen.addUserTimer+  , Gen.removeUserTimer+  , Gen.eval+  , Gen.act+  , Gen.runAfter+  , Gen.evalAfter+  ) where++import Control.Distributed.Process.Extras+ ( ExitReason+ )+import Control.Distributed.Process.Extras.Time+ ( TimeInterval+ , Delay+ )+import Control.Distributed.Process.ManagedProcess.Internal.Types+ ( lift+ , ProcessAction(..)+ , GenProcess+ , ProcessReply(..)+ , ProcessDefinition+ )+import qualified Control.Distributed.Process.ManagedProcess.Internal.GenProcess as Gen+ ( recvLoop+ , precvLoop+ , currentTimeout+ , systemTimeout+ , drainTimeout+ , processState+ , processDefinition+ , processFilters+ , processUnhandledMsgPolicy+ , processQueue+ , gets+ , getAndModifyState+ , modifyState+ , setUserTimeout+ , setProcessState+ , GenProcess+ , peek+ , push+ , enqueue+ , dequeue+ , addUserTimer+ , removeUserTimer+ , eval+ , act+ , runAfter+ , evalAfter+ )+import Control.Distributed.Process.ManagedProcess.Internal.GenProcess+ ( processState+ )+import qualified Control.Distributed.Process.ManagedProcess.Server as Server+ ( replyWith+ , continue+ )+import Control.Distributed.Process.Serializable (Serializable)++-- | Reject the message we're currently handling.+reject :: forall r s . String -> GenProcess s (ProcessReply r s)+reject rs = processState >>= \st -> lift $ Server.continue st >>= return . ProcessReject rs++-- | Reject the message we're currently handling, giving an explicit reason.+rejectWith :: forall r m s . (Show r) => r -> GenProcess s (ProcessReply m s)+rejectWith rs = reject (show rs)++-- | Instructs the process to send a reply and continue running.+reply :: forall r s . (Serializable r) => r -> GenProcess s (ProcessReply r s)+reply r = processState >>= \s -> lift $ Server.continue s >>= Server.replyWith r++-- | Instructs the process to send a reply /and/ evaluate the 'ProcessAction'.+replyWith :: forall r s . (Serializable r)+         => r+         -> ProcessAction s+         -> GenProcess s (ProcessReply r s)+replyWith r s = return $ ProcessReply r s++-- | Instructs the process to skip sending a reply /and/ evaluate a 'ProcessAction'+noReply :: (Serializable r) => ProcessAction s -> GenProcess s (ProcessReply r s)+noReply = return . NoReply++-- | Halt process execution during a call handler, without paying any attention+-- to the expected return type.+haltNoReply :: forall s r . Serializable r => ExitReason -> GenProcess s (ProcessReply r s)+haltNoReply r = stop r >>= noReply++-- | Instructs the process to continue running and receiving messages.+continue :: GenProcess s (ProcessAction s)+continue = processState >>= return . ProcessContinue++-- | Instructs the process loop to wait for incoming messages until 'Delay'+-- is exceeded. If no messages are handled during this period, the /timeout/+-- handler will be called. Note that this alters the process timeout permanently+-- such that the given @Delay@ will remain in use until changed.+--+-- Note that @timeoutAfter NoDelay@ will cause the timeout handler to execute+-- immediately if no messages are present in the process' mailbox.+--+timeoutAfter :: Delay -> GenProcess s (ProcessAction s)+timeoutAfter d = processState >>= \s -> return $ ProcessTimeout d s++-- | Instructs the process to /hibernate/ for the given 'TimeInterval'. Note+-- that no messages will be removed from the mailbox until after hibernation has+-- ceased. This is equivalent to calling @threadDelay@.+--+hibernate :: TimeInterval -> GenProcess s (ProcessAction s)+hibernate d = processState >>= \s -> return $ ProcessHibernate d s++-- | The server loop will execute against the supplied 'ProcessDefinition', allowing+-- the process to change its behaviour (in terms of message handlers, exit handling,+-- termination, unhandled message policy, etc)+become :: forall s . ProcessDefinition s -> GenProcess s (ProcessAction s)+become def = processState >>= \st -> return $ ProcessBecome def st++-- | Instructs the process to terminate, giving the supplied reason. If a valid+-- 'shutdownHandler' is installed, it will be called with the 'ExitReason'+-- returned from this call, along with the process state.+stop :: ExitReason -> GenProcess s (ProcessAction s)+stop r = return $ ProcessStop r
src/Control/Distributed/Process/ManagedProcess/Server/Priority.hs view
@@ -188,6 +188,8 @@         -> FilterHandler s info_ c h = info (const $ c) h +-- | As 'safe', but as applied to api messages (i.e. those originating from+-- call as cast client interactions). apiSafe :: forall s m b . (Serializable m, Serializable b)      => (s -> m -> Maybe b -> Bool)      -> DispatchFilter s@@ -201,6 +203,13 @@         Just (ChanMessage m' _) -> return $ c' s m' Nothing         Nothing                 -> return False +-- | Given a check expression, if it evaluates to @True@ for some input,+-- then do not dequeue the message until after any matching handlers have+-- successfully run, or the the unhandled message policy is chosen if none match.+-- Thus, if an exit signal (async exception) terminates execution of a handler, and we+-- have an installed exit handler which allows the process to continue running,+-- we will retry the input in question since it has not been fully dequeued prior+-- to the exit signal arriving. safe :: forall s m . (Serializable m)      => (s -> m -> Bool)      -> DispatchFilter s@@ -212,6 +221,7 @@         Just m' -> return $ c' s m'         Nothing -> return False +-- | As 'safe', but matches on a raw message. safely :: forall s . (s -> P.Message -> Bool) -> DispatchFilter s safely c = check $ HandleSafe $ \s m -> return (c s m) @@ -251,7 +261,6 @@ refuse c = check $ info (const $ \m -> return $ c m) (reject RejectedByServer)  {-- apiCheck :: forall s m r . (Serializable m, Serializable r)       => (s -> Message m r -> Bool)       -> (s -> Message m r -> Process (Filter s))
tests/TestPrioritisedProcess.hs view
@@ -12,13 +12,18 @@  ) import Control.Exception (SomeException) import Control.DeepSeq (NFData)-import Control.Distributed.Process hiding (call, send, catch, sendChan)+import Control.Distributed.Process hiding (call, send, catch, sendChan, wrapMessage) import Control.Distributed.Process.Node import Control.Distributed.Process.Extras hiding (__remoteTable, monitor) import Control.Distributed.Process.Async hiding (check)-import Control.Distributed.Process.ManagedProcess hiding (reject)+import Control.Distributed.Process.ManagedProcess hiding (reject, Message) import qualified Control.Distributed.Process.ManagedProcess.Server.Priority as P (Message)-import Control.Distributed.Process.ManagedProcess.Server.Priority+import Control.Distributed.Process.ManagedProcess.Server.Priority hiding (Message)+import qualified Control.Distributed.Process.ManagedProcess.Server.Gen as Gen+ ( dequeue+ , continue+ , lift+ ) import Control.Distributed.Process.SysTest.Utils import Control.Distributed.Process.Extras.Time import Control.Distributed.Process.Extras.Timer hiding (runAfter)@@ -29,7 +34,7 @@ import Data.Binary import Data.Either (rights) import Data.List (isInfixOf)-import Data.Maybe (isNothing)+import Data.Maybe (isNothing, isJust) import Data.Typeable (Typeable)  #if ! MIN_VERSION_base(4,6,0)@@ -241,6 +246,37 @@   pid <- spawnLocal $ pserve 0 (\c -> return $ InitOk c Infinity) p'   return (pid, cp) +testStupidInfiniteLoop :: TestResult Bool -> Process ()+testStupidInfiniteLoop result = do+  let def = statelessProcess {+                  apiHandlers = [+                    handleCast (\_ sp -> eval $ do q <- processQueue+                                                   m <- Gen.dequeue+                                                   Gen.lift $ sendChan sp (length q, m)+                                                   Gen.continue)+                  ]+                , infoHandlers = [+                    handleInfo (\_ (m :: String) -> eval $ do enqueue (wrapMessage m)+                                                              Gen.continue)+                  ]+                } :: ProcessDefinition ()++  let prio = def `prioritised` []+  pid <- spawnLocal $ pserve () (statelessInit Infinity) prio++  -- this message should create an infinite loop+  send pid "fooboo"++  (sp, rp) <- newChan :: Process (SendPort (Int, Maybe Message), ReceivePort (Int, Maybe Message))++  cast pid sp+  (i, m) <- receiveChan rp++  cast pid sp+  (i', m') <- receiveChan rp++  stash result $ (i == 1 && isJust m && i' == 0 && isNothing m')+ testFilteringBehavior :: TestResult Bool -> Process () testFilteringBehavior result = do   us <- getSelfPid@@ -587,6 +623,9 @@           , testCase "Swapping ProcessDefinitions at runtime"              (delayedAssertion "expected our handler to exist in the new handler list"               localNode True testServerSwap)+          , testCase "Accessing the internal process implementation"+             (delayedAssertion "it should allow us to modify the internal q"+              localNode True testStupidInfiniteLoop)          ]       ]