From 6ee0aefd8a0ea9c165211c42d5244182b5aa9210 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 13 Sep 2016 14:00:04 +0200 Subject: Implement state entry events --- system/doc/design_principles/statem.xml | 152 ++++++++++++++++++++++++-------- 1 file changed, 113 insertions(+), 39 deletions(-) (limited to 'system/doc') diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 57e47431b8..8090016b54 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -109,7 +109,7 @@ State(S) x Event(E) -> Actions(A), State(S')

 StateName(EventType, EventContent, Data) ->
-    .. code for actions here ...
+    ... code for actions here ...
     {next_state, NewStateName, NewData}.
@@ -120,7 +120,7 @@ StateName(EventType, EventContent, Data) ->

 handle_event(EventType, EventContent, State, Data) ->
-    .. code for actions here ...
+    ... code for actions here ...
     {next_state, NewState, NewData}
@@ -192,13 +192,42 @@ handle_event(EventType, EventContent, State, Data) -> + + +
+ + State Entry Events +

+ The gen_statem behavior can regardless of callback mode + automatically generate an + + event whenever the state changes + + so you can write state entry code + near the rest of the state transition rules. + It typically looks like this: +

+
+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. +

+
+
Example

This example starts off as equivalent to the example in section - gen_fsm Behavior. + gen_fsm-Behavior. In later sections, additions and tweaks are made using features in gen_statem that gen_fsm does not have. The end of this chapter provides the example again @@ -722,6 +751,12 @@ stop() -> Generated by any regular process message sent to the gen_statem process. + enter + + Generated by a state transition with + OldState =/= NewState when running with + state entry events. + timeout Generated by state transition action @@ -981,7 +1016,7 @@ init(Code) -> callback_mode() -> state_functions. -locked(internal, enter, _Data) -> +locked(internal, enter, Data) -> do_lock(), {keep_state,Data#{remaining => Code}}; locked( @@ -992,7 +1027,7 @@ locked( enter(next_state, open, Data); ... -open(internal, enter, _Data) -> +open(internal, enter, Data) -> Tref = erlang:start_timer(10000, self(), lock), do_unlock(), {keep_state,Data#{timer => Tref}}; @@ -1005,6 +1040,49 @@ enter(Tag, State, Data) -> ]]>

+ + +
+ Using State Entry Events +

+ 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. + If you want state entry code in just a few states the previous + example may be more suitable. +

+ + 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}; +... + ]]> +
+
@@ -1054,12 +1132,12 @@ code_length() -> init(Code) -> process_flag(trap_exit, true), Data = #{code => Code}, - enter(ok, locked, Data). + {ok, locked, Data}. callback_mode() -> - state_functions. + [state_functions,state_entry_events]. -locked(internal, enter, #{code := Code} = Data) -> +locked(enter, _OldState, #{code := Code} = Data) -> do_lock(), {keep_state,Data#{remaining => Code}}; locked( @@ -1067,7 +1145,7 @@ locked( #{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 @@ -1076,12 +1154,12 @@ locked( locked(EventType, EventContent, Data) -> handle_event(EventType, EventContent, 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}; open(cast, {button,_}, _) -> {keep_state_and_data,[postpone]}; open(EventType, EventContent, Data) -> @@ -1089,8 +1167,6 @@ open(EventType, EventContent, Data) -> handle_event({call,From}, code_length, #{code := Code}) -> {keep_state_and_data,[{reply,From,length(Code)}]}. -enter(Tag, State, Data) -> - {Tag,State,Data,[{next_event,internal,enter}]}. do_lock() -> io:format("Locked~n", []). @@ -1111,8 +1187,9 @@ code_change(_Vsn, State, Data, _Extra) -> This section describes what to change in the example to use one handle_event/4 function. The previously used approach to first branch depending on event - does not work that well here because of the generated - entry actions, so this example first branches depending on state: + does not work that well here because of + the state entry events, + so this example first branches depending on state:

... 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 --- system/doc/design_principles/statem.xml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'system/doc') 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) -> state entry events. 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. + example may be more suitable, especially to only send internal + events when entering just those few states. +

+

