From f986565050ac30075ef3c0a451bf6dad91c7c446 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
- For
- The call can fail, for example, if the
+ If you combine catching exceptions from this function
+ with
+ The call can also fail, for example, if the
+ The
If you in
+ The state entry mode is selected when starting the
+
+ If
+
+ If
+
+ If the state changes or is the initial state, and the
+
If an
@@ -1288,7 +1333,9 @@ handle_event(_, _, State, Data) ->
+ The
+ If the atom
+ No state entry event will be inserted after a
+
- If this function's body does not consist of solely one of two
- possible
-
StateName(EventType, EventContent, Data) -> - .. code for actions here ... + ... code for actions here ... {next_state, NewStateName, NewData}.
handle_event(EventType, EventContent, State, Data) -> - .. code for actions here ... + ... code for actions here ... {next_state, NewState, NewData}
+ The
+StateName(enter, _OldState, Data) -> + ... code for state entry here ... + {keep_state, NewData}; +StateName(EventType, EventContent, Data) -> + ... code for actions here ... + {next_state, NewStateName, NewData}.+
+ Depending on how your state machine is specified, + this can be a very useful feature, but if you use it + you will have to handle the state entry events in all states. +
+
This example starts off as equivalent to the example in section
-
+ Here is the same example as the previous but instead using
+ the built in
+
+ process_flag(trap_exit, true),
+ Data = #{code => Code},
+ {ok, locked, Data}.
+
+callback_mode() ->
+ [state_functions,state_entry_events].
+
+locked(enter, _OldState, Data) ->
+ do_lock(),
+ {keep_state,Data#{remaining => Code}};
+locked(
+ cast, {button,Digit},
+ #{code := Code, remaining := Remaining} = Data) ->
+ case Remaining of
+ [Digit] ->
+ {next_state, open, Data};
+...
+
+open(enter, _OldState, Data) ->
+ Tref = erlang:start_timer(10000, self(), lock),
+ do_unlock(),
+ {keep_state,Data#{timer => Tref}};
+open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
+ {next_state, locked, Data};
+...
+ ]]>
+
...
callback_mode() ->
- handle_event_function.
+ [handle_event_function,state_entry_events].
%% State: locked
-handle_event(internal, enter, locked, #{code := Code} = Data) ->
+handle_event(enter, _OldState, locked, #{code := Code} = Data) ->
do_lock(),
{keep_state,Data#{remaining => Code}};
handle_event(
@@ -1131,7 +1208,7 @@ handle_event(
#{code := Code, remaining := Remaining} = Data) ->
case Remaining of
[Digit] -> % Complete
- enter(next_state, open, Data);
+ {next_state, open, Data};
[Digit|Rest] -> % Incomplete
{keep_state,Data#{remaining := Rest}};
[_|_] -> % Wrong
@@ -1139,12 +1216,12 @@ handle_event(
end;
%%
%% State: open
-handle_event(internal, enter, open, Data) ->
+handle_event(enter, _OldState, open, Data) ->
Tref = erlang:start_timer(10000, self(), lock),
do_unlock(),
{keep_state,Data#{timer => Tref}};
handle_event(info, {timeout,Tref,lock}, open, #{timer := Tref} = Data) ->
- enter(next_state, locked, Data);
+ {next_state, locked, Data};
handle_event(cast, {button,_}, open, _) ->
{keep_state_and_data,[postpone]};
%%
@@ -1305,10 +1382,10 @@ set_lock_button(LockButton) ->
init({Code,LockButton}) ->
process_flag(trap_exit, true),
Data = #{code => Code, remaining => undefined, timer => undefined},
- enter(ok, {locked,LockButton}, Data, []).
+ {ok, {locked,LockButton}, Data}.
callback_mode() ->
- handle_event_function.
+ [handle_event_function,state_entry_events].
handle_event(
{call,From}, {set_lock_button,NewLockButton},
@@ -1320,55 +1397,52 @@ handle_event(
{_StateName,_LockButton}, #{code := Code}) ->
{keep_state_and_data,
[{reply,From,length(Code)}]};
+%%
+%% State: locked
handle_event(
EventType, EventContent,
{locked,LockButton}, #{code := Code, remaining := Remaining} = Data) ->
case {EventType,EventContent} of
- {internal,enter} ->
+ {enter,_OldState} ->
do_lock(),
{keep_state,Data#{remaining := Code}};
{{call,From},{button,Digit}} ->
case Remaining of
[Digit] -> % Complete
- next_state(
- {open,LockButton}, Data,
- [{reply,From,ok}]);
+ {next_state, {open,LockButton}, Data,
+ [{reply,From,ok}]};
[Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest},
+ {keep_state, Data#{remaining := Rest},
[{reply,From,ok}]};
[_|_] -> % Wrong
- {keep_state,Data#{remaining := Code},
+ {keep_state, Data#{remaining := Code},
[{reply,From,ok}]}
end
end;
+%%
+%% State: open
handle_event(
EventType, EventContent,
{open,LockButton}, #{timer := Timer} = Data) ->
case {EventType,EventContent} of
- {internal,enter} ->
+ {enter,_OldState} ->
Tref = erlang:start_timer(10000, self(), lock),
do_unlock(),
{keep_state,Data#{timer := Tref}};
{info,{timeout,Timer,lock}} ->
- next_state({locked,LockButton}, Data, []);
+ {next_state, {locked,LockButton}, Data};
{{call,From},{button,Digit}} ->
if
Digit =:= LockButton ->
erlang:cancel_timer(Timer),
- next_state(
- {locked,LockButton}, Data,
- [{reply,From,locked}]);
+ {next_state, {locked,LockButton}, Data,
+ [{reply,From,locked}]);
true ->
{keep_state_and_data,
[postpone]}
end
end.
-next_state(State, Data, Actions) ->
- enter(next_state, State, Data, Actions).
-enter(Tag, State, Data, Actions) ->
- {Tag,State,Data,[{next_event,internal,enter}|Actions]}.
-
do_lock() ->
io:format("Locked~n", []).
do_unlock() ->
@@ -1434,7 +1508,7 @@ handle_event(
EventType, EventContent,
{open,LockButton}, #{timer := Timer} = Data) ->
case {EventType,EventContent} of
- {internal,enter} ->
+ {enter,_OldState} ->
Tref = erlang:start_timer(10000, self(), lock),
do_unlock(),
{keep_state,Data#{timer := Tref},[hibernate]};
--
cgit v1.2.3
From 4ebdabdca2c964887115f21405993f3916843d10 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Fri, 16 Sep 2016 10:15:22 +0200
Subject: Improve docs
---
lib/stdlib/doc/src/gen_statem.xml | 33 +++++++++++++++------------------
system/doc/design_principles/statem.xml | 22 ++++++++++++++++++++--
2 files changed, 35 insertions(+), 20 deletions(-)
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index a4c5438a08..944e9ab13b 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -583,6 +583,20 @@ handle_event(_, _, State, Data) ->
Module:callback_mode/0
does not return such a list, no state entry events are inserted.
+
+ No state entry event will be inserted after a
+ Module:code_change/4
+ since transforming the state to a newer version is regarded
+ as staying in the same state even if the newer version state
+ should have a different name.
+
+
+ Note that a state entry event will be inserted
+ when entering the initial state even though this formally
+ is not a state change. In this case OldState
+ will be the same as State , which can not happen
+ for an actual state change.
+
- If the atom
- No state entry event will be inserted after a
-
If this function's body does not return an inline constant diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 8090016b54..43359829b2 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -989,7 +989,17 @@ do_unlock() -> from your state machine to itself.
- One example of using self-generated events can be when you have + One example for this is to pre-process incoming data, for example + decrypting chunks or collecting characters up to a line break. + This could be modelled with a separate state machine that sends + the pre-processed events to the main state machine, or to decrease + overhead the small pre-processing state machine can be implemented + in the common state event handling of the main state machine + using a few state data variables and then send the pre-processed + events as internal events to the main state machine. +
+
+ Another example of using self-generated events can be when you have
a state machine specification that uses state entry actions.
You can code that using a dedicated function
to do the state transition. But if you want that code to be
@@ -1050,7 +1060,15 @@ enter(Tag, State, Data) ->
+ You can also in the previous example choose to generate
+ events looking just like the events you get from using
+
Date: Tue, 20 Sep 2016 17:03:24 +0200
Subject: Improve docs
---
system/doc/design_principles/statem.xml | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 43359829b2..565b0e5274 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -1010,6 +1010,8 @@ do_unlock() ->
the state, you must explicitly
insert the internal event
or use a state transition function.
+ This is something that can be forgotten, and if you find that
+ annoying please look at the next chapter.
The following is an implementation of entry actions
@@ -1058,10 +1060,18 @@ enter(Tag, State, Data) ->
Here is the same example as the previous but instead using
the built in
state entry events .
- You will have to handle the state entry events in every state.
+
+
+ Since the state entry events are unconditionally inserted by
+ the gen_statem engine you can not forget to insert them
+ yourself and you will have to handle the state entry events
+ in every state.
+
+
If you want state entry code in just a few states the previous
example may be more suitable, especially to only send internal
events when entering just those few states.
+ Note: additional discipline will be required.
You can also in the previous example choose to generate
--
cgit v1.2.3
From 04d40c5cd18aca449606c19608e8044f593ee99e Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Thu, 22 Sep 2016 17:40:47 +0200
Subject: Change state entry events into state enter calls
---
lib/stdlib/doc/src/gen_statem.xml | 305 ++++++++++++-----
lib/stdlib/src/gen_statem.erl | 582 +++++++++++++++++---------------
lib/stdlib/test/gen_statem_SUITE.erl | 6 +-
lib/tools/emacs/erlang-skels.el | 12 +-
system/doc/design_principles/statem.xml | 194 +++++------
5 files changed, 639 insertions(+), 460 deletions(-)
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 944e9ab13b..aa34f53d29 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -54,7 +54,8 @@
This is a new behavior in Erlang/OTP 19.0.
It has been thoroughly reviewed, is stable enough
- to be used by at least two heavy OTP applications, and is here to stay.
+ to be used by at least two heavy OTP applications,
+ and is here to stay.
Depending on user feedback, we do not expect
but can find it necessary to make minor
not backward compatible changes into Erlang/OTP 20.0.
@@ -70,7 +71,7 @@
- The state can be any term.
- Events can be postponed.
- Events can be self-generated.
- - Automatic state entry events can be generated.
+ - Automatic state enter code can be called.
- A reply can be sent from a later state.
- There can be multiple
sys traceable replies.
@@ -195,10 +196,14 @@ erlang:'!' -----> Module:StateName/3
to force processing an inserted event before others.
- The gen_statem engine can automatically insert
- a special event whenever a new state is entered; see
- state_entry_mode() .
- This makes it easy to handle code common to all state entries.
+ The gen_statem engine can automatically
+ make a specialized call to the
+ state function
+ whenever a new state is entered; see
+ state_enter() .
+ This is for writing code common to all state entries.
+ Another way to do it is to insert events at state transitions,
+ but you have to do so everywhere it is needed.
If you in gen_statem , for example, postpone
@@ -526,6 +531,20 @@ handle_event(_, _, State, Data) ->
+ This is the return type from
+
- The state entry mode is selected when starting the
-
If
If
- No state entry event will be inserted after a
+ If
- Note that a state entry event will be inserted
- when entering the initial state even though this formally
- is not a state change. In this case
Transition options can be set by
- If the state changes or is the initial state, and the
-
- Notice that it is not possible or needed to cancel this time-out, + Note that it is not possible or needed to cancel this time-out, as it is cancelled automatically by any other event.
@@ -743,7 +771,10 @@ handle_event(_, _, State, Data) ->
These state transition actions can be invoked by
returning them from the
-
+ Stores the specified
+ The stored events are inserted in the queue as the next to process
+ before any already queued events. The order of these stored events
+ is preserved, so the first
+ An event of type
+
+ These state transition actions can be invoked by
+ returning them from the
+
+ Actions are executed in the containing list order. +
+
+ Actions that set
+
@@ -805,32 +883,6 @@ handle_event(_, _, State, Data) ->
to
- Replies to a caller. -
-
- Stores the specified
- The stored events are inserted in the queue as the next to process
- before any already queued events. The order of these stored events
- is preserved, so the first
- An event of type
-
- Replies to a caller waiting for a reply in
+ This state transition action can be invoked by
+ returning it from the
+
+ It replies to a caller waiting for a reply in
+ Note that using this action from
+
+ The
+ All these terms are tuples or atoms and this property
+ will hold in any future version of
+ The
+ All these terms are tuples or atoms and this property
+ will hold in any future version of
@@ -1362,7 +1475,8 @@ handle_event(_, _, State, Data) ->
once after server start and after code change,
but before the first
@@ -1380,7 +1494,7 @@ handle_event(_, _, State, Data) ->
or a list containing
@@ -1601,7 +1715,8 @@ handle_event(_, _, State, Data) ->
The function is to return
@@ -1694,6 +1823,24 @@ handle_event(_, _, State, Data) ->
by
+ When the
Note the fact that you can use
The
Depending on how your state machine is specified,
this can be a very useful feature, but if you use it
- you will have to handle the state entry events in all states.
+ you will have to handle the state enter call in all states.
- It can sometimes be beneficial to be able to generate events
- to your own state machine.
- This can be done with the state transition
-
- You can generate events of any existing
-
- One example for this is to pre-process incoming data, for example
- decrypting chunks or collecting characters up to a line break.
- This could be modelled with a separate state machine that sends
- the pre-processed events to the main state machine, or to decrease
- overhead the small pre-processing state machine can be implemented
- in the common state event handling of the main state machine
- using a few state data variables and then send the pre-processed
- events as internal events to the main state machine.
-
- Another example of using self-generated events can be when you have
- a state machine specification that uses state entry actions.
- You can code that using a dedicated function
- to do the state transition. But if you want that code to be
- visible besides the other state logic, you can insert
- an
- The following is an implementation of entry actions
- using
- Here is the same example as the previous but instead using
- the built in
-
- Since the state entry events are unconditionally inserted by
- the
- If you want state entry code in just a few states the previous
- example may be more suitable, especially to only send internal
- events when entering just those few states.
- Note: additional discipline will be required.
+ One example for this is to pre-process incoming data, for example
+ decrypting chunks or collecting characters up to a line break.
+ Purists may argue that this should be modelled with a separate
+ state machine that sends pre-processed events
+ to the main state machine.
+ But to decrease overhead the small pre-processing state machine
+ can be implemented in the common state event handling
+ of the main state machine using a few state data variables
+ that then sends the pre-processed events as internal events
+ to the main state machine.
- You can also in the previous example choose to generate
- events looking just like the events you get from using
-
+ If you start this program with
This section includes the example after all mentioned modifications
- and some more using the entry actions,
+ and some more using state enter calls,
which deserves a new state diagram:
StateName(enter, _OldState, Data) ->
- ... code for state entry here ...
+ ... code for state entry actions here ...
{keep_state, NewData};
StateName(EventType, EventContent, Data) ->
... code for actions here ...
@@ -217,7 +215,7 @@ StateName(EventType, EventContent, Data) ->
process_flag(trap_exit, true),
Data = #{code => Code},
- enter(ok, locked, Data).
+ {ok, locked, Data}.
callback_mode() ->
- state_functions.
+ [state_functions,state_enter].
-locked(internal, enter, Data) ->
+locked(enter, _OldState, Data) ->
do_lock(),
{keep_state,Data#{remaining => Code}};
locked(
@@ -1036,79 +1000,94 @@ locked(
#{code := Code, remaining := Remaining} = Data) ->
case Remaining of
[Digit] ->
- enter(next_state, open, Data);
+ {next_state, open, Data};
...
-open(internal, enter, Data) ->
+open(enter, _OldState, Data) ->
Tref = erlang:start_timer(10000, self(), lock),
do_unlock(),
{keep_state,Data#{timer => Tref}};
open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
- enter(next_state, locked, Data);
+ {next_state, locked, Data};
...
-
-enter(Tag, State, Data) ->
- {Tag,State,Data,[{next_event,internal,enter}]}.
]]>
- process_flag(trap_exit, true),
- Data = #{code => Code},
- {ok, locked, Data}.
+-export(put_chars/1, enter/0).
+...
+put_chars(Chars) when is_binary(Chars) ->
+ gen_statem:call(?NAME, {chars,Chars}).
-callback_mode() ->
- [state_functions,state_entry_events].
+enter() ->
+ gen_statem:call(?NAME, enter).
+
+...
locked(enter, _OldState, Data) ->
do_lock(),
- {keep_state,Data#{remaining => Code}};
-locked(
- cast, {button,Digit},
- #{code := Code, remaining := Remaining} = Data) ->
- case Remaining of
- [Digit] ->
- {next_state, open, Data};
+ {keep_state,Data#{remaining => Code, buf => []}};
...
-open(enter, _OldState, Data) ->
- Tref = erlang:start_timer(10000, self(), lock),
- do_unlock(),
- {keep_state,Data#{timer => Tref}};
-open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
- {next_state, locked, Data};
+handle_event({call,From}, {chars,Chars}, #{buf := Buf} = Data) ->
+ {keep_state, Data#{buf := [Chars|Buf],
+ [{reply,From,ok}]};
+handle_event({call,From}, enter, #{buf := Buf} = Data) ->
+ Chars = unicode:characters_to_binary(lists:reverse(Buf)),
+ try binary_to_integer(Chars) of
+ Digit ->
+ {keep_state, Data#{buf := []},
+ [{reply,From,ok},
+ {next_event,internal,{button,Chars}}]}
+ catch
+ error:badarg ->
+ {keep_state, Data#{buf := []},
+ [{reply,From,{error,not_an_integer}}]}
+ end;
...
]]>
+
...
callback_mode() ->
- [handle_event_function,state_entry_events].
+ [handle_event_function,state_enter].
%% State: locked
handle_event(enter, _OldState, locked, #{code := Code} = Data) ->
@@ -1413,7 +1391,7 @@ init({Code,LockButton}) ->
{ok, {locked,LockButton}, Data}.
callback_mode() ->
- [handle_event_function,state_entry_events].
+ [handle_event_function,state_enter].
handle_event(
{call,From}, {set_lock_button,NewLockButton},
--
cgit v1.2.3
From 800265f49f912dcf66846b13aa8032bf2f380caf Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
- Terminates the
- Sends all
+ All these terms are tuples or atoms and this property
+ will hold in any future version of
@@ -1053,6 +1070,36 @@ handle_event(_, _, State, Data) ->
+ Terminates the
+ Sends all
+ All these terms are tuples or atoms and this property
+ will hold in any future version of
The
This section is to be read with the
The
Depending on how your state machine is specified,
- this can be a very useful feature, but if you use it
- you will have to handle the state enter call in all states.
+ this can be a very useful feature,
+ but it forces you to handle the state enter calls in all states.
+ See also the
+
Say you have a state machine specification
--
cgit v1.2.3
From 77e175589b0ee3c1a4c94aef3cdcdf54cd84c53c Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
- If an
-
- The (possibly new)
+ Timeout timers
+
+ Otherwise the
If the value is
- If the value is
Note that it is not possible or needed to cancel this time-out, @@ -768,6 +772,34 @@ handle_event(_, _, State, Data) ->
+
+ Generates an event of
+
+ If the value is
+ If the value is
+ Setting this timer while it is running will restart it with
+ the new time-out value. Therefore it is possible to cancel
+ this timeout by setting it to
+ Sets the
+
This section is to be read with the
Established Automata Theory does not deal much with
@@ -94,7 +95,7 @@ State(S) x Event(E) -> Actions(A), State(S')
The
+ This form is used in most examples here for example in section
+
@@ -121,7 +127,13 @@ StateName(EventType, EventContent, Data) ->
+ Se section
+
@@ -134,10 +146,11 @@ handle_event(EventType, EventContent, State, Data) ->
The two
-
The
+ In the first section
+
+ There are more specific state-transition actions
+ that a callback function can order the
+ For details, see the
+
+ Events are categorized in different
+
+ The following is a complete list of event types and where
+ they come from:
+
This example starts off as equivalent to the example in section
- The code is explained in the next sections.
In the example in the previous section,
If name registration succeeds, the new The function notifying the code lock about a button event is
implemented using
@@ -451,11 +615,13 @@ button(Digit) ->
The event is made into a message and sent to the
If the door is locked and a button is pressed, the pressed
@@ -490,38 +656,55 @@ open(cast, {button,_}, Data) ->
restarts from the start of the code sequence.
- In state
+ In state
When a correct code has been given, the door is unlocked and
the following tuple is returned from
10,000 is a time-out value in milliseconds.
After this time (10 seconds), a time-out occurs.
- Then,
+ The timer for a state time-out is automatically cancelled
+ when the state machine changes states. You can restart
+ a state time-out by setting it to a new time, which cancels
+ the running timer and starts a new. This implies that
+ you can cancel a state time-out by restarting it with
+ time
Sometimes events can arrive in any state of the
This example uses
If mode
If the
If the
- In the first sections actions were mentioned as a part of
- the general state machine model. These general actions
- are implemented with the code that callback module
-
- There are more specific state-transition actions
- that a callback function can order the
- In the example earlier was mentioned the event time-out
- and replying to a caller.
- An example of event postponing is included later in this chapter.
- For details, see the
-
- The previous sections mentioned a few
-
- The following is a complete list of event types and where
- they come from:
+ An event timeout is cancelled by any other event so you either
+ get some other event or the timeout event. It is therefore
+ not possible nor needed to cancel or restart an event timeout.
+ Whatever event you act on has already cancelled
+ the event timeout...
- The time-out event generated by state transition action
-
- Often you want a timer not to be cancelled by any event
- or you want to start a timer in one state and respond
- to the time-out in another. This can be accomplished
- with a regular Erlang timer:
-
- For the example so far in this chapter: using the
-
- Suppose that we do not want a button to lock the door,
- instead we want to ignore button events in the
+ Removing the
If you need to cancel a timer because of some other event, you can use
- Another way to cancel a timer is not to cancel it,
+ Another way to handle a late time-out can be to not cancel it,
but to ignore it if it arrives in a state
where it is known to be late.
If you want to ignore a particular event in the current state
@@ -877,6 +1029,7 @@ open(cast, {button,_}, Data) ->
It is not uncommon that a state diagram does not specify
@@ -893,6 +1046,7 @@ open(cast, {button,_}, Data) ->
Erlang's selective receive statement is often used to
@@ -972,7 +1126,7 @@ do_unlock() ->
Say you have a state machine specification
@@ -981,7 +1135,7 @@ do_unlock() ->
(described in the next section), especially if just
one or a few states has got state entry actions,
this is a perfect use case for the built in
-
You return a list containing
It can sometimes be beneficial to be able to generate events
@@ -1054,7 +1208,7 @@ open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
to the main state machine.
- The following example use an input model where you give the lock
+ The following example uses an input model where you give the lock
characters with
- This section includes the example after all mentioned modifications
- and some more using state enter calls,
+ This section includes the example after most of the mentioned
+ modifications and some more using state enter calls,
which deserves a new state diagram:
Using state functions:
@@ -1155,7 +1311,11 @@ callback_mode() ->
locked(enter, _OldState, #{code := Code} = Data) ->
do_lock(),
- {keep_state,Data#{remaining => Code}};
+ {keep_state, Data#{remaining => Code}};
+locked(
+ timeout, _,
+ #{code := Code, remaining := Remaining} = Data) ->
+ {keep_state, Data#{remaining := Code}};
locked(
cast, {button,Digit},
#{code := Code, remaining := Remaining} = Data) ->
@@ -1163,26 +1323,25 @@ locked(
[Digit] -> % Complete
{next_state, open, Data};
[Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest}};
+ {keep_state, Data#{remaining := Rest}, 30000};
[_|_] -> % Wrong
- {keep_state,Data#{remaining := Code}}
+ {keep_state, Data#{remaining := Code}}
end;
locked(EventType, EventContent, Data) ->
handle_event(EventType, EventContent, Data).
-open(enter, _OldState, Data) ->
- Tref = erlang:start_timer(10000, self(), lock),
+open(enter, _OldState, _Data) ->
do_unlock(),
- {keep_state,Data#{timer => Tref}};
-open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
+ {keep_state_and_data, [{state_timeout,10000,lock}]};
+open(state_timeout, lock, Data) ->
{next_state, locked, Data};
open(cast, {button,_}, _) ->
- {keep_state_and_data,[postpone]};
+ {keep_state_and_data, [postpone]};
open(EventType, EventContent, Data) ->
handle_event(EventType, EventContent, Data).
handle_event({call,From}, code_length, #{code := Code}) ->
- {keep_state_and_data,[{reply,From,length(Code)}]}.
+ {keep_state_and_data, [{reply,From,length(Code)}]}.
do_lock() ->
io:format("Locked~n", []).
@@ -1198,6 +1357,7 @@ code_change(_Vsn, State, Data, _Extra) ->
This section describes what to change in the example
@@ -1215,9 +1375,15 @@ callback_mode() ->
[handle_event_function,state_enter].
%% State: locked
-handle_event(enter, _OldState, locked, #{code := Code} = Data) ->
+handle_event(
+ enter, _OldState, locked,
+ #{code := Code} = Data) ->
do_lock(),
- {keep_state,Data#{remaining => Code}};
+ {keep_state, Data#{remaining => Code}};
+handle_event(
+ timeout, _, locked,
+ #{code := Code, remaining := Remaining} = Data) ->
+ {keep_state, Data#{remaining := Code}};
handle_event(
cast, {button,Digit}, locked,
#{code := Code, remaining := Remaining} = Data) ->
@@ -1225,31 +1391,30 @@ handle_event(
[Digit] -> % Complete
{next_state, open, Data};
[Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest}};
+ {keep_state, Data#{remaining := Rest}, 30000};
[_|_] -> % Wrong
- {keep_state,Data#{remaining := Code}}
+ {keep_state, Data#{remaining := Code}}
end;
%%
%% State: open
-handle_event(enter, _OldState, open, Data) ->
- Tref = erlang:start_timer(10000, self(), lock),
+handle_event(enter, _OldState, open, _Data) ->
do_unlock(),
- {keep_state,Data#{timer => Tref}};
-handle_event(info, {timeout,Tref,lock}, open, #{timer := Tref} = Data) ->
+ {keep_state_and_data, [{state_timeout,10000,lock}]};
+handle_event(state_timeout, lock, open, Data) ->
{next_state, locked, Data};
handle_event(cast, {button,_}, open, _) ->
{keep_state_and_data,[postpone]};
%%
%% Any state
handle_event({call,From}, code_length, _State, #{code := Code}) ->
- {keep_state_and_data,[{reply,From,length(Code)}]}.
+ {keep_state_and_data, [{reply,From,length(Code)}]}.
...
]]>
Notice that postponing buttons from the
The example servers so far in this chapter
@@ -1317,12 +1483,13 @@ format_status(Opt, [_PDict,State,Data]) ->
The callback mode
@@ -1396,7 +1563,7 @@ set_lock_button(LockButton) ->
init({Code,LockButton}) ->
process_flag(trap_exit, true),
- Data = #{code => Code, remaining => undefined, timer => undefined},
+ Data = #{code => Code, remaining => undefined},
{ok, {locked,LockButton}, Data}.
callback_mode() ->
@@ -1405,29 +1572,31 @@ callback_mode() ->
handle_event(
{call,From}, {set_lock_button,NewLockButton},
{StateName,OldLockButton}, Data) ->
- {next_state,{StateName,NewLockButton},Data,
+ {next_state, {StateName,NewLockButton}, Data,
[{reply,From,OldLockButton}]};
handle_event(
{call,From}, code_length,
{_StateName,_LockButton}, #{code := Code}) ->
{keep_state_and_data,
- [{reply,From,length(Code)}]};
+ [{reply,From,length(Code)}]};
%%
%% State: locked
handle_event(
EventType, EventContent,
{locked,LockButton}, #{code := Code, remaining := Remaining} = Data) ->
- case {EventType,EventContent} of
- {enter,_OldState} ->
+ case {EventType, EventContent} of
+ {enter, _OldState} ->
do_lock(),
- {keep_state,Data#{remaining := Code}};
- {{call,From},{button,Digit}} ->
+ {keep_state, Data#{remaining := Code}};
+ {timeout, _} ->
+ {keep_state, Data#{remaining := Code}};
+ {{call,From}, {button,Digit}} ->
case Remaining of
[Digit] -> % Complete
{next_state, {open,LockButton}, Data,
[{reply,From,ok}]};
[Digit|Rest] -> % Incomplete
- {keep_state, Data#{remaining := Rest},
+ {keep_state, Data#{remaining := Rest, 30000},
[{reply,From,ok}]};
[_|_] -> % Wrong
{keep_state, Data#{remaining := Code},
@@ -1438,18 +1607,16 @@ handle_event(
%% State: open
handle_event(
EventType, EventContent,
- {open,LockButton}, #{timer := Timer} = Data) ->
- case {EventType,EventContent} of
- {enter,_OldState} ->
- Tref = erlang:start_timer(10000, self(), lock),
+ {open,LockButton}, Data) ->
+ case {EventType, EventContent} of
+ {enter, _OldState} ->
do_unlock(),
- {keep_state,Data#{timer := Tref}};
- {info,{timeout,Timer,lock}} ->
+ {keep_state_and_data, [{state_timeout,10000,lock}]};
+ {state_timeout, lock} ->
{next_state, {locked,LockButton}, Data};
- {{call,From},{button,Digit}} ->
+ {{call,From}, {button,Digit}} ->
if
Digit =:= LockButton ->
- erlang:cancel_timer(Timer),
{next_state, {locked,LockButton}, Data,
[{reply,From,locked}]);
true ->
@@ -1494,6 +1661,7 @@ format_status(Opt, [_PDict,State,Data]) ->
If you have many servers in one node
@@ -1519,20 +1687,21 @@ format_status(Opt, [_PDict,State,Data]) ->
- The
-
+ Another not uncommon scenario is to use the event time-out
+ to triger hibernation after a certain time of inactivity.
+
This server probably does not use
heap memory worth hibernating for.
--
cgit v1.2.3
From f4de3f5887be010db178a178e1f20027f3e5d22b Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
- The "state function" for a specific
+ The "state callback" for a specific
The
- The
The If you in
+ If the
+
If there are enqueued events the (possibly new)
-
These state transition actions can be invoked by
returning them from the
-
These state transition actions can be invoked by
returning them from the
-
This state transition action can be invoked by
returning it from the
-
Note that using this action from
@@ -956,77 +962,48 @@ handle_event(_, _, State, Data) ->
- The
- All these terms are tuples or atoms and this property
- will hold in any future version of
The
- All these terms are tuples or atoms and this property
- will hold in any future version of
- The
- All these terms are tuples or atoms and this property
- will hold in any future version of
- All these terms are tuples or atoms and this property
- will hold in any future version of
- The
- The
- All these terms are tuples or atoms and this property
- will hold in any future version of
- All these terms are tuples or atoms and this property
- will hold in any future version of
@@ -1155,14 +1093,14 @@ handle_event(_, _, State, Data) ->
by sending a request
and waiting until its reply arrives.
The
A
@@ -1562,7 +1500,7 @@ handle_event(_, _, State, Data) ->
for efficiency reasons, so this function is only called
once after server start and after code change,
but before the first
-
If the initialization fails,
@@ -1829,13 +1767,13 @@ handle_event(_, _, State, Data) ->
StateName(EventType, EventContent, Data) ->
... code for actions here ...
- {next_state, NewStateName, NewData}.
+ {next_state, NewStateName, NewData}.
+
+
handle_event(EventType, EventContent, State, Data) ->
... code for actions here ...
- {next_state, NewState, NewData}
+ {next_state, NewState, NewData}
+
+
+
+
init(Code) ->
do_lock(),
Data = #{code => Code, remaining => Code},
- {ok,locked,Data}.
+ {ok, locked, Data}.
callback_mode() ->
state_functions.
@@ -287,19 +449,19 @@ locked(
case Remaining of
[Digit] ->
do_unlock(),
- {next_state,open,Data#{remaining := Code},10000};
+ {next_state, open, Data#{remaining := Code},
+ [{state_timeout,10000,lock}];
[Digit|Rest] -> % Incomplete
- {next_state,locked,Data#{remaining := Rest}};
+ {next_state, locked, Data#{remaining := Rest}};
_Wrong ->
- {next_state,locked,Data#{remaining := Code}}
+ {next_state, locked, Data#{remaining := Code}}
end.
-open(timeout, _, Data) ->
+open(state_timeout, lock, Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state, locked, Data};
open(cast, {button,_}, Data) ->
- do_lock(),
- {next_state,locked,Data}.
+ {next_state, open, Data}.
do_lock() ->
io:format("Lock~n", []).
@@ -310,7 +472,7 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {ok,State,Data}.
+ {ok, State, Data}.
]]>
% Complete
do_unlock(),
- {next_state,open,Data#{remaining := Code},10000};
+ {next_state, open, Data#{remaining := Code},
+ [{state_timeout,10000,lock}]};
[Digit|Rest] -> % Incomplete
- {next_state,locked,Data#{remaining := Rest}};
+ {next_state, locked, Data#{remaining := Rest}};
[_|_] -> % Wrong
- {next_state,locked,Data#{remaining := Code}}
+ {next_state, locked, Data#{remaining := Code}}
end.
-open(timeout, _, Data) ->
+open(state_timeout, lock, Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state, locked, Data};
open(cast, {button,_}, Data) ->
- do_lock(),
- {next_state,locked,Data}.
+ {next_state, open, Data}.
]]>
+open(state_timeout, lock, Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state, locked, Data};
]]>
+
-
-
do_unlock(),
Tref = erlang:start_timer(10000, self(), lock),
- {next_state,open,Data#{remaining := Code, timer := Tref}};
+ {next_state, open, Data#{remaining := Code, timer => Tref}};
...
open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state,locked,maps:remove(timer, Data)};
open(cast, {button,_}, Data) ->
{keep_state,Data};
...
]]>
+
- case {EventType,EventContent} of
- {enter,_OldState} ->
- Tref = erlang:start_timer(10000, self(), lock),
+ {open,LockButton}, Data) ->
+ case {EventType, EventContent} of
+ {enter, _OldState} ->
do_unlock(),
- {keep_state,Data#{timer := Tref},[hibernate]};
+ {keep_state_and_data,
+ [{state_timeout,10000,lock},hibernate]};
...
]]>
callback_mode() -> handle_event_function.
-%%% State function(s)
+%%% state callback(s)
handle_event({call,From}, push, off, Data) ->
%% Go to 'on', increment count and reply
@@ -482,6 +482,10 @@ handle_event(_, _, State, Data) ->