From f06f69068807168cf4cc731711ed82489cc5b99c Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Mon, 25 Apr 2016 12:07:32 +0200 Subject: Add section on state filtering Misc documentation fixes. --- system/doc/design_principles/code_lock.dia | Bin 2968 -> 2955 bytes system/doc/design_principles/code_lock.png | Bin 58906 -> 58823 bytes system/doc/design_principles/statem.xml | 209 +++++++++++++++++++++-------- 3 files changed, 152 insertions(+), 57 deletions(-) (limited to 'system') diff --git a/system/doc/design_principles/code_lock.dia b/system/doc/design_principles/code_lock.dia index d8262138a3..bed6d8ee86 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 5e5744ecc2..e40f0320aa 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/statem.xml b/system/doc/design_principles/statem.xml index a921a40243..a4b8fb06a0 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -135,7 +135,7 @@ handle_event(EventType, EventContent, State, Data) ->

- Choosing Callback Mode + Choosing the Callback Mode

The two callback modes @@ -157,7 +157,7 @@ handle_event(EventType, EventContent, State, Data) -> on state name for you. This encourages the callback module to gather the implementation of all event actions particular to one state in the same place in the code - hence focus on one state at the time. + hence to focus on one state at the time.

This mode fits well when you have a regular state diagram @@ -216,7 +216,7 @@ handle_event(EventType, EventContent, State, Data) -> it is is wrong, we start all over, waiting for a new button sequence.

- + Code lock state diagram

@@ -842,9 +842,9 @@ open(cast, {button,_}, Data) -> changed i.e OldState =/= NewState.

- Postponing is ordered by the + Postponing is ordered by the state transition - state transition action + action postpone.

@@ -951,9 +951,9 @@ do_unlock() -> passing non-system messages to the callback module.

- The + The state transition - state transition action + action postpone is designed to be able to model selective receives. A selective receive implicitly postpones @@ -974,9 +974,10 @@ do_unlock() -> Self Generated Events

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

@@ -1054,7 +1055,7 @@ enter(Tag, State, Data) -> and some more utilizing the entry actions, which deserves a new state diagram:

- + Code lock state diagram revisited

@@ -1200,6 +1201,71 @@ handle_event({call,From}, code_length, _State, #{code := Code}) ->

+ + +
+ Filter the State +

+ The example servers so far in this chapter will for example + when killed by an exit signal or due to an internal error + print out the full internal state in the error log. + This state contains both the code lock code + and which digits that remains to unlock. +

+

+ This state data can be regarded as sensitive, + and maybe not what you want in the error log + because of something unpredictable happening. +

+

+ Another reason to filter the state can be + that the state is too big to print out since it fills + the error log with uninteresting details. +

+

+ To avoid this you can format the internal state + that gets in the error log and gets returned from + + sys:get_status/1,2 + + by implementing the + + Module:format_status/2 + + function, for example like this: +

+ + StateData = + {State, + maps:filter( + fun (code, _) -> false; + (remaining, _) -> false; + (_, _) -> true + end, + Data)}, + case Opt of + terminate -> + StateData; + normal -> + [{data,[{"State",StateData}]}] + end. + ]]> +

+ It is not mandatory to implement a + + Module:format_status/2 + + function. If you do not a default implementation is used that + does the same as this example function without filtering + the Data term that is: StateData = {State,Data}. +

