Skip to content
This repository was archived by the owner on Feb 15, 2025. It is now read-only.

Commit 6875c24

Browse files
committed
Test suite improvements, sequence breaks early
1 parent 7415fc8 commit 6875c24

File tree

7 files changed

+119
-15
lines changed

7 files changed

+119
-15
lines changed

src/Control/Distributed/Process/FSM.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ initState = Yield
3232
enter :: forall s d . s -> FSM s d (Transition s d)
3333
enter = return . Enter
3434

35+
resume :: forall s d . FSM s d (Transition s d)
36+
resume = return Remain
37+
3538
event :: (Serializable m) => Event m
3639
event = Wait
3740

@@ -88,6 +91,13 @@ infixr 9 ~>
8891
await :: forall s d m . (Serializable m) => Event m -> Step s d -> Step s d
8992
await = Await
9093

94+
(*>) :: forall s d m . (Serializable m) => Event m -> Step s d -> Step s d
95+
(*>) = SafeWait
96+
infixr 9 *>
97+
98+
safeWait :: forall s d m . (Serializable m) => Event m -> Step s d -> Step s d
99+
safeWait = SafeWait
100+
91101
(~@) :: forall s d . (Eq s) => s -> FSM s d (Transition s d) -> Step s d
92102
(~@) = Perhaps
93103
infixr 9 ~@

src/Control/Distributed/Process/FSM/Internal/Process.hs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import Control.Distributed.Process.ManagedProcess
3333
, Action
3434
, InitHandler
3535
, InitResult(..)
36+
, DispatchFilter
3637
, defaultProcess
3738
, prioritised
3839
)
@@ -46,6 +47,7 @@ import Control.Distributed.Process.ManagedProcess.Server
4647
import Control.Distributed.Process.ManagedProcess.Internal.Types
4748
( ExitSignalDispatcher(..)
4849
)
50+
import Data.Maybe (isJust)
4951
import qualified Data.Sequence as Q (empty)
5052
-- import Control.Distributed.Process.Serializable (Serializable)
5153
-- import Control.Monad (void)
@@ -57,14 +59,14 @@ start :: forall s d . (Show s) => s -> d -> (Step s d) -> Process ProcessId
5759
start s d p = spawnLocal $ run s d p
5860

5961
run :: forall s d . (Show s) => s -> d -> (Step s d) -> Process ()
60-
run s d p = MP.pserve (s, d, p) fsmInit processDefinition
62+
run s d p = MP.pserve (s, d, p) fsmInit (processDefinition p)
6163

6264
fsmInit :: forall s d . (Show s) => InitHandler (s, d, Step s d) (State s d)
6365
fsmInit (st, sd, prog) =
64-
return $ InitOk (State st sd prog Nothing (const $ return ()) Q.empty) Infinity
66+
return $ InitOk (State st sd prog prog Nothing (const $ return ()) Q.empty) Infinity
6567

66-
processDefinition :: forall s d . (Show s) => PrioritisedProcessDefinition (State s d)
67-
processDefinition =
68+
processDefinition :: forall s d . (Show s) => Step s d -> PrioritisedProcessDefinition (State s d)
69+
processDefinition prog =
6870
(prioritised
6971
defaultProcess
7072
{
@@ -73,7 +75,17 @@ processDefinition =
7375
]
7476
, exitHandlers = [ ExitSignalDispatcher (\s _ m -> handleAllRawInputs s m >>= return . Just)
7577
]
76-
} []) { filters = [safely] }
78+
} []) { filters = (walkFSM prog []) }
79+
80+
-- we should probably make a Foldable (Step s d) for this
81+
walkFSM :: forall s d . Step s d -> [DispatchFilter (State s d)] -> [DispatchFilter (State s d)]
82+
walkFSM st acc
83+
| SafeWait evt act <- st = walkFSM act $ safely (\_ m -> isJust $ decodeToEvent evt m) : acc
84+
| Await _ act <- st = walkFSM act acc
85+
| Sequence ac1 ac2 <- st = walkFSM ac1 $ walkFSM ac2 acc
86+
| Init ac1 ac2 <- st = walkFSM ac1 $ walkFSM ac2 acc
87+
| Alternate ac1 ac2 <- st = walkFSM ac1 $ walkFSM ac2 acc -- both branches need filter defs
88+
| otherwise = acc
7789

7890
handleRpcRawInputs :: forall s d . (Show s) => State s d
7991
-> (P.Message, SendPort P.Message)

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ import Data.Tuple (swap, uncurry)
6767