+ You can also in the previous example choose to generate + events looking just like the events you get from using + state entry events. + This may be confusing, or practical, + depending on your point of view.

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(-) (limited to 'system/doc') 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 --- system/doc/design_principles/statem.xml | 194 ++++++++++++++------------------ 1 file changed, 86 insertions(+), 108 deletions(-) (limited to 'system/doc') diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 565b0e5274..d2a9b23570 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -195,21 +195,19 @@ handle_event(EventType, EventContent, State, Data) ->

- - State Entry Events + + State Enter Calls

The gen_statem behavior can regardless of callback mode - automatically generate an - - event whenever the state changes - - so you can write state entry code + automatically call the state function + with special arguments whenever the state changes + so you can write state entry actions near the rest of the state transition rules. It typically looks like this:

 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) ->
     

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.

@@ -751,12 +749,6 @@ stop() -> Generated by any regular process message sent to the gen_statem process. - enter - - Generated by a state transition with - OldState =/= NewState when running with - state entry events. - timeout Generated by state transition action @@ -972,63 +964,35 @@ do_unlock() ->
- Self-Generated Events -

- It can sometimes be beneficial to be able to generate events - to your own state machine. - This can be done with the state transition - action - {next_event,EventType,EventContent}. -

+ State Entry Actions

- You can generate events of any existing - type, - but the internal type can only be generated through action - next_event. Hence, it cannot come from an external source, - so you can be certain that an internal event is an event - from your state machine to itself. + Say you have a state machine specification + that uses state entry actions. + Allthough you can code this using self-generated events + (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 + state enter calls.

- 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 internal event that does the entry actions. - This has the same unfortunate consequence as using - state transition functions: everywhere you go to - 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 - using internal events with content enter - using a helper function enter/3 for state entry: + You return a list containing state_enter from your + callback_mode/0 + function and the gen_statem engine will call your + state function once with the arguments + (enter, OldState, ...) whenever the state changes. + Then you just need to handle these event-like calls in all states.

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}]}. ]]>
- Using State Entry Events + Self-Generated Events

- Here is the same example as the previous but instead using - the built in - state entry events. + It can sometimes be beneficial to be able to generate events + to your own state machine. + This can be done with the state transition + action + {next_event,EventType,EventContent}.

- 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. + You can generate events of any existing + type, + but the internal type can only be generated through action + next_event. Hence, it cannot come from an external source, + so you can be certain that an internal event is an event + from your state machine to itself.

- 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 - state entry events. - This may be confusing, or practical, - depending on your point of view. + The following example use an input model where you give the lock + characters with put_chars(Chars) and then call + enter() to finish the input.

- 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; ... ]]> +

+ If you start this program with code_lock:start([17]) + you can unlock with code_lock:put_chars(<<"001">>), + code_lock:put_chars(<<"7">>), code_lock:enter(). +

@@ -1117,7 +1096,7 @@ open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> Example Revisited

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:

@@ -1163,7 +1142,7 @@ init(Code) -> {ok, locked, Data}. callback_mode() -> - [state_functions,state_entry_events]. + [state_functions,state_enter]. locked(enter, _OldState, #{code := Code} = Data) -> do_lock(), @@ -1215,8 +1194,7 @@ code_change(_Vsn, State, Data, _Extra) -> This section describes what to change in the example to use one handle_event/4 function. The previously used approach to first branch depending on event - does not work that well here because of - the state entry events, + does not work that well here because of the state enter calls, so this example first branches depending on state:

... 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 Date: Fri, 30 Sep 2016 11:17:22 +0200 Subject: Improve docs and types --- system/doc/design_principles/statem.xml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'system/doc') diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index d2a9b23570..69d1e8e9fa 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -29,7 +29,7 @@ statem.xml - +

This section is to be read with the gen_statem(3) @@ -199,7 +199,10 @@ handle_event(EventType, EventContent, State, Data) -> State Enter Calls

