Suspension
Freezing a state in place with suspend, immediate suspend and weak suspend, and what that does to its actions and transitions.
Open a microwave halfway through and it stops. Shut the door and it carries on: the timer does not start again from the beginning, and it does not skip ahead either. It was simply paused.
The aborts you have met so far end a state for good. Suspension is a third way to interrupt a state from outside: it stops the state for a tick, or for many ticks, without leaving it. When the suspension lifts, the state continues exactly where it was. (Together, weak aborts, strong aborts and suspension are called preemption: something outside a state takes control away from it.)
A pause button
Section titled “A pause button”Here is a microwave that counts down from 3. Its cooking state carries one extra line,
suspend if doorOpen:
Diagram
Code
scchart Microwave { input bool doorOpen output int remaining = 3 output bool beep = false
initial state Cooking { suspend if doorOpen
initial state Heating if remaining > 1 do remaining-- go to Heating if remaining == 1 do remaining-- go to Finished
final state Finished } join to Done
state Done { entry do beep = true }}| Tick | input bool doorOpen | output int remaining | output bool beep | Active state of the chart | Active state of the region of Cooking |
|---|
Guided tour, 7 ticks:
- doorOpen = falseTick 1 enters
Cookingand its initial stateHeating. The countdown transitions are delayed, soremainingstarts at 3. - The door is shut, so
Cookingruns normally: the self-loop onHeatingcounts down to 2. - doorOpen = trueThe door opens.
suspend if doorOpenholds, so nothing insideCookingruns this tick:remainingstays 2 andHeatingstays active. - Still open, still frozen. A suspended state keeps its place; it does not restart or move on.
- doorOpen = falseThe door shuts and
Cookingcarries on exactly where it stopped:remaininggoes from 2 to 1. - The last step goes to the final state
Finished(remaining= 0), soCookingterminates andjoin to Donefires in the same tick:beepis true. - In
Done. Nothing else happens.
remaining stops at 2 and carries on from 2 when the door shuts. After the tour, press Take over to try it yourself: doorOpen stays on until you switch it off.KIELER’s trace of the tour, with the state and the values after each tick:
| Tick | doorOpen |
State | remaining |
beep |
|---|---|---|---|---|
| 1 | false | Cooking (Heating) | 3 | false |
| 2 | false (held) | Cooking (Heating) | 2 | false |
| 3 | true | Cooking (Heating) | 2 | false |
| 4 | true (held) | Cooking (Heating) | 2 | false |
| 5 | false | Cooking (Heating) | 1 | false |
| 6 | false (held) | Done | 0 | true |
| 7 | false (held) | Done | 0 | true |
In ticks 3 and 4 the self-loop on Heating would normally fire, but Cooking is suspended, so
nothing inside it moves. In tick 5 the count resumes from 2. Nothing was lost and nothing restarted.
The rule
Section titled “The rule”suspend if c is a state action, written at the top of a state next to its entry, during and
exit actions. The diagram lists it with them, as suspend doorOpen.
In every tick in which c holds, the state is suspended, and nothing that belongs to it runs:
- no transition inside it fires, delayed or immediate;
- no
duringaction runs, neither the state’s own nor those of the states inside it; - no entry or exit action of an inner state runs;
- count delays inside it (
if 3 press) do not count that tick.
The active inner states stay active. In the next tick in which c is false, the state carries on
from them as if the suspended ticks had not happened.
Three details complete the rule:
- A plain
suspendis delayed, like a delayed transition: it is not checked in the tick the state is entered, only from the next tick on. Immediate suspend changes that. - Only the state that carries the
suspend, and what is inside it, freezes. The states around it and the regions next to it keep reacting as usual. - Several
suspendactions on one state suspend it whenever any of their triggers holds.
What freezes, and what does not
Section titled “What freezes, and what does not”Suspension stops behaviour, not time. Whatever looks at “the previous tick” still sees the previous
tick, even if that tick was a suspended one. The most important case is pre.
The chart below measures how much sensor changed since the last tick, sensor - pre(sensor), and
has a hold input that suspends it. Before you run it, predict what change is in tick 3, and in
tick 5, the first tick after the hold. Then open the explanation below.
Diagram
Code
scchart RateOfChange { input bool hold input int sensor output int change = 0
initial state Measuring { suspend if hold
initial state Sample if true do change = sensor - pre(sensor) go to Sample }}hold is true in ticks 3 and 4. In tick 5, does pre(sensor) remember 12 or 20?
Inputs per tick. A value in grey was not set in that tick: it holds from an earlier one.
| Tick | hold | sensor |
|---|---|---|
| 1 | false | 10 |
| 2 | false | 12 |
| 3 | true | 15 |
| 4 | true | 20 |
| 5 | false | 26 |
Explanation
In tick 3 nothing runs, so change keeps its old value, 2. In tick 5 the transition runs again, and
pre(sensor) is 20, the value sensor had in tick 4. That tick was suspended, but pre does not
care: it always means “at the end of the previous tick”. So change is 26 − 20 = 6. If you wanted
“the change since the last tick that ran”, you would have to keep that value yourself, in a variable
that the transition writes.
Count delays, on the other hand, belong to the state’s behaviour, so they do freeze. A transition
if 3 press go to Done inside a suspended state counts only the presses in unsuspended ticks.
Surprises
Section titled “Surprises”The rule fits in a paragraph, but the way KIELER implements it has consequences that catch people
out. Suspension does not exist in KIELER’s core language. The compiler rewrites a suspend into a
hidden flag, _enabled, which is recomputed in every tick, and adds && _enabled to the trigger of
every action and transition that belongs to the state. Each surprise below comes from something that
counts as “belonging to the state”.
A suspended state cannot be left by its own transitions
Section titled “A suspended state cannot be left by its own transitions”The state’s own outgoing transitions belong to it too. While the state is suspended, they cannot fire.
That is a real trap. Give the microwave a cancel button with if cancel go to Idle on Cooking, and
cancelling stops working while the door is open. The fix is to put the suspension one level further in:
a superstate Oven holds the cancel transition, and only the Cooking inside it is suspended.
Diagrams
CancelTrap
CancelFixed
Code
CancelTrap
scchart CancelTrap { input bool doorOpen, cancel output int remaining = 5
initial state Cooking { suspend if doorOpen
initial state Heating if remaining > 0 do remaining-- go to Heating } if cancel go to Idle
state Idle}CancelFixed
scchart CancelFixed { input bool doorOpen, cancel output int remaining = 5
initial state Oven { initial state Cooking { suspend if doorOpen
initial state Heating if remaining > 0 do remaining-- go to Heating } } if cancel go to Idle
state Idle}| Tick | input bool doorOpen | input bool cancel | CancelTrap: output int remaining | CancelTrap: Active state of the chart | CancelTrap: Active state of the region of Cooking | CancelFixed: output int remaining | CancelFixed: Active state of the chart | CancelFixed: Active state of the region of Oven | CancelFixed: Active state of the region of Oven.Cooking |
|---|
Guided tour, 7 ticks, the same inputs for both charts:
- doorOpen = false, cancel = falseTick 1 enters both ovens with
remaining= 5. InCancelTrapthesuspendandif cancel go to Idleare both onCooking. InCancelFixedthe cancel transition is onOven, the state aroundCooking. - Both count down: 4.
- doorOpen = trueThe door opens and both
Cookingstates are suspended:remainingstays 4. InCancelFixed,Ovenis not suspended: only the state that carries thesuspendfreezes. - cancel = true
cancelis pressed while the door is open.CancelTrap: the cancel transition belongs to the suspendedCooking, so it is frozen with the rest of it, and the press is ignored.CancelFixed: the transition belongs toOven, which is not suspended, so it fires and the oven goes toIdle. - cancel = false
cancelis released.CancelTrapis still suspended;CancelFixedis inIdle. - doorOpen = falseThe door shuts.
CancelTrapcarries on cooking (3): the cancel press was lost.CancelFixedstays inIdle, withremainingstill 4. CancelTrapcounts on (2).CancelFixednever resumes.
Note: While Cooking is suspended, its own outgoing transitions cannot fire either (CancelTrap)
- Note in
CancelTrap, line 6, column 5KIELER's suspend freezes everything that belongs toCooking, including the transitions that leave it. To leave a suspended state, put the transition on an enclosing state.
cancel in tick 4. CancelTrap ignores the press and later carries on cooking; CancelFixed goes to Idle at once.The rule of thumb: put the suspend on an inner state and the ways out on a state around it. The
outer state is never suspended, so its transitions always work.
Diagram
Code
scchart CancelRejected { input bool doorOpen, cancel output int remaining = 5
initial state Cooking { suspend if doorOpen
initial state Heating if remaining > 0 do remaining-- go to Heating } if cancel abort to Idle
state Idle}KIELER rejects this chart
- Line 11, column 3The strong abort out of
CookingandCooking's own suspend depend on each other within the tickKIELER says:The SCG is NOT asc-schedulable!
A strong abort is decided before anything insideCookingruns, butCooking'ssuspendalso freezesCooking's own transitions, so the abort waits forCooking's suspend condition, which is worked out insideCooking. Move the abort to a state aroundCooking: KIELER rejects everyabort toout of a state that has its ownsuspend.About this message
Also a note: While Cooking is suspended, its own outgoing transitions cannot fire either
- Note, line 6, column 5KIELER's suspend freezes everything that belongs to
Cooking, including the transitions that leave it. To leave a suspended state, put the transition on an enclosing state.
CancelTrap with abort to in place of go to: KIELER rejects it.Entry actions can be lost
Section titled “Entry actions can be lost”The state’s own entry and exit actions belong to it as well, and here the hidden flag misbehaves: when
the state is entered, its entry actions run before the flag has been switched on for the first time.
The result is that a state with a suspend skips its own entry actions the first time it is
entered, even if the suspend never triggers.
This chart counts cooking sessions with an entry action on Cooking:
Diagram
Code
scchart SessionCounter { input bool start, stop, doorOpen output int sessions = 0
initial state Idle if start go to Cooking
state Cooking { suspend if doorOpen entry do sessions++
initial state Heating } if stop go to Idle}| Tick | input bool start | input bool stop | input bool doorOpen | output int sessions | Active state of the chart | Active state of the region of Cooking |
|---|
Guided tour, 7 ticks:
- start = false, stop = false, doorOpen = falseTick 1 starts in
Idle. - start = trueSurprise:
Cookingis entered, but its entry action does not run (sessionsstays 0). KIELER guards it with the suspend’s internal flag, which is only switched on after the entry action has looked at it. - start = false, stop = trueBack to
Idle. - start = true, stop = falseThe second entry does run the entry action (
sessions= 1): the flag kept its value from the last visit. - start = falseCooking.
- stop = trueBack to
Idle. Two sessions, butsessionssays 1. - stop = falseIn
Idle.
2 notes
- Note, line 9, column 5While
Cookingis suspended, its own outgoing transitions cannot fire eitherKIELER's suspend freezes everything that belongs toCooking, including the transitions that leave it. To leave a suspended state, put the transition on an enclosing state. - Note, line 10, column 5Because
Cookinghas a suspend, KIELER skips its entry actions whenCookingis entered for the first timeKIELER guards every action of a suspendable state with a flag that its during actions compute, and they run after the entry actions; before the first tick the flag is false. Later entries run them only ifCookingwas not suspended when it was last left. Put the set-up into the transition that entersCooking, or into an inner state.
Cooking (tick 2) leaves sessions at 0; the second (tick 4) counts.KIELER gives no error and no warning; the count is simply wrong.
The safe habit is to keep entry and exit actions off the state that carries the suspend. Do the
set-up on the transition that enters the state (if start do sessions++ go to Cooking), or in an entry
action of a state inside it: the entry actions of the inner initial states do run on the first entry,
as long as the state is not suspended in that tick.
A suspend can delay termination
Section titled “A suspend can delay termination”The join to of a superstate belongs to the superstate, so it gets && _enabled added to its trigger.
A termination with a trigger is delayed (see Superstates), so a superstate whose
inside finishes in the tick it is entered terminates one tick later once it has a suspend. The two
charts below differ only in the line suspend if doorOpen, and the door stays shut throughout:
Diagrams
PreheatPlain
PreheatSuspend
Code
PreheatPlain
scchart PreheatPlain { input bool doorOpen output bool heater = false
initial state Prepare { initial state Check immediate go to Ready
final state Ready } join to Heating
state Heating { entry do heater = true }}PreheatSuspend
scchart PreheatSuspend { input bool doorOpen output bool heater = false
initial state Prepare { suspend if doorOpen
initial state Check immediate go to Ready
final state Ready } join to Heating
state Heating { entry do heater = true }}| Tick | input bool doorOpen | PreheatPlain: output bool heater | PreheatPlain: Active state of the chart | PreheatPlain: Active state of the region of Prepare | PreheatSuspend: output bool heater | PreheatSuspend: Active state of the chart | PreheatSuspend: Active state of the region of Prepare |
|---|
Guided tour, 3 ticks, the same inputs for both charts:
- doorOpen = falseIn both charts the inside of
Prepareruns straight through to its final stateReadyin tick 1. InPreheatPlain,Prepareterminates at once andjoin to Heatingfires:heater= true. InPreheatSuspendthesuspendhas added a condition toPrepare’s termination, and a termination with a condition is delayed:heaterstays false, although the door is shut. - In
PreheatSuspend,Prepareterminates now, one tick late:heater= true in both. - Both in
Heating.
Note: Because Prepare has a suspend, this join to waits one tick longer when the inside finishes in Prepare's first tick (PreheatSuspend)
- Note in
PreheatSuspend, line 13, column 3The suspend flag becomes part of the termination's trigger, and a termination with a trigger is delayed.
Prepare finishes in its entry tick in both charts, but PreheatSuspend switches the heater on a tick later.If the inside takes at least one tick to finish, as in the microwave, the timing is the same with or without the suspend.
Immediate suspend
Section titled “Immediate suspend”A plain suspend is not checked in the tick its state is entered; an immediate suspend is. The two
charts below differ only in that keyword. Both enter Cooking with the door already open, and the
inner transition immediate do heater = true go to Heating runs as soon as it is allowed to:
Diagrams
StartDelayed
StartImmediate
Code
StartDelayed
scchart StartDelayed { input bool doorOpen output bool heater = false output int remaining = 3
initial state Cooking { suspend if doorOpen
initial state Start immediate do heater = true go to Heating
state Heating if remaining > 0 do remaining-- go to Heating }}StartImmediate
scchart StartImmediate { input bool doorOpen output bool heater = false output int remaining = 3
initial state Cooking { immediate suspend if doorOpen
initial state Start immediate do heater = true go to Heating
state Heating if remaining > 0 do remaining-- go to Heating }}| Tick | input bool doorOpen | StartDelayed: output bool heater | StartDelayed: output int remaining | StartDelayed: Active state of the chart | StartDelayed: Active state of the region of Cooking | StartImmediate: output bool heater | StartImmediate: output int remaining | StartImmediate: Active state of the chart | StartImmediate: Active state of the region of Cooking |
|---|
Guided tour, 4 ticks, the same inputs for both charts:
- doorOpen = trueThe door is already open in tick 1.
StartDelayed: a plainsuspendis not checked in the tickCookingis entered, so the immediate transition fromStartruns and switches the heater on.StartImmediate:immediate suspendis checked in the entry tick too, soCookingis suspended at once and the heater stays off. - From tick 2 on, the plain
suspendis checked as well. The door is open, so both charts are frozen. - doorOpen = falseThe door shuts.
StartDelayedis already inHeating, so its countdown starts:remaining= 2.StartImmediatenow runs the immediate transition that waited: the heater goes on andHeatingis entered, but its delayed countdown can only start in the next tick. - Both count down,
StartImmediateone tick behind:remainingis 1 and 2.
StartDelayed switches the heater on anyway, StartImmediate waits until the door shuts in tick 3.heater and remaining after each tick:
| Tick | doorOpen |
Delayed | Immediate |
|---|---|---|---|
| 1 | true | true, 3 | false, 3 |
| 2 | true (held) | true, 3 | false, 3 |
| 3 | false | true, 2 | true, 3 |
| 4 | false (held) | true, 1 | true, 2 |
StartImmediate’s countdown also starts a tick later: the transition that waited enters Heating in
tick 3, and Heating’s delayed self-loop can only fire from tick 4.
A state whose immediate suspend holds in its entry tick does none of its entry-tick behaviour. Its
immediate inner transitions and immediate during actions wait until it resumes, and then run. Its
entry actions, and those of its inner initial states, are not postponed: they are skipped for good.
Use immediate suspend when a state must do nothing at all while the condition holds, including in
the tick it starts, and keep its set-up out of entry actions.
Weak suspend
Section titled “Weak suspend”weak suspend if c sounds like a gentler suspend, but it does something quite different. In a tick
in which c holds, the inside runs normally: transitions fire, entry and during actions run, and the
values they write are kept. Then, at the end of the tick, every inner region is put back into the
state it was in at the end of the last tick without weak suspension, and no entry action runs on the
way back. The outputs of the tick happen; the moves do not count.
Diagram
Code
scchart Preview { input bool preview, next output int page = 1
initial state Slides { weak suspend if preview
initial state Page1 if next go to Page2
state Page2 { entry do page = 2 } if next go to Page3
state Page3 { entry do page = 3 } }}| Tick | input bool preview | input bool next | output int page | Active state of the chart | Active state of the region of Slides |
|---|
Guided tour, 6 ticks:
- preview = false, next = falseTick 1 enters
SlidesandPage1;page= 1. - preview = true, next = true
weak suspendholds, but unlikesuspendthe inside still runs:Page1goes toPage2and its entry action setspage= 2. At the end of the tick, though, the region is put back intoPage1. - next = falseThe chart is in
Page1whilepagestill says 2: the data change was kept, the state change was not. - preview = falseNo longer suspended. Still in
Page1. - next = true
Page1toPage2again:page= 2. nextis still true, soPage2moves on toPage3:page= 3.
next moves to Page2 and sets page = 2, but the chart is back in Page1 afterwards. In tick 5 the same transition fires again.After tick 2, page says 2 while the chart is in Page1: data and control disagree, which is rarely
what you want. Unlike suspend, a weak suspend does not block the state’s own outgoing transitions,
and immediate weak suspend applies in the entry tick too.
Try it
Section titled “Try it”A metronome with a pause
Metronome toggles click in every tick until stop is pressed. Add a pause input that freezes the
beat, so that when pause is released the metronome carries on where it was. stop must still work
while the metronome is paused: check it by setting pause and then stop.
Show the starter chart
scchart Metronome { input bool pause, stop output bool click = false
initial state Running { initial state Low do click = true go to High
state High do click = false go to Low } if stop go to Stopped
state Stopped}Show a solution
scchart Metronome { input bool pause, stop output bool click = false
initial state Running { initial state Beating { suspend if pause
initial state Low do click = true go to High
state High do click = false go to Low } } if stop go to Stopped
state Stopped}Count every session
SessionCounter misses the first cooking session, because the entry action on the suspendable
Cooking does not run on its first entry. Change the chart so that sessions counts every visit to
Cooking, without removing the suspend.
Show the starter chart
scchart SessionCounter { input bool start, stop, doorOpen output int sessions = 0
initial state Idle if start go to Cooking
state Cooking { suspend if doorOpen entry do sessions++
initial state Heating } if stop go to Idle}Show a solution
scchart SessionCounter { input bool start, stop, doorOpen output int sessions = 0
initial state Idle if start do sessions++ go to Cooking
state Cooking { suspend if doorOpen
initial state Heating } if stop go to Idle}Check your understanding
Section titled “Check your understanding”Show the answer
Answer C: S stays where it is and nothing inside it runs: the transition to T is frozen too
S’s own outgoing transitions belong to S, so they are suspended with it. T is reached in the first
tick in which X holds and P does not. To be able to leave while suspended, put the suspend on a state
inside S and keep the transition on S.
Show the answer
Answer A: Yes: a plain suspend is not checked in the tick the state is entered
A plain suspend is delayed, like a delayed transition: it is checked from the tick after the entry on.
In the entry tick the state behaves normally (apart from the entry action quirk).
immediate suspend if P would suspend it in tick 4 already.
Key ideas
suspend if cfreezes a state in the ticks in whichcholds: nothing inside it runs and nothing is counted, and it resumes where it stopped.- A plain
suspendis not checked in the entry tick;immediate suspendis, and then swallows the entry actions. preis not frozen: it always looks at the previous tick, suspended or not.- A suspended state cannot be left by its own transitions, and
abort toon it is rejected. Put thesuspendon an inner state and the ways out on the state around it. - Keep
entryandexitactions off a state with asuspend: KIELER skips them in some ticks without telling you. weak suspendruns the tick and then forgets the state changes. You will rarely need it.
Next, History and deferred transitions: coming back to a superstate where you left it, and arriving in a state without its arrival behaviour.