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/statem.xml | 581 +++++++++++++++++++++----------- 1 file changed, 377 insertions(+), 204 deletions(-) (limited to 'system/doc/design_principles/statem.xml') 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