The gen_statem behavior can regardless of callback mode - automatically call the state function + automatically + + call the state function + with special arguments whenever the state changes so you can write state entry actions near the rest of the state transition rules. @@ -214,8 +217,13 @@ StateName(EventType, EventContent, Data) -> {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 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 + + State Entry Actions + + chapter.

@@ -964,6 +972,7 @@ do_unlock() ->
+ State Entry Actions

Say you have a state machine specification -- cgit v1.2.3 From 77e175589b0ee3c1a4c94aef3cdcdf54cd84c53c Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Fri, 30 Sep 2016 18:00:38 +0200 Subject: Implement state timeouts --- system/doc/design_principles/code_lock.dia | Bin 2932 -> 2945 bytes system/doc/design_principles/code_lock.png | Bin 59160 -> 59827 bytes system/doc/design_principles/code_lock_2.dia | Bin 2621 -> 2956 bytes system/doc/design_principles/code_lock_2.png | Bin 48927 -> 55553 bytes system/doc/design_principles/statem.xml | 581 +++++++++++++++++---------- 5 files changed, 377 insertions(+), 204 deletions(-) (limited to 'system/doc') diff --git a/system/doc/design_principles/code_lock.dia b/system/doc/design_principles/code_lock.dia index 8e6ff8a898..eaa2aca5b0 100644 Binary files a/system/doc/design_principles/code_lock.dia and b/system/doc/design_principles/code_lock.dia differ diff --git a/system/doc/design_principles/code_lock.png b/system/doc/design_principles/code_lock.png index 745fd91920..40bd35fc74 100644 Binary files a/system/doc/design_principles/code_lock.png and b/system/doc/design_principles/code_lock.png differ diff --git a/system/doc/design_principles/code_lock_2.dia b/system/doc/design_principles/code_lock_2.dia index 142909a2f5..3b9ba554d8 100644 Binary files a/system/doc/design_principles/code_lock_2.dia and b/system/doc/design_principles/code_lock_2.dia differ diff --git a/system/doc/design_principles/code_lock_2.png b/system/doc/design_principles/code_lock_2.png index ecf7b0d799..3aca9dd5aa 100644 Binary files a/system/doc/design_principles/code_lock_2.png and b/system/doc/design_principles/code_lock_2.png differ diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 69d1e8e9fa..9a50bef7b1 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -29,7 +29,7 @@ statem.xml - +

This section is to be read with the gen_statem(3) @@ -50,6 +50,7 @@

+ Event-Driven State Machines

Established Automata Theory does not deal much with @@ -94,7 +95,7 @@ State(S) x Event(E) -> Actions(A), State(S')

- + Callback Modes

The gen_statem behavior supports two callback modes: @@ -110,7 +111,12 @@ State(S) x Event(E) -> Actions(A), State(S')

 StateName(EventType, EventContent, Data) ->
     ... code for actions here ...
-    {next_state, NewStateName, NewData}.
+ {next_state, NewStateName, NewData}. + +

+ This form is used in most examples here for example in section + Example. +

@@ -121,7 +127,13 @@ StateName(EventType, EventContent, Data) ->

 handle_event(EventType, EventContent, State, Data) ->
     ... code for actions here ...
-    {next_state, NewState, NewData}
+ {next_state, NewState, NewData} + +

+ Se section + One Event Handler + for an example. +

@@ -134,10 +146,11 @@ handle_event(EventType, EventContent, State, Data) ->

+ Choosing the Callback Mode

The two - callback modes + callback modes give different possibilities and restrictions, but one goal remains: you want to handle all possible combinations of @@ -195,7 +208,7 @@ handle_event(EventType, EventContent, State, Data) ->

- + State Enter Calls

The gen_statem behavior can regardless of callback mode @@ -230,10 +243,160 @@ StateName(EventType, EventContent, Data) ->

+ + Actions +

+ In the first section + + Event-Driven State Machines + + actions were mentioned as a part of + the general state machine model. These general actions + are implemented with the code that callback module + gen_statem executes in an event-handling + callback function before returning + to the gen_statem engine. +

+

+ There are more specific state-transition actions + that a callback function can order the gen_statem + engine to do after the callback function return. + These are ordered by returning a list of + actions + in the + return tuple + from the + callback function. + These state transition actions affect the gen_statem + engine itself and can do the following: +

+ + + + Postpone + + the current event, see section + Postponing Events + + + + Hibernate + + the gen_statem, treated in + Hibernation + + + Start a + + state time-out, + read more in section + State Time-Outs + + + Start an + event time-out, + see more in section + Event Time-Outs + + + + Reply + + to a caller, mentioned at the end of section + All State Events + + + Generate the + + next event + + to handle, see section + Self-Generated Events + + +

+ For details, see the + + gen_statem(3) + + manual page. + You can, for example, reply to many callers + and generate multiple next events to handle. +

+
+ + + +
+ + Event Types +

+ Events are categorized in different + event types. + Events of all types are handled in the same callback function, + for a given state, and the function gets + EventType and EventContent as arguments. +

+

+ The following is a complete list of event types and where + they come from: +

+ + cast + + Generated by + gen_statem:cast. + + {call,From} + + Generated by + gen_statem:call, + where From is the reply address to use + when replying either through the state transition action + {reply,From,Msg} or by calling + gen_statem:reply. + + info + + Generated by any regular process message sent to + the gen_statem process. + + state_timeout + + Generated by state transition action + + {state_timeout,Time,EventContent} + + state timer timing out. + + timeout + + Generated by state transition action + + {timeout,Time,EventContent} + + (or its short form Time) + event timer timing out. + + internal + + Generated by state transition + action + {next_event,internal,EventContent}. + All event types above can also be generated using + {next_event,EventType,EventContent}. + + +
+ + + +
+ Example

This example starts off as equivalent to the example in section - gen_fsm-Behavior. + gen_fsm Behavior. In later sections, additions and tweaks are made using features in gen_statem that gen_fsm does not have. The end of this chapter provides the example again @@ -256,7 +419,6 @@ StateName(EventType, EventContent, Data) -> This code lock state machine can be implemented using gen_statem with the following callback module:

- 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}. ]]>