6868
data State s d = State { stName :: s
6969
, stData :: d
70-
, stProg :: Step s d
70+
, stProg :: Step s d -- original program
71+
, stInstr :: Step s d -- current step in the program
7172
, stInput :: Maybe P.Message
7273
, stReply :: (P.Message -> Process ())
7374
, stTrans :: Seq (Transition s d)
@@ -101,6 +102,7 @@ instance forall m . (Typeable m) => Show (Event m) where
101102
data Step s d where
102103
Init :: Step s d -> Step s d -> Step s d
103104
Yield :: s -> d -> Step s d
105+
SafeWait :: (Serializable m) => Event m -> Step s d -> Step s d
104106
Await :: (Serializable m) => Event m -> Step s d -> Step s d
105107
Always :: (Serializable m) => (m -> FSM s d (Transition s d)) -> Step s d
106108
Perhaps :: (Eq s) => s -> FSM s d (Transition s d) -> Step s d
@@ -114,6 +116,7 @@ instance forall s d . (Show s) => Show (Step s d) where
114116
| Init _ _ <- st = "Init"
115117
| Yield _ _ <- st = "Yield"
116118
| Await _ s <- st = "Await (_ " ++ (show s) ++ ")"
119+
| SafeWait _ s <- st = "SafeWait (_ " ++ (show s) ++ ")"
117120
| Always _ <- st = "Always _"
118121
| Perhaps s _ <- st = "Perhaps (" ++ (show s) ++ ")"
119122
| Matching _ _ <- st = "Matching _ _"
@@ -189,11 +192,16 @@ apply st msg step
189192
P.liftIO $ putStrLn "Init _ _"
190193
st' <- apply st msg is
191194
case st' of
192-
Just s -> apply (s { stProg = ns }) msg ns
195+
Just s -> apply (s { stProg = ns, stInstr = ns }) msg ns
193196
Nothing -> die $ ExitOther $ baseErr ++ ":InitFailed"
194197
| Yield sn sd <- step = do
195198
P.liftIO $ putStrLn "Yield s d"
196199
return $ Just $ st { stName = sn, stData = sd }
200+
| SafeWait evt act' <- step = do
201+
let ev = decodeToEvent evt msg
202+
P.liftIO $ putStrLn $ (show evt) ++ " decoded: " ++ (show $ isJust ev)
203+
if isJust (ev) then apply st msg act'
204+
else (P.liftIO $ putStrLn $ "Cannot decode " ++ (show (evt, msg))) >> return Nothing
197205
| Await evt act' <- step = do
198206
let ev = decodeToEvent evt msg
199207
P.liftIO $ putStrLn $ (show evt) ++ " decoded: " ++ (show $ isJust ev)
@@ -211,8 +219,8 @@ apply st msg step
211219
runFSM st (handleMessageIf msg chk fsm) >>= mstash
212220
| Sequence ac1 ac2 <- step = do s <- apply st msg ac1
213221
P.liftIO $ putStrLn $ "Seq LHS valid: " ++ (show $ isJust s)
214-
let st' = if isJust s then fromJust s else st
215-
apply st' msg ac2
222+
if isJust s then apply (fromJust s) msg ac2
223+
else return Nothing
216224
| Alternate al1 al2 <- step = do s <- apply st msg al1
217225
P.liftIO $ putStrLn $ "Alt LHS valid: " ++ (show $ isJust s)
218226
if isJust s then return s

stack-ghc-7.10.3.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ packages:
44
- '.'
55
- location:
66
git: https://github.com/haskell-distributed/distributed-process-client-server.git
7-
commit: 36b179ec804bfdf380286082255e8b1fae099414
7+
commit: 5589d9ef5a50b86d489797f78c0118f75e53659e
88
extra-dep: true
99

1010
extra-deps:

stack-ghc-8.0.1.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ packages:
44
- '.'
55
- location:
66
git: https://github.com/haskell-distributed/distributed-process-client-server.git
7-
commit: 36b179ec804bfdf380286082255e8b1fae099414
7+
commit: 5589d9ef5a50b86d489797f78c0118f75e53659e
88
extra-dep: true
99

1010
extra-deps:

stack.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ packages:
44
- '.'
55
- location:
66
git: https://github.com/haskell-distributed/distributed-process-client-server.git
7-
commit: 36b179ec804bfdf380286082255e8b1fae099414
7+
commit: 5589d9ef5a50b86d489797f78c0118f75e53659e
88
extra-dep: true
99

1010
extra-deps:

tests/TestFSM.hs

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import Control.Rematch (equalTo)
2525
#if ! MIN_VERSION_base(4,6,0)
2626
import Prelude hiding (catch, drop)
2727
#else
28-
import Prelude hiding (drop)
28+
import Prelude hiding (drop, (*>))
2929
#endif
3030

3131
import Test.Framework as TF (defaultMain, testGroup, Test)
@@ -89,14 +89,82 @@ switchFsmAlt =
8989
(pick (await (event :: Event Check) (reply stateData))
9090
(await (event :: Event Reset) (always $ \Reset -> put initCount >> enter Off))))
9191

