From d86fd35ca0c65069955a34d6ae9fbc33b9663eb0 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 20 Mar 2018 03:34:23 -0700 Subject: Update User's Guide and pointers to it --- system/doc/design_principles/statem.xml | 543 ++++++++++++++++++++++++-------- 1 file changed, 412 insertions(+), 131 deletions(-) (limited to 'system/doc/design_principles') diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 5be2981f62..16f6ce8348 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -36,16 +36,6 @@ manual page in STDLIB, where all interface functions and callback functions are described in detail.

- -

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

-
@@ -92,6 +82,31 @@ State(S) x Event(E) -> Actions(A), State(S')

+ + +
+ + Callback Module +

+ The callback module contains functions that implement + the state machine. + When an event occurs, + the gen_statem behaviour engine + calls a function in the callback module with the event, + current state and server data. + This function performs the actions for this event, + and returns the new state and server data + and also actions to be performed by the behaviour engine. +

+

+ The behaviour engine holds the state machine state, + server data, timer references, a queue of posponed messages + and other metadata. It receives all process messages, + handles the system messages, and calls the callback module + with machine specific events. +

+
+
@@ -100,54 +115,65 @@ State(S) x Event(E) -> Actions(A), State(S')

The gen_statem behavior supports two callback modes:

- + + + + state_functions + +

- In mode - state_functions, - the state transition rules are written as some Erlang - functions, which conform to the following convention: -

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

- This form is used in most examples here for example in section - Example. + Events are handled by one callback functions per state.

+ + + handle_event_function + +

- In mode - handle_event_function, - only one Erlang function provides all state transition rules: -

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

- See section - One Event Handler - for an example. + Events are handled by one single callback function.

-
+

- Both these modes allow other return tuples; see - Module:StateName/3 - in the gen_statem manual page. - These other return tuples can, for example, stop the machine, - execute state transition actions on the machine engine itself, - and send replies. + The callback mode is selected at server start + and may be changed with a code upgrade/downgrade. +

+

+ See the section + Event Handler + that describes the event handling callback function(s). +

+

+ The callback mode is selected by implementing a callback function + + Module:callback_mode() + + that returns one of the callback modes. +

+

+ The + + Module:callback_mode() + + function may also return a list containing the callback mode + and the atom state_enter in which case + state enter calls + are activated for the callback mode.

Choosing the Callback Mode +

+ The short version: choose state_functions - + it is the one most like gen_fsm. + But if you do not want the restriction that the state + must be an atom, or if having to write an event handler function + per state is not as you like it; please read on... +

The two callback modes @@ -186,7 +212,9 @@ handle_event(EventType, EventContent, State, Data) -> This mode works equally well when you want to focus on one event at the time or on one state at the time, but function - Module:handle_event/4 + + Module:handle_event/4 + quickly grows too large to handle without branching to helper functions.

@@ -208,36 +236,166 @@ handle_event(EventType, EventContent, State, Data) ->
- - State Enter Calls + + Event Handler

- The gen_statem behavior can regardless of callback mode - automatically - - call the state callback - - 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: + Which callback function that handles an event + depends on the callback mode:

-
-StateName(enter, _OldState, Data) ->
-    ... code for state entry actions here ...
-    {keep_state, NewData};
-StateName(EventType, EventContent, Data) ->
-    ... code for actions here ...
-    {next_state, NewStateName, NewData}.
+ + state_functions + + The event is handled by:
+ + Module:StateName(EventType, EventContent, Data) + +

+ This form is the one mostly used in the + Example + section. +

+
+ handle_event_function + + The event is handled by:
+ + Module:handle_event(EventType, EventContent, State, Data) + +

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

+
+

- Depending on how your state machine is specified, - 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 + The state is either the name of the function itself or an argument to it. + The other arguments are the EventType described in section + Event Types, + the event dependent EventContent, and the current server Data. +

+

+ State enter calls are also handled by the event handler and have + slightly different arguments. See the section + State Enter Calls. +

+

+ The event handler return values are defined in the description of + + Module:StateName/3 - chapter. + in the gen_statem manual page, but here is + a more readable list:

+ + + {next_state, NextState, NewData, Actions}
+ {next_state, NextState, NewData} +
+ +

+ Set next state and update the server data. + If the Actions field is used, execute state transition actions. + An empty Actions list is equivalent to not returning the field. +

+

+ See section + Actions for a list of possible + state transition actions. +

+

+ If NextState =/= State the state machine changes + to a new state. A + state enter call + is performed if enabled and all + postponed events + are retried. +

+
+ + {keep_state, NewData, Actions}
+ {keep_state, NewData} +
+ +

+ Same as the next_state values with + NextState =:= State, that is no state change. +

+
+ + {keep_state_and_data, Actions}
+ keep_state_and_data +
+ +

+ Same as the keep_state values with + NextData =:= Data, that is no change in server data. +

+
+ + {repeat_state, NewData, Actions}
+ {repeat_state, NewData}
+ {repeat_state_and_data, Actions}
+ repeat_state_and_data +
+ +

+ Same as the keep_state or keep_state_and_data values, + and if state enter calls + are enabled, repeat that call. +

+
+ + {stop, Reason, NewData}
+ {stop, Reason} +
+ +

+ Stop the server with reason Reason. + If the NewData field is used, first update the server data. +

+
+ + {stop_and_reply, Reason, NewData, Actions}
+ {stop_and_reply, Reason, Actions} +
+ +

+ Same as the stop values, but first execute the given + state transition actions that may only be reply actions. +

+
+
+ +
+ + The First State +

+ To decide the first state the + + Module:init(Args) + + callback function is called before any + event handler + is called. This function behaves exactly as an event handler + function, but gets its only argument Args from + the gen_statem + + start/3,4 + + or + + start_link/3,4 + + function, and returns {ok, State, Data} + or {ok, State, Data, Actions}. + If you use the + postpone + action from this function, that action is ignored, + since there is no event to postpone. +

+
+
@@ -246,10 +404,8 @@ StateName(EventType, EventContent, Data) -> Actions

- In the first section - - Event-Driven State Machines - + In the first + section actions were mentioned as a part of the general state machine model. These general actions are implemented with the code that callback module @@ -264,72 +420,97 @@ StateName(EventType, EventContent, Data) -> These are ordered by returning a list of actions in the - return tuple + + return value + from the callback function. - These state transition actions affect the gen_statem - engine itself and can do the following: + These are the possible state transition actions:

- - + + - Postpone + postpone - the current event, see section +
+ {postpone, Boolean} +
+ + If set postpone the current event, see section Postponing Events - + - Hibernate + hibernate - the gen_statem, treated in +
+ {hibernate, Boolean} +
+ + If set hibernate the gen_statem, treated in section Hibernation - - Start a + - state time-out, - read more in section + {state_timeout, Time} + +
+ {state_timeout, Time, Opts} +
+ + Start a state time-out, read more in section State Time-Outs - - Start a + - generic time-out, - read more in section + {{timeout, Name}, Time} + +
+ {{timeout, Name}, Time, Opts} +
+ + Start a generic time-out, read more in section Generic Time-Outs + + + {timeout, Time} + +
+ {timeout, Time, Opts}
+ Time +
- Start an - event time-out, - see more in section + Start an event time-out, see more in section Event Time-Outs - + - Reply + {reply, From, Reply} - to a caller, mentioned at the end of section + + + Reply to a caller, mentioned at the end of section All State Events - - Generate the + - next event + {next_event, EventType, EventContent} - to handle, see section - Self-Generated Events + + + Generate the next event to handle, see section + Inserted Events -
+

- For details, see the - - gen_statem(3) - - manual page. + For details, see the gen_statem(3) + manual page for type + action(). You can, for example, reply to many callers, generate multiple next events, - and set time-outs to relative or absolute times. + and set a time-out to use absolute instead of relative time + (using the Opts field).

@@ -341,8 +522,8 @@ StateName(EventType, EventContent, Data) ->

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 + Events of all types are for a given state + handled in the same callback function, and that function gets EventType and EventContent as arguments.

@@ -350,12 +531,20 @@ StateName(EventType, EventContent, Data) -> they come from:

- cast + + + cast + + Generated by gen_statem:cast. - {call,From} + + + {call,From} + + Generated by gen_statem:call, @@ -364,12 +553,20 @@ StateName(EventType, EventContent, Data) -> {reply,From,Msg} or by calling gen_statem:reply. - info + + + info + + Generated by any regular process message sent to the gen_statem process. - state_timeout + + + state_timeout + + Generated by state transition action @@ -377,7 +574,11 @@ StateName(EventType, EventContent, Data) -> state timer timing out. - {timeout,Name} + + + {timeout,Name} + + Generated by state transition action @@ -385,7 +586,11 @@ StateName(EventType, EventContent, Data) -> generic timer timing out. - timeout + + + timeout + + Generated by state transition action @@ -394,7 +599,11 @@ StateName(EventType, EventContent, Data) -> (or its short form Time) event timer timing out. - internal + + + internal + + Generated by state transition action @@ -405,6 +614,61 @@ StateName(EventType, EventContent, Data) ->
+ + +
+ + State Enter Calls +

+ The gen_statem behavior can if this is enabled, + regardless of callback mode, + automatically + + call the state callback + + with special arguments whenever the state changes + so you can write state enter actions + near the rest of the state transition rules. + It typically looks like this: +

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

+ Since the state enter call is not an event there are restrictions + on the allowed return value and + state transition actions. + You may not change the state, + postpone + this non-event, or + insert events. +

+

+ The first state that is entered will get a state enter call + with OldState equal to the current state. +

+

+ You may repeat the state enter call using the {repeat_state,...} + return value from the + event handler. + In this case OldState will also be equal to the current state. +

+

+ Depending on how your state machine is specified, + this can be a very useful feature, + but it forces you to handle the state enter calls in all states. + See also the + + State Enter Actions + + chapter. +

+
+
@@ -1196,14 +1460,14 @@ do_unlock() ->
- - State Entry Actions + + State Enter Actions

Say you have a state machine specification - that uses state entry actions. - Allthough you can code this using self-generated events + that uses state enter actions. + Allthough you can code this using inserted events (described in the next section), especially if just - one or a few states has got state entry actions, + one or a few states has got state enter actions, this is a perfect use case for the built in state enter calls.

@@ -1244,7 +1508,7 @@ open(state_timeout, lock, Data) -> ... ]]>

- You can repeat the state entry code by returning one of + You can repeat the state enter code by returning one of {repeat_state, ...}, {repeat_state_and_data,_} or repeat_state_and_data that otherwise behaves exactly like their keep_state siblings. @@ -1259,8 +1523,8 @@ open(state_timeout, lock, Data) ->

- - Self-Generated Events + + Inserted Events

It can sometimes be beneficial to be able to generate events to your own state machine. @@ -1279,14 +1543,18 @@ open(state_timeout, lock, Data) ->

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 + 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. + Using internal events also can make it easier + to synchronize the state machines.

The following example uses an input model where you give the lock @@ -1800,10 +2068,23 @@ handle_event(

Another not uncommon scenario is to use the event time-out - to triger hibernation after a certain time of inactivity. + to trigger hibernation after a certain time of inactivity. + There is also a server start option + + {hibernate_after, Timeout} + + for + + start/3,4 + + or + + start_link/3,4 + + that may be used to automatically hibernate the server.

- This server probably does not use + This particular server probably does not use heap memory worth hibernating for. To gain anything from hibernation, your server would have to produce some garbage during callback execution, -- cgit v1.2.3