The code is explained in the next sections.

@@ -318,6 +480,7 @@ code_change(_Vsn, State, Data, _Extra) ->
+ Starting gen_statem

In the example in the previous section, gen_statem is @@ -380,7 +543,7 @@ start_link(Code) ->

If name registration succeeds, the new gen_statem process calls callback function code_lock:init(Code). - This function is expected to return {ok,State,Data}, + This function is expected to return {ok, State, Data}, where State is the initial state of the gen_statem, in this case locked; assuming that the door is locked to begin with. Data is the internal server data of the gen_statem. @@ -421,7 +584,7 @@ callback_mode() -> Function Module:callback_mode/0 selects the - CallbackMode + CallbackMode for the callback module, in this case state_functions. That is, each state has got its own handler function. @@ -432,6 +595,7 @@ callback_mode() ->

+ Handling Events

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 gen_statem. When the event is received, the gen_statem calls StateName(cast, Event, Data), which is expected to - return a tuple {next_state,NewStateName,NewData}. + return a tuple {next_state, NewStateName, NewData}, + or {next_state, NewStateName, NewData, Actions}. StateName is the name of the current state and NewStateName is the name of the next state to go to. NewData is a new value for the server data of - the gen_statem. + the gen_statem, and Actions is a list of + actions on the gen_statem engine.

% 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}. ]]>

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 open, any button locks the door, as - any event cancels the event timer, so no - time-out event occurs after a button event. + If the whole code is correct, the server changes states + to open. +

+

+ In state open, a button event is ignored + by staying in the same state. This can also be done + by returning {keep_state, Data} or in this case + since Data unchanged even by returning + keep_state_and_data.

- Event Time-Outs + + State Time-Outs

When a correct code has been given, the door is unlocked and the following tuple is returned from locked/2:

10,000 is a time-out value in milliseconds. After this time (10 seconds), a time-out occurs. - Then, StateName(timeout, 10000, Data) is called. + Then, StateName(state_timeout, lock, Data) is called. The time-out occurs when the door has been in state open for 10 seconds. After that the door is locked again:

+open(state_timeout, lock, Data) -> do_lock(), - {next_state,locked,Data}; + {next_state, locked, Data}; ]]> +

+ 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 infinity. +

+ All State Events