92+
blockingFsm :: SendPort () -> Step State ()
93+
blockingFsm sp = initState Off ()
94+
^. ((event :: Event ())
95+
*> (allState $ \() -> (lift $ sleep (seconds 10) >> sendChan sp ()) >> resume))
96+
.| ((event :: Event Stop)
97+
~> ( ((== ExitNormal) ~? (\_ -> (liftIO $ putStrLn "resuming...") >> resume) )
98+
{- let's verify that we can't override
99+
a normal shutdown sequence... -}
100+
.| ((== ExitShutdown) ~? const resume)
101+
))
102+
103+
deepFSM :: SendPort () -> SendPort () -> Step State ()
104+
deepFSM on off = initState Off ()
105+
^. ((event :: Event State) ~> (allState $ \s -> enter s))
106+
.| ( (Off ~@ resume)
107+
|> ((event :: Event ())
108+
~> (allState $ \s -> (lift $ sendChan off s) >> resume))
109+
)
110+
.| ( (On ~@ resume)
111+
|> ((event :: Event ())
112+
~> (allState $ \s -> (lift $ sendChan on s) >> resume))
113+
)
114+
115+
waitForDown :: MonitorRef -> Process DiedReason
116+
waitForDown ref =
117+
receiveWait [ matchIf (\(ProcessMonitorNotification ref' _ _) -> ref == ref')
118+
(\(ProcessMonitorNotification _ _ dr) -> return dr) ]
119+
120+
verifyOuterStateHandler :: Process ()
121+
verifyOuterStateHandler = do
122+
(spOn, rpOn) <- newChan
123+
(spOff, rpOff) <- newChan
124+
125+
pid <- start Off () $ deepFSM spOn spOff
126+
127+
send pid On
128+
send pid ()
129+
Nothing <- receiveChanTimeout (asTimeout $ seconds 3) rpOff
130+
() <- receiveChan rpOn
131+
132+
send pid Off
133+
send pid ()
134+
Nothing <- receiveChanTimeout (asTimeout $ seconds 3) rpOn
135+
() <- receiveChan rpOff
136+
137+
kill pid "bye bye"
138+
139+
verifyMailboxHandling :: Process ()
140+
verifyMailboxHandling = do
141+
(sp, rp) <- newChan :: Process (SendPort (), ReceivePort ())
142+
pid <- start Off () (blockingFsm sp)
143+
144+
send pid ()
145+
exit pid ExitNormal
146+
147+
sleep $ seconds 5
148+
alive <- isProcessAlive pid
149+
alive `shouldBe` equalTo True
150+
151+
-- we should resume after the ExitNormal handler runs, and get back into the ()
152+
-- handler due to safeWait (*>) which adds a `safe` filter check for the given type
153+
() <- receiveChan rp
154+
155+
exit pid ExitShutdown
156+
monitor pid >>= waitForDown
157+
alive' <- isProcessAlive pid
158+
alive' `shouldBe` equalTo False
159+
92160
verifyStopBehaviour :: Process ()
93161
verifyStopBehaviour = do
94162
pid <- start Off initCount switchFsm
95163
alive <- isProcessAlive pid
96164
alive `shouldBe` equalTo True
97165

98166
exit pid $ ExitOther "foobar"
99-
sleep $ seconds 5
167+
monitor pid >>= waitForDown
100168
alive' <- isProcessAlive pid
101169
alive' `shouldBe` equalTo False
102170

@@ -132,7 +200,7 @@ walkingAnFsmTree pid = do
132200
mrst' `shouldBe` equalTo On
133201

134202
exit pid ExitShutdown
135-
sleep $ seconds 5
203+
monitor pid >>= waitForDown
136204
alive' <- isProcessAlive pid
137205
alive' `shouldBe` equalTo False
138206

@@ -151,6 +219,12 @@ tests transport = do
151219
(runProcess localNode quirkyOperators)
152220
, testCase "Traversing an FSM definition (functions)"
153221
(runProcess localNode notSoQuirkyDefinitions)
222+
, testCase "Traversing an FSM definition (exit handling)"
223+
(runProcess localNode verifyStopBehaviour)
224+
, testCase "Traversing an FSM definition (mailbox handling)"
225+
(runProcess localNode verifyMailboxHandling)
226+
, testCase "Traversing an FSM definition (nested definitions)"
227+
(runProcess localNode verifyOuterStateHandler)
154228
]
155229
]
156230

0 commit comments

Comments
 (0)