+
+
@@ -1260,7 +1326,7 @@ handle_event({call,From}, code_length, _State, #{code := Code}) -> where StateName is as before and LockButton is the current lock button:

- -export([start_link/2,stop/0]). -export([button/1,code_length/0,set_lock_button/1]). --export([init/1,terminate/3,code_change/4]). +-export([init/1,terminate/3,code_change/4,format_status/2]). -export([handle_event/4]). start_link(Code, LockButton) -> @@ -1286,51 +1352,65 @@ set_lock_button(LockButton) -> init({Code,LockButton}) -> process_flag(trap_exit, true), - Data = #{code => Code}, + Data = #{code => Code, remaining => undefined, timer => undefined}, enter(?CALLBACK_MODE, {locked,LockButton}, Data, []). -%% State: locked -handle_event(internal, enter, {locked,_}, #{code := Code} = Data) -> - do_lock(), - {keep_state,Data#{remaining => Code}}; handle_event( - {call,From}, {button,Digit}, {locked,LockButton}, - #{code := Code, remaining := Remaining} = Data) -> - case Remaining of - [Digit] -> % Complete - enter(next_state, {open,LockButton}, Data, [{reply,From,ok}]); - [Digit|Rest] -> % Incomplete - {keep_state,Data#{remaining := Rest},[{reply,From,ok}]}; - [_|_] -> % Wrong - {keep_state,Data#{remaining := Code},[{reply,From,ok}]} - end; -%% -%% State: open -handle_event(internal, enter, {open,_}, Data) -> - Tref = erlang:start_timer(10000, self(), lock), - do_unlock(), - {keep_state,Data#{timer => Tref}}; + {call,From}, {set_lock_button,NewLockButton}, + {StateName,OldLockButton}, Data) -> + {next_state,{StateName,NewLockButton},Data, + [{reply,From,OldLockButton}]}; handle_event( - info, {timeout,Tref,lock}, {open,LockButton}, - #{timer := Tref} = Data) -> - enter(next_state, {locked,LockButton}, Data, []); + {call,From}, code_length, + {_StateName,_LockButton}, #{code := Code}) -> + {keep_state_and_data, + [{reply,From,length(Code)}]}; handle_event( - {call,From}, {button,LockButton}, {open,LockButton}, - #{timer := Tref} = Data) -> - erlang:cancel_timer(Tref), - enter(next_state, {locked,LockButton}, Data, [{reply,From,locked}]); -handle_event({call,_}, {button,_}, {open,_}, _) -> - {keep_state_and_data,[postpone]}; -%% -%% Any state + EventType, EventContent, + {locked,LockButton}, #{code := Code, remaining := Remaining} = Data) -> + case {EventType,EventContent} of + {internal,enter} -> + do_lock(), + {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}, + [{reply,From,ok}]}; + [_|_] -> % Wrong + {keep_state,Data#{remaining := Code}, + [{reply,From,ok}]} + end + end; handle_event( - {call,From}, {set_lock_button,NewLockButton}, {StateName,OldLockButton}, - Data) -> - {next_state,{StateName,NewLockButton},Data, - [{reply,From,OldLockButton}]}; -handle_event({call,From}, code_length, _State, #{code := Code}) -> - {keep_state_and_data,[{reply,From,length(Code)}]}. + EventType, EventContent, + {open,LockButton}, #{timer := Timer} = Data) -> + case {EventType,EventContent} of + {internal,enter} -> + Tref = erlang:start_timer(10000, self(), lock), + do_unlock(), + {keep_state,Data#{timer := Tref}}; + {info,{timeout,Timer,lock}} -> + next_state({locked,LockButton}, Data, []); + {{call,From},{button,Digit}} -> + if + Digit =:= LockButton -> + erlang:cancel_timer(Timer), + 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]}. @@ -1344,13 +1424,28 @@ terminate(_Reason, State, _Data) -> ok. code_change(_Vsn, State, Data, _Extra) -> {?CALLBACK_MODE,State,Data}. - ]]> -

- It may be an ill-fitting model for a physical code lock - that the button/1 call might hang until the lock - is locked. But for an API in general it is really not - that strange. -

+format_status(Opt, [_PDict,State,Data]) -> + StateData = + {State, + maps:filter( + fun (code, _) -> false; + (remaining, _) -> false; + (_, _) -> true + end, + Data)}, + case Opt of + terminate -> + StateData; + normal -> + [{data,[{"State",StateData}]}] + end. + ]]> +

+ It may be an ill-fitting model for a physical code lock + that the button/1 call might hang until the lock + is locked. But for an API in general it is really not + that strange. +

-- cgit v1.2.3