Sometimes events can arrive in any state of the gen_statem. @@ -554,21 +737,24 @@ open(EventType, EventContent, Data) -> handle_event(EventType, EventContent, Data). handle_event({call,From}, code_length, #{code := Code} = Data) -> - {keep_state,Data,[{reply,From,length(Code)}]}. + {keep_state, Data, [{reply,From,length(Code)}]}. ]]>

This example uses gen_statem:call/2, which waits for a reply from the server. The reply is sent with a {reply,From,Reply} tuple - in an action list in the {keep_state,...} tuple - that retains the current state. + in an action list in the {keep_state, ...} tuple + that retains the current state. This return form is convenient + when you want to stay in the current state but do not know or + care about what it is.

+ One Event Handler

If mode handle_event_function is used, @@ -592,19 +778,19 @@ handle_event(cast, {button,Digit}, State, #{code := Code} = Data) -> case maps:get(remaining, Data) of [Digit] -> % Complete do_unlock(), - {next_state,open,Data#{remaining := Code},10000}; + {next_state, open, Data#{remaining := Code}, + [{state_timeout,10000,lock}}; [Digit|Rest] -> % Incomplete - {keep_state,Data#{remaining := Rest}}; + {keep_state, Data#{remaining := Rest}}; [_|_] -> % Wrong - {keep_state,Data#{remaining := Code}} + {keep_state, Data#{remaining := Code}} end; open -> - do_lock(), - {next_state,locked,Data} + keep_state_and_data end; -handle_event(timeout, _, open, Data) -> +handle_event(state_timeout, lock, open, Data) -> do_lock(), - {next_state,locked,Data}. + {next_state, locked, Data}. ... ]]> @@ -613,9 +799,11 @@ handle_event(timeout, _, open, Data) ->

+ Stopping
+ In a Supervision Tree

If the gen_statem is part of a supervision tree, @@ -655,6 +843,7 @@ terminate(_Reason, State, _Data) ->

+ Standalone gen_statem

If the gen_statem is not part of a supervision tree, @@ -681,127 +870,77 @@ stop() ->

- Actions + + Event Time-Outs

- 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 - gen_statem executes in an event-handling - callback function before returning - to the gen_statem engine. + A timeout feature inherited from gen_statem's predecessor + gen_fsm, + is an event time-out, that is, + if an event arrives the timer is cancelled. + You get either an event or a time-out, but not both.

- There are more specific state-transition actions - that a callback function can order the gen_statem - engine to do after the callback function return. - These are ordered by returning a list of - actions - in the - return tuple - from the - callback function. - These state transition actions affect the gen_statem - engine itself and can do the following: + It is ordered by the state transition action + {timeout,Time,EventContent}, or just Time, + or even just Time instead of an action list + (the latter is a form inherited from gen_fsm.

- - Postpone the current event - Hibernate the gen_statem - Start an event time-out - Reply to a caller - Generate the next event to handle -

- 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 - gen_statem(3) - manual page. - You can, for example, reply to many callers - and generate multiple next events to handle. + This type of time-out is useful to for example act on inactivity. + Let us start restart the code sequence + if no button is pressed for say 30 seconds:

-
- - + - Event Types +locked( + timeout, _, + #{code := Code, remaining := Remaining} = Data) -> + {next_state, locked, Data#{remaining := Code}}; +locked( + cast, {button,Digit}, + #{code := Code, remaining := Remaining} = Data) -> +... + [Digit|Rest] -> % Incomplete + {next_state, locked, Data#{remaining := Rest}, 30000}; +... + ]]>

- The previous sections mentioned a few - event types. - Events of all types are handled in the same callback function, - for a given state, and the function gets - EventType and EventContent as arguments. + Whenever we receive a button event we start an event timeout + of 30 seconds, and if we get an event type timeout + we reset the remaining code sequence.

- 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...

- - cast - - Generated by - gen_statem:cast. - - {call,From} - - Generated by - gen_statem:call, - where From is the reply address to use - when replying either through the state transition action - {reply,From,Msg} or by calling - gen_statem:reply. - - info - - Generated by any regular process message sent to - the gen_statem process. - - timeout - - Generated by state transition action - {timeout,Time,EventContent} (or its short form Time) - timer timing out. - - internal - - Generated by state transition action - {next_event,internal,EventContent}. - All event types above can also be generated using - {next_event,EventType,EventContent}. - -
- State Time-Outs -

- The time-out event generated by state transition action - {timeout,Time,EventContent} is an event time-out, - that is, if an event arrives the timer is cancelled. - You get either an event or a time-out, but not both. -

+ + Erlang Timers

- 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: - erlang:start_timer. + The previous example of state time-outs only work if + the state machine stays in the same state during the + time-out time. And event time-outs only work if no + disturbing unrelated events occur.

- For the example so far in this chapter: using the - gen_statem event timer has the consequence that - if a button event is generated while in the open state, - the time-out is cancelled and the button event is delivered. - So, we choose to lock the door if this occurred. + You may want to start a timer in one state and respond + to the time-out in another, maybe cancel the time-out + without changing states, or perhaps run multiple + time-outs in parallel. All this can be accomplished + with Erlang Timers: + erlang:start_timer3,4.

- Suppose that we do not want a button to lock the door, - instead we want to ignore button events in the open state. - Then we start a timer when entering the open state - and wait for it to expire while ignoring button events: + Here is how to accomplish the state time-out + in the previous example by insted using an Erlang Timer:

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}; ... ]]> +

+ Removing the timer key from the map when we + change to state locked is not strictly + necessary since we can only get into state open + with an updated timer map value. But it can be nice + to not have outdated values in the state Data! +

If you need to cancel a timer because of some other event, you can use erlang:cancel_timer(Tref). - Notice that a time-out message cannot arrive after this, - unless you have postponed it (see the next section) before, + Note that a time-out message cannot arrive after this, + unless you have postponed it before (see the next section), so ensure that you do not accidentally postpone such messages. + Also note that a time-out message may have arrived + just before you cancelling it, so you may have to read out + such a message from the process mailbox depending on + the return value from + erlang:cancel_timer(Tref).

- 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.

@@ -839,6 +990,7 @@ open(cast, {button,_}, Data) ->
+ Postponing Events

If you want to ignore a particular event in the current state @@ -877,6 +1029,7 @@ open(cast, {button,_}, Data) ->

+ Fuzzy State Diagrams

It is not uncommon that a state diagram does not specify @@ -893,6 +1046,7 @@ open(cast, {button,_}, Data) ->

+ Selective Receive

Erlang's selective receive statement is often used to @@ -972,7 +1126,7 @@ do_unlock() ->

- + State Entry Actions

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 - state enter calls. + state enter calls.

You return a list containing state_enter from your @@ -1012,11 +1166,10 @@ locked( {next_state, open, 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}; ... ]]> @@ -1025,6 +1178,7 @@ open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->

