Skip to content
This repository was archived by the owner on Sep 3, 2024. It is now read-only.

Commit 3c8d03b

Browse files
committed
Prioritised Process Improvements
Segregate external and api handlers at the type level. Provide a means for reading control channels and STM handlers within a prioritised process loop. Fixes #13
1 parent c850b29 commit 3c8d03b

File tree

7 files changed

+161
-86
lines changed

7 files changed

+161
-86
lines changed

src/Control/Distributed/Process/ManagedProcess.hs

+9-2
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ module Control.Distributed.Process.ManagedProcess
438438
, Priority(..)
439439
, DispatchPriority()
440440
, Dispatcher()
441+
, ExternDispatcher()
441442
, DeferredDispatcher()
442443
, ShutdownHandler
443444
, TimeoutHandler
@@ -598,6 +599,7 @@ defaultProcess :: ProcessDefinition s
598599
defaultProcess = ProcessDefinition {
599600
apiHandlers = []
600601
, infoHandlers = []
602+
, externHandlers = []
601603
, exitHandlers = []
602604
, timeoutHandler = \s _ -> continue s
603605
, shutdownHandler = \_ _ -> return ()
@@ -607,10 +609,15 @@ defaultProcess = ProcessDefinition {
607609
-- | Turns a standard 'ProcessDefinition' into a 'PrioritisedProcessDefinition',
608610
-- by virtue of the supplied list of 'DispatchPriority' expressions.
609611
--
612+
-- Terminates the caller with an exit signal if the supplied process definition
613+
-- contains any externHandlers, since these are not supported by prioritised
614+
-- process definitions.
615+
--
610616
prioritised :: ProcessDefinition s
611617
-> [DispatchPriority s]
612618
-> PrioritisedProcessDefinition s
613-
prioritised def ps = PrioritisedProcessDefinition def ps defaultRecvTimeoutPolicy
619+
prioritised def ps =
620+
PrioritisedProcessDefinition def ps defaultRecvTimeoutPolicy
614621

615622
-- | Sets the default 'recvTimeoutPolicy', which gives up after 10k reads.
616623
defaultRecvTimeoutPolicy :: RecvTimeoutPolicy
@@ -619,7 +626,7 @@ defaultRecvTimeoutPolicy = RecvCounter 10000
619626
-- | Creates a default 'PrioritisedProcessDefinition' from a list of
620627
-- 'DispatchPriority'. See 'defaultProcess' for the underlying definition.
621628
defaultProcessWithPriorities :: [DispatchPriority s] -> PrioritisedProcessDefinition s
622-
defaultProcessWithPriorities dps = prioritised defaultProcess dps
629+
defaultProcessWithPriorities = prioritised defaultProcess
623630

624631
-- | A basic, stateless 'ProcessDefinition'. See 'defaultProcess' for the
625632
-- default field values.

src/Control/Distributed/Process/ManagedProcess/Internal/GenProcess.hs

+8-13
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,8 @@ data CancelTimer = CancelTimer deriving (Eq, Show, Typeable)
6363
--
6464
precvLoop :: PrioritisedProcessDefinition s -> s -> Delay -> Process ExitReason
6565
precvLoop ppDef pState recvDelay = do
66-
void $ verify $ processDef ppDef
6766
tref <- startTimer recvDelay
6867
recvQueue ppDef pState tref PriorityQ.empty
69-
where
70-
verify pDef = mapM_ disallowCC $ apiHandlers pDef
71-
72-
-- TODO: better failure messages here!
73-
disallowCC (DispatchCC _ _) = die $ ExitOther "IllegalControlChannel"
74-
disallowCC (DispatchSTM _ _) = die $ ExitOther "IllegalSTMAction"
75-
disallowCC _ = return ()
7668

7769
recvQueue :: PrioritisedProcessDefinition s
7870
-> s
@@ -143,7 +135,7 @@ recvQueue p s t q =
143135
-- if the internal queue is empty, we fall back to reading the
144136
-- actual mailbox, however if /that/ times out, then we need
145137
-- to let the timeout handler kick in again and make a decision
146-
drainOrTimeout s' t' queue ps' h
138+
drainOrTimeout def s' t' queue ps' h
147139
Just (m', q') -> do
148140
act <- catchesExit (processApply def s' m')
149141
(map (\d' -> dispatchExit d' s') ex)
@@ -153,8 +145,9 @@ recvQueue p s t q =
153145
let pol = unhandledMessagePolicy def
154146
apiMatchers = map (dynHandleMessage pol pState) (apiHandlers def)
155147
infoMatchers = map (dynHandleMessage pol pState) (infoHandlers def)
148+
extMatchers = map (dynHandleMessage pol pState) (externHandlers def)
156149
shutdown' = dynHandleMessage pol pState shutdownHandler'
157-
ms' = (shutdown':apiMatchers) ++ infoMatchers
150+
ms' = (shutdown':apiMatchers) ++ infoMatchers ++ extMatchers
158151
in processApplyAux ms' pol pState msg
159152

160153
processApplyAux [] p' s' m' = applyPolicy p' s' m'
@@ -164,8 +157,9 @@ recvQueue p s t q =
164157
Nothing -> processApplyAux hs p' s' m'
165158
Just act -> return act
166159

167-
drainOrTimeout pState delay queue ps' h =
168-
let matches = [ matchMessage return ]
160+
drainOrTimeout pDef pState delay queue ps' h =
161+
let p' = unhandledMessagePolicy pDef
162+
matches = ((matchMessage return):(map (matchExtern p' pState) (externHandlers pDef)))
169163
recv = case delay of
170164
Infinity -> fmap Just (receiveWait matches)
171165
NoDelay -> receiveTimeout 0 matches
@@ -257,7 +251,8 @@ recvLoop pDef pState recvDelay =
257251
handleTimeout = timeoutHandler pDef
258252
handleStop = shutdownHandler pDef
259253
shutdown' = matchDispatch p pState shutdownHandler'
260-
matchers = map (matchDispatch p pState) (apiHandlers pDef)
254+
extMatchers = map (matchDispatch p pState) (externHandlers pDef)
255+
matchers = extMatchers ++ (map (matchDispatch p pState) (apiHandlers pDef))
261256
ex' = (trapExit:(exitHandlers pDef))
262257
ms' = (shutdown':matchers) ++ matchAux p pState (infoHandlers pDef)
263258
in do

src/Control/Distributed/Process/ManagedProcess/Internal/Types.hs

+32-15
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ module Control.Distributed.Process.ManagedProcess.Internal.Types
3838
, ControlPort(..)
3939
, channelControlPort
4040
, Dispatcher(..)
41+
, ExternDispatcher(..)
4142
, DeferredDispatcher(..)
4243
, ExitSignalDispatcher(..)
4344
, MessageMatcher(..)
45+
, ExternMatcher(..)
4446
, DynMessageHandler(..)
4547
, Message(..)
4648
, CallResponse(..)
@@ -166,7 +168,6 @@ data Condition s m =
166168
| State (s -> Bool) -- ^ predicated on the process state only
167169
| Input (m -> Bool) -- ^ predicated on the input message only
168170

169-
170171
-- | An action (server state transition) in the @Process@ monad
171172
type Action s = Process (ProcessAction s)
172173

@@ -254,17 +255,21 @@ data Dispatcher s =
254255
dispatch :: s -> Message a b -> Process (ProcessAction s)
255256
, dispatchIf :: s -> Message a b -> Bool
256257
}
257-
| forall a b . (Serializable a, Serializable b) =>
258+
259+
-- | Provides dispatch for channels and STM actions
260+
data ExternDispatcher s =
261+
forall a b . (Serializable a, Serializable b) =>
258262
DispatchCC -- control channel dispatch
259263
{
260-
channel :: ReceivePort (Message a b)
261-
, dispatch :: s -> Message a b -> Process (ProcessAction s)
264+
channel :: ReceivePort (Message a b)
265+
, dispatchChan :: s -> Message a b -> Process (ProcessAction s)
262266
}
263-
| forall a .
267+
| forall a . (Serializable a) =>
264268
DispatchSTM -- arbitrary STM actions
265269
{
266270
stmAction :: STM a
267-
, stmDispatch :: s -> a -> Process (ProcessAction s)
271+
, dispatchStm :: s -> a -> Process (ProcessAction s)
272+
, matchStm :: Match P.Message
268273
}
269274

270275
-- | Provides dispatch for any input, returns 'Nothing' for unhandled messages.
@@ -291,10 +296,19 @@ class MessageMatcher d where
291296
matchDispatch :: UnhandledMessagePolicy -> s -> d s -> Match (ProcessAction s)
292297

293298
instance MessageMatcher Dispatcher where
294-
matchDispatch _ s (Dispatch d) = match (d s)
295-
matchDispatch _ s (DispatchIf d cond) = matchIf (cond s) (d s)
296-
matchDispatch _ s (DispatchCC c d) = matchChan c (d s)
297-
matchDispatch _ s (DispatchSTM c d) = matchSTM c (d s)
299+
matchDispatch _ s (Dispatch d) = match (d s)
300+
matchDispatch _ s (DispatchIf d cond) = matchIf (cond s) (d s)
301+
302+
instance MessageMatcher ExternDispatcher where
303+
matchDispatch _ s (DispatchCC c d) = matchChan c (d s)
304+
matchDispatch _ s (DispatchSTM c d _) = matchSTM c (d s)
305+
306+
class ExternMatcher d where
307+
matchExtern :: UnhandledMessagePolicy -> s -> d s -> Match P.Message
308+
309+
instance ExternMatcher ExternDispatcher where
310+
matchExtern _ _ (DispatchCC c _) = matchChan c (return . unsafeWrapMessage)
311+
matchExtern _ _ (DispatchSTM _ _ m) = m
298312

299313
-- | Maps handlers to a dynamic action that can take place outside of a
300314
-- expect/recieve block.
@@ -308,8 +322,10 @@ class DynMessageHandler d where
308322
instance DynMessageHandler Dispatcher where
309323
dynHandleMessage _ s (Dispatch d) msg = handleMessage msg (d s)
310324
dynHandleMessage _ s (DispatchIf d c) msg = handleMessageIf msg (c s) (d s)
311-
dynHandleMessage _ _ (DispatchCC _ _) _ = error "ThisCanNeverHappen"
312-
dynHandleMessage _ _ (DispatchSTM _ _) _ = error "ThisCanNeverHappen"
325+
326+
instance DynMessageHandler ExternDispatcher where
327+
dynHandleMessage _ s (DispatchCC _ d) msg = handleMessage msg (d s)
328+
dynHandleMessage _ s (DispatchSTM _ d _) msg = handleMessage msg (d s)
313329

314330
instance DynMessageHandler DeferredDispatcher where
315331
dynHandleMessage _ s (DeferredDispatcher d) = d s
@@ -368,9 +384,10 @@ data UnhandledMessagePolicy =
368384
-- | Stores the functions that determine runtime behaviour in response to
369385
-- incoming messages and a policy for responding to unhandled messages.
370386
data ProcessDefinition s = ProcessDefinition {
371-
apiHandlers :: [Dispatcher s] -- ^ functions that handle call/cast messages
372-
, infoHandlers :: [DeferredDispatcher s] -- ^ functions that handle non call/cast messages
373-
, exitHandlers :: [ExitSignalDispatcher s] -- ^ functions that handle exit signals
387+
apiHandlers :: [Dispatcher s] -- ^ functions that handle call/cast messages
388+
, infoHandlers :: [DeferredDispatcher s] -- ^ functions that handle non call/cast messages
389+
, externHandlers :: [ExternDispatcher s] -- ^ functions that handle control channel and STM inputs
390+
, exitHandlers :: [ExitSignalDispatcher s] -- ^ functions that handle exit signals
374391
, timeoutHandler :: TimeoutHandler s -- ^ a function that handles timeouts
375392
, shutdownHandler :: ShutdownHandler s -- ^ a function that is run just before the process exits
376393
, unhandledMessagePolicy :: UnhandledMessagePolicy -- ^ how to deal with unhandled messages

src/Control/Distributed/Process/ManagedProcess/Server.hs

+26-22
Original file line numberDiff line numberDiff line change
@@ -399,34 +399,38 @@ handleCastIf cond h
399399
--
400400
-- NB: this function cannot be used with a prioristised process definition.
401401
--
402-
handleExternal :: forall s a .
403-
STM a
404-
-> ActionHandler s a
405-
-> Dispatcher s
406-
handleExternal = DispatchSTM
402+
handleExternal :: forall s a . (Serializable a)
403+
=> STM a
404+
-> ActionHandler s a
405+
-> ExternDispatcher s
406+
handleExternal a h =
407+
DispatchSTM a h (matchSTM a (\(m :: r) -> return $ unsafeWrapMessage m))
407408

408409
-- | Version of @handleExternal@ that ignores state.
409-
handleExternal_ :: forall s a .
410-
STM a
411-
-> StatelessHandler s a
412-
-> Dispatcher s
413-
handleExternal_ a h = DispatchSTM a $ flip h
410+
handleExternal_ :: forall s a . (Serializable a)
411+
=> STM a
412+
-> StatelessHandler s a
413+
-> ExternDispatcher s
414+
handleExternal_ a h =
415+
DispatchSTM a (flip h) (matchSTM a (\(m :: r) -> return $ unsafeWrapMessage m))
414416

415417
-- | Handle @call@ style API interactions using arbitrary /STM/ actions.
416418
--
417419
-- The usual @CallHandler@ is preceded by an stm action that, when evaluated,
418420
-- yields a value, and a second expression that is used to send a reply back
419421
-- to the /caller/. The corrolary client API is /callSTM/.
420422
--
421-
handleCallExternal :: forall s r w .
422-
STM r
423+
handleCallExternal :: forall s r w . (Serializable r)
424+
=> STM r
423425
-> (w -> STM ())
424426
-> CallHandler s r w
425-
-> Dispatcher s
427+
-> ExternDispatcher s
426428
handleCallExternal reader writer handler
427-
= DispatchSTM { stmAction = reader
428-
, stmDispatch = doStmReply handler
429-
}
429+
= DispatchSTM
430+
{ stmAction = reader
431+
, dispatchStm = doStmReply handler
432+
, matchStm = matchSTM reader (\(m :: r) -> return $ unsafeWrapMessage m)
433+
}
430434
where
431435
doStmReply d s m = d s m >>= doXfmReply writer
432436

@@ -443,21 +447,21 @@ handleControlChan :: forall s a . (Serializable a)
443447
=> ControlChannel a -- ^ the receiving end of the control channel
444448
-> ActionHandler s a
445449
-- ^ an action yielding function over the process state and input message
446-
-> Dispatcher s
450+
-> ExternDispatcher s
447451
handleControlChan chan h
448-
= DispatchCC { channel = snd $ unControl chan
449-
, dispatch = \s ((CastMessage p) :: Message a ()) -> h s p
452+
= DispatchCC { channel = snd $ unControl chan
453+
, dispatchChan = \s ((CastMessage p) :: Message a ()) -> h s p
450454
}
451455

452456
-- | Version of 'handleControlChan' that ignores the server state.
453457
--
454458
handleControlChan_ :: forall s a. (Serializable a)
455459
=> ControlChannel a
456460
-> StatelessHandler s a
457-
-> Dispatcher s
461+
-> ExternDispatcher s
458462
handleControlChan_ chan h
459-
= DispatchCC { channel = snd $ unControl chan
460-
, dispatch = \s ((CastMessage p) :: Message a ()) -> h p s
463+
= DispatchCC { channel = snd $ unControl chan
464+
, dispatchChan = \s ((CastMessage p) :: Message a ()) -> h p s
461465
}
462466

463467
-- | Version of 'handleCast' that ignores the server state.

tests/ManagedProcessCommon.hs

+39
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
{-# LANGUAGE ScopedTypeVariables #-}
22
{-# LANGUAGE FlexibleInstances #-}
3+
{-# LANGUAGE RecordWildCards #-}
34

45
module ManagedProcessCommon where
56

67
import Control.Concurrent.MVar (MVar)
8+
import Control.Concurrent.STM.TQueue
9+
( newTQueueIO
10+
, readTQueue
11+
, writeTQueue
12+
, TQueue
13+
)
714
import Control.Distributed.Process hiding (call, send)
815
import Control.Distributed.Process.Extras hiding (monitor)
916
import qualified Control.Distributed.Process as P
@@ -67,6 +74,38 @@ standardTestServer policy =
6774
wrap :: (Process (ProcessId, MVar ExitReason)) -> Launcher a
6875
wrap it = \_ -> do it
6976

77+
data StmServer = StmServer { serverPid :: ProcessId
78+
, writerChan :: TQueue String
79+
, readerChan :: TQueue String
80+
}
81+
82+
instance Resolvable StmServer where
83+
resolve = return . Just . serverPid
84+
85+
echoStm :: StmServer -> String -> Process (Either ExitReason String)
86+
echoStm StmServer{..} = callSTM serverPid
87+
(writeTQueue writerChan)
88+
(readTQueue readerChan)
89+
90+
launchEchoServer :: CallHandler () String String -> Process StmServer
91+
launchEchoServer handler = do
92+
(inQ, replyQ) <- liftIO $ do
93+
cIn <- newTQueueIO
94+
cOut <- newTQueueIO
95+
return (cIn, cOut)
96+
97+
let procDef = statelessProcess {
98+
externHandlers = [
99+
handleCallExternal
100+
(readTQueue inQ)
101+
(writeTQueue replyQ)
102+
handler
103+
]
104+
}
105+
106+
pid <- spawnLocal $ serve () (statelessInit Infinity) procDef
107+
return $ StmServer pid inQ replyQ
108+
70109
-- common test cases
71110

72111
testBasicCall :: Launcher () -> TestResult (Maybe String) -> Process ()

tests/TestManagedProcess.hs

+1-34
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import Control.Concurrent.STM.TQueue
88
( newTQueueIO
99
, readTQueue
1010
, writeTQueue
11-
, TQueue
1211
)
1312
import Control.Concurrent.MVar
1413
import Control.Exception (SomeException)
@@ -104,7 +103,7 @@ testExternalService result = do
104103
inChan <- liftIO newTQueueIO
105104
replyQ <- liftIO newTQueueIO
106105
let procDef = statelessProcess {
107-
apiHandlers = [
106+
externHandlers = [
108107
handleExternal
109108
(readTQueue inChan)
110109
(\s (m :: String) -> do
@@ -123,38 +122,6 @@ testExternalService result = do
123122
stash result (echoTxt == txt)
124123
kill pid "done"
125124

126-
data StmServer = StmServer { serverPid :: ProcessId
127-
, writerChan :: TQueue String
128-
, readerChan :: TQueue String
129-
}
130-
131-
instance Resolvable StmServer where
132-
resolve = return . Just . serverPid
133-
134-
echoStm :: StmServer -> String -> Process (Either ExitReason String)
135-
echoStm StmServer{..} = callSTM serverPid
136-
(writeTQueue writerChan)
137-
(readTQueue readerChan)
138-
139-
launchEchoServer :: CallHandler () String String -> Process StmServer
140-
launchEchoServer handler = do
141-
(inQ, replyQ) <- liftIO $ do
142-
cIn <- newTQueueIO
143-
cOut <- newTQueueIO
144-
return (cIn, cOut)
145-
146-
let procDef = statelessProcess {
147-
apiHandlers = [
148-
handleCallExternal
149-
(readTQueue inQ)
150-
(writeTQueue replyQ)
151-
handler
152-
]
153-
}
154-
155-
pid <- spawnLocal $ serve () (statelessInit Infinity) procDef
156-
return $ StmServer pid inQ replyQ
157-
158125
testExternalCall :: TestResult Bool -> Process ()
159126
testExternalCall result = do
160127
let txt = "hello stm-call foo"

0 commit comments

Comments
 (0)