summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaimo Niskanen <raimo@erlang.org>2020-03-06 14:15:01 +0100
committerRaimo Niskanen <raimo@erlang.org>2020-03-06 14:15:01 +0100
commit89f85ed97a5aee83ecf79685c26abbd501d44508 (patch)
treeec2e46512d113ed09b0ca9de4f9ed48781e5eba3
parentd030be503fe23bb4d61db07d436146cdc84ccc3b (diff)
parentf0a1c8d9e3ea410caa0ce00f2c2e9e1bebaba914 (diff)
downloaderlang-89f85ed97a5aee83ecf79685c26abbd501d44508.tar.gz
Merge branch 'raimo/stdlib/gen_statem-change_cb' into maint
* raimo/stdlib/gen_statem-change_cb: Improve function name Document change|push|pop _callback_module Implement push and pop of callback module
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml81
-rw-r--r--lib/stdlib/src/gen_statem.erl229
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl33
-rw-r--r--lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl14
-rw-r--r--system/doc/design_principles/statem.xml45
5 files changed, 281 insertions, 121 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 39fc565ff8..c12ad2deef 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -101,10 +101,10 @@
</item>
<item>
In OTP 22.3 the possibility to change the callback module
- with action
- <seealso marker="#type-action">
- <c>change_callback_module</c>
- </seealso>
+ with actions
+ <seealso marker="#type-action"><c>change_callback_module</c></seealso>,
+ <seealso marker="#type-action"><c>push_callback_module</c></seealso> and
+ <seealso marker="#type-action"><c>pop_callback_module</c></seealso>,
was added.
</item>
</list>
@@ -750,8 +750,10 @@ handle_event(_, _, State, Data) ->
<seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
is called when starting the <c>gen_statem</c>,
after code change
- and after changing the callback module with action
- <seealso marker="#type-action"><c>change_callback_module</c></seealso>.
+ and after changing the callback module with any of the actions
+ <seealso marker="#type-action"><c>change_callback_module</c></seealso>,
+ <seealso marker="#type-action"><c>push_callback_module</c></seealso> or
+ <seealso marker="#type-action"><c>pop_callback_module</c></seealso>.
The result is cached for subsequent calls to
<seealso marker="#state callback">state callbacks</seealso>.
</p>
@@ -1191,7 +1193,9 @@ handle_event(_, _, State, Data) ->
an event inserted this way from any external event.
</p>
</item>
- <tag><c>change_callback_module</c></tag>
+ <tag>
+ <c>change_callback_module</c>
+ </tag>
<item>
<p>
Changes the callback module to
@@ -1231,6 +1235,32 @@ handle_event(_, _, State, Data) ->
from the old module.
</p>
</item>
+ <tag>
+ <c>push_callback_module</c><br />
+ </tag>
+ <item>
+ <p>
+ Pushes the current callback module
+ to the top of an internal stack of callback modules
+ and changes the callback module to
+ <c><anno>NewModule</anno></c>.
+ Otherwise like
+ <c>{change_callback_module, NewModule}</c>
+ above.
+ </p>
+ </item>
+ <tag>
+ <c>pop_callback_module</c>
+ </tag>
+ <item>
+ Pops the top module from the internal stack of
+ callback modules and changes the callback module
+ to be the popped module.
+ If the stack is empty the server fails.
+ Otherwise like
+ <c>{change_callback_module, NewModule}</c>
+ above.
+ </item>
</taglist>
</desc>
</datatype>
@@ -2017,8 +2047,10 @@ handle_event(_, _, State, Data) ->
returns.
A change of the callback module happens when a
<seealso marker="#state callback"><em>state callback</em></seealso>
- returns the action
- <seealso marker="#type-action"><c>change_callback_module</c></seealso>.
+ returns any of the actions
+ <seealso marker="#type-action"><c>change_callback_module</c></seealso>,
+ <seealso marker="#type-action"><c>push_callback_module</c></seealso> or
+ <seealso marker="#type-action"><c>pop_callback_module</c></seealso>.
</p>
<p>
The <c>CallbackMode</c> is either just
@@ -2125,6 +2157,37 @@ handle_event(_, _, State, Data) ->
will not be honoured,
most probably causing a server crash.
</p>
+ <p>
+ If the server changes callback module using any of the actions
+ <seealso marker="#type-action"><c>change_callback_module</c></seealso>,
+ <seealso marker="#type-action"><c>push_callback_module</c></seealso> or
+ <seealso marker="#type-action"><c>pop_callback_module</c></seealso>,
+ be aware that it is always the current callback module that
+ will get this callback call. That the current callback module
+ handles the current state and data update should be no surprise,
+ but it must be able to handle even parts of
+ the state and data that it is not familiar with,
+ somehow.
+ </p>
+ <p>
+ In the supervisor
+ <seealso marker="doc/design_principles:sup_princ#child-specification">child specification</seealso>
+ there is a list of modules which is recommended to contain
+ only the callback module.
+ For a <c>gen_statem</c> with multiple callback modules
+ there is no real need to list all of them,
+ it may not even be possible since the list could change
+ after code upgrade.
+ If this list would contain only the start callback module,
+ as recommended, what is important is to upgrade <em>that</em> module
+ whenever a <em>synchronized code replacement</em> is done.
+ Then the release handler concludes that an upgrade
+ that upgrades <em>that</em> module needs to suspend,
+ code change, and resume any server whose child specification
+ declares that it is using <em>that</em> module.
+ And again; the <em>current</em> callback module will get the
+ <c>Module:code_change/4</c> call.
+ </p>
</desc>
</func>
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 885c6ef031..2cf30e10ec 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -150,6 +150,8 @@
EventType :: event_type(),
EventContent :: term()} |
{'change_callback_module', NewModule :: module()} |
+ {'push_callback_module', NewModule :: module()} |
+ 'pop_callback_module' |
enter_action().
-type enter_action() ::
'hibernate' | % Set the hibernate option
@@ -423,7 +425,7 @@ timeout_event_type(Type) ->
{callback_mode = undefined :: callback_mode() | undefined,
state_enter = false :: boolean(),
parent :: pid(),
- module :: atom(),
+ modules :: [module()],
name :: atom() | pid(),
hibernate_after = infinity :: timeout()
}).
@@ -691,7 +693,7 @@ enter(
P =
#params{
parent = Parent,
- module = Module,
+ modules = [Module],
name = Name,
hibernate_after = HibernateAfterTimeout},
S = #state{state_data = {State,Data}},
@@ -728,7 +730,7 @@ init_it(Starter, Parent, ServerRef, Module, Args, Opts) ->
proc_lib:init_ack(Starter, {error,Reason}),
error_info(
Class, Reason, Stacktrace, Debug,
- #params{parent = Parent, name = Name, module = Module},
+ #params{parent = Parent, name = Name, modules = [Module]},
#state{}, []),
erlang:raise(Class, Reason, Stacktrace)
end.
@@ -764,7 +766,7 @@ init_result(
proc_lib:init_ack(Starter, {error,Error}),
error_info(
error, Error, ?STACKTRACE(), Debug,
- #params{parent = Parent, name = Name, module = Module},
+ #params{parent = Parent, name = Name, modules = [Module]},
#state{}, []),
exit(Error)
end.
@@ -785,7 +787,7 @@ system_terminate(Reason, Parent, Debug, {P,S}) ->
update_parent(P, Parent), Debug, S, []).
system_code_change(
- {#params{module = Module} = P,
+ {#params{modules = [Module | _]} = P,
#state{state_data = {State,Data}} = S},
_Mod, OldVsn, Extra) ->
case
@@ -816,7 +818,7 @@ system_replace_state(
format_status(
Opt,
[PDict,SysState,Parent,Debug,
- {#params{name = Name} = P,
+ {#params{name = Name, modules = Modules} = P,
#state{postponed = Postponed, timers = Timers} = S}]) ->
Header = gen:format_status_header("Status for state machine", Name),
Log = sys:get_log(Debug),
@@ -824,6 +826,7 @@ format_status(
{data,
[{"Status",SysState},
{"Parent",Parent},
+ {"Modules",Modules},
{"Time-outs",list_timeouts(Timers)},
{"Logged Events",Log},
{"Postponed",Postponed}]} |
@@ -1079,7 +1082,7 @@ loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent) ->
StateCall, CallbackEvent).
%%
loop_state_callback(
- #params{callback_mode = undefined, module = Module} = P,
+ #params{callback_mode = undefined, modules = [Module | _]} = P,
Debug, S, Q, State_Data,
NextEventsR, Hibernate, TimeoutsR, Postpone,
StateCall, CallbackEvent) ->
@@ -1105,7 +1108,7 @@ loop_state_callback(
Class, Reason, Stacktrace, P, Debug, S, Q)
end;
loop_state_callback(
- #params{callback_mode = CallbackMode, module = Module} = P,
+ #params{callback_mode = CallbackMode, modules = [Module | _]} = P,
Debug, S, Q, {State,Data} = State_Data,
NextEventsR, Hibernate, TimeoutsR, Postpone,
StateCall, {Type,Content}) ->
@@ -1446,13 +1449,21 @@ loop_actions_list(
NextEventsR, Hibernate, TimeoutsR, Postpone,
CallEnter, StateCall, Actions, Type, Content);
%%
- {change_callback_module, NewModule}
- when is_atom(NewModule) ->
+ {Tag, NewModule}
+ when Tag =:= change_callback_module, is_atom(NewModule);
+ Tag =:= push_callback_module, is_atom(NewModule) ->
if
StateCall ->
+ NewModules =
+ case Tag of
+ change_callback_module ->
+ [NewModule | tl(P#params.modules)];
+ push_callback_module ->
+ [NewModule | P#params.modules]
+ end,
P_1 =
P#params{
- callback_mode = undefined, module = NewModule},
+ callback_mode = undefined, modules = NewModules},
loop_actions_list(
P_1, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postpone,
@@ -1467,91 +1478,40 @@ loop_actions_list(
hibernate = Hibernate},
Q)
end;
- %%
- Timeout ->
- loop_actions_timeout(
- P, Debug, S, Q, NextState_NewData,
- NextEventsR, Hibernate, TimeoutsR, Postpone,
- CallEnter, StateCall, Actions, Timeout)
- end.
-
-%% Process a reply action
-%%
-loop_actions_reply(
- P, Debug, S, Q, NextState_NewData,
- NextEventsR, Hibernate, TimeoutsR, Postpone,
- CallEnter, StateCall, Actions,
- From, Reply) ->
- %%
- case from(From) of
- true ->
- %% No need for a separate ?not_sys_debug clause here
- %% since the external call to erlang:'!'/2 in reply/2
- %% will cause swap out of all live registers anyway
- reply(From, Reply),
- Debug_1 = ?sys_debug(Debug, P#params.name, {out,Reply,From}),
- loop_actions_list(
- P, Debug_1, S, Q, NextState_NewData,
- NextEventsR, Hibernate, TimeoutsR, Postpone,
- CallEnter, StateCall, Actions);
- false ->
- terminate(
- error,
- {bad_action_from_state_function,{reply,From,Reply}},
- ?STACKTRACE(), P, Debug,
- S#state{
- state_data = NextState_NewData,
- hibernate = Hibernate},
- Q)
- end.
-
-%% Process a next_event action
-%%
-loop_actions_next_event(
- P, Debug, S, Q, NextState_NewData,
- NextEventsR, Hibernate, TimeoutsR, Postpone,
- CallEnter, StateCall, Actions, Type, Content) ->
- case event_type(Type) of
- true when StateCall ->
- NextEvent = {Type,Content},
- case Debug of
- ?not_sys_debug ->
+ pop_callback_module when tl(P#params.modules) =/= [] ->
+ if
+ StateCall ->
+ NewModules = tl(P#params.modules),
+ P_1 =
+ P#params{
+ callback_mode = undefined,
+ modules = NewModules},
loop_actions_list(
- P, Debug, S, Q, NextState_NewData,
- [NextEvent|NextEventsR],
- Hibernate, TimeoutsR, Postpone,
+ P_1, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
CallEnter, StateCall, Actions);
- _ ->
- Name = P#params.name,
- {State,_Data} = S#state.state_data,
- Debug_1 =
- sys_debug(Debug, Name, {in,{Type,Content},State}),
- loop_actions_list(
- P, Debug_1, S, Q, NextState_NewData,
- [NextEvent|NextEventsR],
- Hibernate, TimeoutsR, Postpone,
- CallEnter, StateCall, Actions)
- end;
+ true ->
+ terminate(
+ error,
+ {bad_state_enter_action_from_state_function,Action},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
+ end;
+ %%
_ ->
- terminate(
- error,
- {if
- StateCall ->
- bad_action_from_state_function;
- true ->
- bad_state_enter_action_from_state_function
- end,
- {next_event,Type,Content}},
- ?STACKTRACE(), P, Debug,
- S#state{
- state_data = NextState_NewData,
- hibernate = Hibernate},
- Q)
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions, Action)
end.
-%% Process a timeout action, or also any unrecognized action
+%% Process all other actions, i.e timeout actions,
+%% all others are unrecognized
%%
-loop_actions_timeout(
+loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postpone,
CallEnter, StateCall, Actions,
@@ -1618,7 +1578,7 @@ loop_actions_timeout(
hibernate = Hibernate},
Q)
end;
-loop_actions_timeout(
+loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postpone,
CallEnter, StateCall, Actions,
@@ -1643,7 +1603,7 @@ loop_actions_timeout(
hibernate = Hibernate},
Q)
end;
-loop_actions_timeout(
+loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postpone,
CallEnter, StateCall, Actions,
@@ -1667,7 +1627,7 @@ loop_actions_timeout(
hibernate = Hibernate},
Q)
end;
-loop_actions_timeout(
+loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postpone,
CallEnter, StateCall, Actions,
@@ -1692,6 +1652,80 @@ loop_actions_timeout(
Q)
end.
+%% Process a reply action
+%%
+loop_actions_reply(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
+ From, Reply) ->
+ %%
+ case from(From) of
+ true ->
+ %% No need for a separate ?not_sys_debug clause here
+ %% since the external call to erlang:'!'/2 in reply/2
+ %% will cause swap out of all live registers anyway
+ reply(From, Reply),
+ Debug_1 = ?sys_debug(Debug, P#params.name, {out,Reply,From}),
+ loop_actions_list(
+ P, Debug_1, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions);
+ false ->
+ terminate(
+ error,
+ {bad_action_from_state_function,{reply,From,Reply}},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
+ end.
+
+%% Process a next_event action
+%%
+loop_actions_next_event(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions, Type, Content) ->
+ case event_type(Type) of
+ true when StateCall ->
+ NextEvent = {Type,Content},
+ case Debug of
+ ?not_sys_debug ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ [NextEvent|NextEventsR],
+ Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions);
+ _ ->
+ Name = P#params.name,
+ {State,_Data} = S#state.state_data,
+ Debug_1 =
+ sys_debug(Debug, Name, {in,{Type,Content},State}),
+ loop_actions_list(
+ P, Debug_1, S, Q, NextState_NewData,
+ [NextEvent|NextEventsR],
+ Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions)
+ end;
+ _ ->
+ terminate(
+ error,
+ {if
+ StateCall ->
+ bad_action_from_state_function;
+ true ->
+ bad_state_enter_action_from_state_function
+ end,
+ {next_event,Type,Content}},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
+ end.
+
%% Do the state transition
%%
loop_state_transition(
@@ -2269,7 +2303,7 @@ do_reply_then_terminate(
terminate(
Class, Reason, Stacktrace,
- #params{module = Module} = P, Debug,
+ #params{modules = [Module | _]} = P, Debug,
#state{state_data = {State,Data}} = S, Q) ->
case erlang:function_exported(Module, terminate, 3) of
true ->
@@ -2310,6 +2344,7 @@ error_info(
Class, Reason, Stacktrace, Debug,
#params{
name = Name,
+ modules = Modules,
callback_mode = CallbackMode,
state_enter = StateEnter} = P,
#state{
@@ -2321,6 +2356,7 @@ error_info(
name=>Name,
queue=>Q,
postponed=>Postponed,
+ modules=>Modules,
callback_mode=>CallbackMode,
state_enter=>StateEnter,
state=>format_status(terminate, get(), P, S),
@@ -2360,6 +2396,7 @@ format_log(#{label:={gen_statem,terminate},
name:=Name,
queue:=Q,
postponed:=Postponed,
+ modules:=Modules,
callback_mode:=CallbackMode,
state_enter:=StateEnter,
state:=FmtData,
@@ -2406,6 +2443,7 @@ format_log(#{label:={gen_statem,terminate},
end ++
"** When server state = ~tp~n" ++
"** Reason for termination = ~w:~tp~n" ++
+ "** Callback modules = ~p~n" ++
"** Callback mode = ~p~n" ++
case Q of
[_,_|_] -> "** Queued = ~tp~n";
@@ -2434,6 +2472,7 @@ format_log(#{label:={gen_statem,terminate},
end] ++
[error_logger:limit_term(FmtData),
Class,error_logger:limit_term(FixedReason),
+ error_logger:limit_term(Modules),
CBMode] ++
case Q of
[_|[_|_] = Events] -> [error_logger:limit_term(Events)];
@@ -2471,7 +2510,7 @@ format_client_log({_Pid,{Name,Stacktrace}}) ->
%% Call Module:format_status/2 or return a default value
format_status(
Opt, PDict,
- #params{module = Module},
+ #params{modules = [Module | _]},
#state{state_data = {State,Data} = State_Data}) ->
case erlang:function_exported(Module, format_status, 2) of
true ->
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 0586575736..296973370c 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -69,7 +69,7 @@ tcs(sys) ->
get_state, replace_state];
tcs(undef_callbacks) ->
[undef_code_change, undef_terminate1, undef_terminate2,
- function_clause_after_change_callback_module].
+ pop_too_many].
init_per_suite(Config) ->
Config.
@@ -1778,7 +1778,7 @@ verify_down(Statem, MRef, Reason) ->
end.
-function_clause_after_change_callback_module(_Config) ->
+pop_too_many(_Config) ->
_ = process_flag(trap_exit, true),
Machine =
@@ -1790,8 +1790,17 @@ function_clause_after_change_callback_module(_Config) ->
fun ({call, From}, {change_callback_module, _Module} = Action,
undefined = _Data) ->
{keep_state_and_data,
- [{reply,From,ok},
- Action]}
+ [Action,
+ {reply,From,ok}]};
+ ({call, From}, {verify, ?MODULE},
+ undefined = _Data) ->
+ {keep_state_and_data,
+ [{reply,From,ok}]};
+ ({call, From}, pop_callback_module = Action,
+ undefined = _Data) ->
+ {keep_state_and_data,
+ [Action,
+ {reply,From,ok}]}
end},
{ok, STM} =
gen_statem:start_link(
@@ -1800,16 +1809,16 @@ function_clause_after_change_callback_module(_Config) ->
[{debug, [trace]}]),
ok = gen_statem:call(STM, {change_callback_module, oc_statem}),
+ ok = gen_statem:call(STM, {push_callback_module, ?MODULE}),
+ ok = gen_statem:call(STM, {verify, ?MODULE}),
+ ok = gen_statem:call(STM, pop_callback_module),
+ BadAction = {bad_action_from_state_function, pop_callback_module},
+ {{BadAction, _},
+ {gen_statem,call,[STM,pop_callback_module,infinity]}} =
+ ?EXPECT_FAILURE(gen_statem:call(STM, pop_callback_module), Reason),
- Call = unhandled_call,
- {{function_clause,
- [{oc_statem, handle_event,
- [{call, _From}, Call, start, _Data], _Line}
- | _RestStacktrace]} = Undef,
- {gen_statem, call, [STM,Call,_Timeout]}} =
- ?EXPECT_FAILURE(gen_statem:call(STM, Call), Reason),
receive
- {'EXIT', STM, Undef} ->
+ {'EXIT', STM, {BadAction, _}} ->
ok;
Other ->
ct:fail({surprise, Other})
diff --git a/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
index 1493cfb192..1bcd08867f 100644
--- a/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
+++ b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
@@ -37,4 +37,16 @@ callback_mode() ->
[handle_event_function, state_enter].
handle_event(enter, start, start, _Data) ->
- keep_state_and_data.
+ keep_state_and_data;
+handle_event(
+ {call,From}, {push_callback_module,NewModule} = Action,
+ start, _Data) ->
+ {keep_state_and_data,
+ [Action,
+ {reply,From,ok}]};
+handle_event(
+ {call,From}, pop_callback_module = Action,
+ start, _Data) ->
+ {keep_state_and_data,
+ [Action,
+ {reply,From,ok}]}.
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index f657e8cb00..d6d907de45 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -185,15 +185,17 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</p>
<p>
The <em>callback module</em> can be changed for a running server
- using the
- <seealso marker="#Transition Actions">transition action</seealso>
- <seealso marker="stdlib:gen_statem#type-action"><c>{change_callback_module, NewModule}</c></seealso>.
+ using any of the
+ <seealso marker="#Transition Actions">transition actions</seealso>
+ <seealso marker="stdlib:gen_statem#type-action"><c>{change_callback_module, NewModule}</c></seealso>,
+ <seealso marker="stdlib:gen_statem#type-action"><c>{push_callback_module, NewModule}</c></seealso> or
+ <seealso marker="stdlib:gen_statem#type-action"><c>pop_callback_module</c></seealso>.
Note that this is a pretty esotheric thing to do...
The origin for this feature is a protocol that after
version negotiation branches off into quite different
state machines depending on the protocol version.
There <i>might</i> be other use cases.
- <i>Beware</i> that the <c>NewModule</c>
+ <i>Beware</i> that the new callback module
completely replaces the previous behaviour module,
so all relevant callback functions has to handle
the state and data from the previous callback module.
@@ -664,6 +666,41 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
but it can <i>not</i> be done from a
<seealso marker="#State Enter Calls"><em>state enter call</em></seealso>.
</item>
+ <tag>
+ <seealso marker="stdlib:gen_statem#type-action">
+ <c>{push_callback_module, NewModule}</c>
+ </seealso>
+ </tag>
+ <item>
+ Push the current <em>callback module</em>
+ to the top of an internal stack of callback modules
+ and set the new
+ <seealso marker="#Callback Module">
+ <em>callback module</em>
+ </seealso>
+ for the running server.
+ Otherwise like
+ <c>{change_callback_module, NewModule}</c>
+ above.
+ </item>
+ <tag>
+ <seealso marker="stdlib:gen_statem#type-action">
+ <c>pop_callback_module</c>
+ </seealso>
+ </tag>
+ <item>
+ Pop the top module from
+ the internal stack of callback modules
+ and set it to be the new
+ <seealso marker="#Callback Module">
+ <em>callback module</em>
+ </seealso>
+ for the running server.
+ If the stack is empty the server fails.
+ Otherwise like
+ <c>{change_callback_module, NewModule}</c>
+ above.
+ </item>
</taglist>
<p>
For details, see the <c>gen_statem(3)</c>