+ Self-Generated Events

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 put_chars(Chars) and then call enter() to finish the input.

@@ -1102,10 +1256,11 @@ handle_event({call,From}, enter, #{buf := Buf} = Data) ->
+ Example Revisited

- 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:

@@ -1121,6 +1276,7 @@ handle_event({call,From}, enter, #{buf := Buf} = Data) ->

+ Callback Mode: state_functions

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) ->

+ Callback Mode: handle_event_function

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 locked state - to the open state feels like the wrong thing to do + to the open state feels like a strange thing to do for a code lock, but it at least illustrates event postponing.

@@ -1257,6 +1422,7 @@ handle_event({call,From}, code_length, _State, #{code := Code}) ->
+ Filter the State

The example servers so far in this chapter @@ -1317,12 +1483,13 @@ format_status(Opt, [_PDict,State,Data]) ->

+ Complex State

The callback mode handle_event_function enables using a non-atom state as described in section - Callback Modes, + Callback Modes, for example, a complex state term like a tuple.

@@ -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]) ->

+ Hibernation

If you have many servers in one node @@ -1519,20 +1687,21 @@ format_status(Opt, [_PDict,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]}; ... ]]>

- The - [hibernate] - action list on the last line + The atom + hibernate + in the action list on the last line when entering the {open,_} state is the only change. If any event arrives in the {open,_}, state, we do not bother to rehibernate, so the server stays @@ -1546,6 +1715,10 @@ handle_event( be aware of using hibernate while in the {open,_} state, which would clutter the code.

+

+ 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 Date: Wed, 12 Oct 2016 17:49:26 +0200 Subject: Use parameterized types --- system/doc/design_principles/statem.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'system/doc') diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 9a50bef7b1..bece95e6b8 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -214,7 +214,7 @@ handle_event(EventType, EventContent, State, Data) -> The gen_statem behavior can regardless of callback mode automatically - call the state function + call the state callback with special arguments whenever the state changes so you can write state entry actions @@ -264,7 +264,7 @@ StateName(EventType, EventContent, Data) -> These are ordered by returning a list of actions in the - return tuple + return tuple from the callback function. These state transition actions affect the gen_statem @@ -1141,7 +1141,7 @@ do_unlock() -> You return a list containing state_enter from your callback_mode/0 function and the gen_statem engine will call your - state function once with the arguments + state callback once with the arguments (enter, OldState, ...) whenever the state changes. Then you just need to handle these event-like calls in all states.

-- cgit v1.2.3