aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/src/gen_statem.erl
diff options
context:
space:
mode:
authorRaimo Niskanen <[email protected]>2018-11-28 15:59:00 +0100
committerRaimo Niskanen <[email protected]>2019-01-14 17:18:18 +0100
commit63a7892d0fbcd6463a21ba54622e61cf4a935b64 (patch)
tree1b409874e3a34128fef38957693996074bd2be87 /lib/stdlib/src/gen_statem.erl
parenta5c1cd352976d58577516c6d7b1db45c30f908ab (diff)
downloadotp-63a7892d0fbcd6463a21ba54622e61cf4a935b64.tar.gz
otp-63a7892d0fbcd6463a21ba54622e61cf4a935b64.tar.bz2
otp-63a7892d0fbcd6463a21ba54622e61cf4a935b64.zip
Optimize gen_statem engine loop
* Use synchronolus cancel_timer since that is more efficient than asynchronous when a process cancels its own timer * Restructure the state to have fewer fields by breaking out practically constant fields into a separate loop argument record #params{}, group state and data into one field and dito for timer fields * Optimize engine loop arguments for staying in the loop so the detour to sys:handle_system_message/6 will require packing/unpacking of the sys: state, and of the argument Parent * Avoid double reverse of event queue in many cases * Restructure the state transition to not have duplicate tracks for sys_debug and non-sys_debug cases * Shortcut the empty list case where possible * Loop unroll reversal of up to 2-element lists to avoid calling lists:reverse/2 since an external call spills all live registers to the stack * Bring out the callback mode check to the engine loop
Diffstat (limited to 'lib/stdlib/src/gen_statem.erl')
-rw-r--r--lib/stdlib/src/gen_statem.erl1726
1 files changed, 896 insertions, 830 deletions
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 6f1f6a0228..7645f62856 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -374,50 +374,62 @@ timeout_event_type(Type) ->
-define(
+ relative_timeout(T),
+ ((is_integer(T) andalso 0 =< (T)) orelse (T) =:= infinity)).
+
+-define(
+ absolute_timeout(T),
+ (is_integer(T) orelse (T) =:= infinity)).
+
+-define(
STACKTRACE(),
element(2, erlang:process_info(self(), current_stacktrace))).
-define(not_sys_debug, []).
%%
%% This is a macro to only evaluate arguments if Debug =/= [].
-%% Debug is evaluated multiple times.
+%% Debug is evaluated 2 times.
-define(
- sys_debug(Debug, NameState, Entry),
+ sys_debug(Debug, Extra, SystemEvent),
case begin Debug end of
?not_sys_debug ->
begin Debug end;
_ ->
- sys_debug(begin Debug end, begin NameState end, begin Entry end)
+ sys_debug(
+ begin Debug end, begin Extra end, begin SystemEvent end)
end).
--record(state,
+-record(params,
{callback_mode = undefined :: callback_mode() | undefined,
state_enter = false :: boolean(),
+ parent :: pid(),
module :: atom(),
- name :: atom(),
- state :: term(),
- data :: term(),
+ name :: atom() | pid(),
+ hibernate_after = infinity :: timeout()
+ }).
+
+-record(state,
+ {state_data = {undefined,undefined} ::
+ {State :: term(),Data :: term()},
postponed = [] :: [{event_type(),term()}],
- %%
- timer_refs = #{} :: % timer ref => the timer's event type
- #{reference() => timeout_event_type()},
- timer_types = #{} :: % timer's event type => timer ref
- #{timeout_event_type() => reference()},
- cancel_timers = 0 :: non_neg_integer(),
- %% We add a timer to both timer_refs and timer_types
- %% when we start it. When we request an asynchronous
- %% timer cancel we remove it from timer_types. When
- %% the timer cancel message arrives we remove it from
- %% timer_refs.
- %%
- hibernate = false :: boolean(),
- hibernate_after = infinity :: timeout()}).
+ timers = {#{},#{}} ::
+ {%% TimerRef => TimeoutType
+ TimerRefs :: #{reference() => timeout_event_type()},
+ %% TimeoutType => TimerRef
+ TimeoutTypes :: #{timeout_event_type() => reference()}},
+ hibernate = false :: boolean()
+ }).
-record(trans_opts,
- {hibernate = false,
- postpone = false,
- timeouts_r = [],
- next_events_r = []}).
+ {hibernate = false :: boolean(),
+ postpone = false :: boolean(),
+ timeouts_r = [] ::
+ [{timeout_event_type(),timeout(),TimeoutMsg::term()} |
+ {timeout_event_type(),timeout()|integer(),TimeoutMsg::term(),
+ [timeout_option()]}],
+ next_events_r = [] ::
+ [{'next_event',event_type(),EventContent::term()}]
+ }).
%%%==========================================================================
%%% API
@@ -667,31 +679,22 @@ enter(
Parent, Debug, Module, Name, HibernateAfterTimeout,
State, Data, Actions) ->
%% The values should already have been type checked
- Events = [],
- Event = {internal,init_state},
+ Q = [{internal,init_state}],
%% We enforce {postpone,false} to ensure that
%% our fake Event gets discarded, thought it might get logged
- NewActions = listify(Actions) ++ [{postpone,false}],
- S =
- #state{
+ Actions_1 = listify(Actions) ++ [{postpone,false}],
+ P =
+ #params{
+ parent = Parent,
module = Module,
name = Name,
- state = State,
- data = Data,
hibernate_after = HibernateAfterTimeout},
- CallEnter = true,
- NewDebug = ?sys_debug(Debug, Name, {enter,State}),
- case call_callback_mode(S) of
- #state{} = NewS ->
- loop_event_actions_list(
- Parent, NewDebug, NewS,
- Events, Event, State, Data, false,
- NewActions, CallEnter);
- [Class,Reason,Stacktrace] ->
- terminate(
- Class, Reason, Stacktrace, NewDebug,
- S, [Event|Events])
- end.
+ S = #state{state_data = {State,Data}},
+ TransOpts = trans_opts, % Marker for state_call
+ Debug_1 = ?sys_debug(Debug, Name, {enter,State}),
+ loop_callback_mode(
+ P, Debug_1, S, Q, {State,Data}, TransOpts,
+ Actions_1). % Tunneling Actions through CallbackEvent here...
%%%==========================================================================
%%% gen callbacks
@@ -717,8 +720,8 @@ init_it(Starter, Parent, ServerRef, Module, Args, Opts) ->
proc_lib:init_ack(Starter, {error,Reason}),
error_info(
Class, Reason, Stacktrace, Debug,
- #state{name = Name},
- []),
+ #params{parent = Parent, name = Name, module = Module},
+ #state{}, []),
erlang:raise(Class, Reason, Stacktrace)
end.
@@ -753,25 +756,29 @@ init_result(
proc_lib:init_ack(Starter, {error,Error}),
error_info(
error, Error, ?STACKTRACE(), Debug,
- #state{name = Name},
- []),
+ #params{parent = Parent, name = Name, module = Module},
+ #state{}, []),
exit(Error)
end.
%%%==========================================================================
%%% sys callbacks
+%%%
+%%% We use {P,S} as state (Misc) for the sys module,
+%%% wrap/unwrap it for the server loop* and update
+%%% P#params{parent = Parent}.
-system_continue(Parent, Debug, S) ->
- loop(Parent, Debug, S).
+system_continue(Parent, Debug, {P,S}) ->
+ loop(update_parent(P, Parent), Debug, S).
-system_terminate(Reason, _Parent, Debug, S) ->
- terminate(exit, Reason, ?STACKTRACE(), Debug, S, []).
+system_terminate(Reason, Parent, Debug, {P,S}) ->
+ terminate(
+ exit, Reason, ?STACKTRACE(),
+ update_parent(P, Parent), Debug, S, []).
system_code_change(
- #state{
- module = Module,
- state = State,
- data = Data} = S,
+ {#params{module = Module} = P,
+ #state{state_data = {State,Data}} = S},
_Mod, OldVsn, Extra) ->
case
try Module:code_change(OldVsn, State, Data, Extra)
@@ -781,31 +788,28 @@ system_code_change(
of
{ok,NewState,NewData} ->
{ok,
- S#state{
- callback_mode = undefined,
- state = NewState,
- data = NewData}};
+ {P#params{callback_mode = undefined},
+ S#state{state_data = {NewState,NewData}}}};
{ok,_} = Error ->
error({case_clause,Error});
Error ->
Error
end.
-system_get_state(#state{state = State, data = Data}) ->
- {ok,{State,Data}}.
+system_get_state({_P,#state{state_data = State_Data}}) ->
+ {ok,State_Data}.
system_replace_state(
- StateFun,
- #state{
- state = State,
- data = Data} = S) ->
- {NewState,NewData} = Result = StateFun({State,Data}),
- {ok,Result,S#state{state = NewState, data = NewData}}.
+ StateFun, {P,#state{state_data = State_Data} = S}) ->
+ %%
+ NewState_NewData = StateFun(State_Data),
+ {ok,NewState_NewData,{P,S#state{state_data = NewState_NewData}}}.
format_status(
Opt,
[PDict,SysState,Parent,Debug,
- #state{name = Name, postponed = P} = S]) ->
+ {#params{name = Name} = P,
+ #state{postponed = Postponed} = S}]) ->
Header = gen:format_status_header("Status for state machine", Name),
Log = sys:get_log(Debug),
[{header,Header},
@@ -813,12 +817,22 @@ format_status(
[{"Status",SysState},
{"Parent",Parent},
{"Logged Events",Log},
- {"Postponed",P}]} |
- case format_status(Opt, PDict, S) of
+ {"Postponed",Postponed}]} |
+ case format_status(Opt, PDict, update_parent(P, Parent), S) of
L when is_list(L) -> L;
T -> [T]
end].
+
+-compile({inline, update_parent/2}).
+update_parent(P, Parent) ->
+ case P of
+ #params{parent = Parent} ->
+ P;
+ #params{} ->
+ P#params{parent = Parent}
+ end.
+
%%---------------------------------------------------------------------------
%% Format debug messages. Print them as the call-back module sees
%% them, not as the real erlang messages. Use trace for that.
@@ -874,9 +888,9 @@ event_string(Event) ->
%%%==========================================================================
%%% Internal callbacks
-wakeup_from_hibernate(Parent, Debug, S) ->
+wakeup_from_hibernate(P, Debug, S) ->
%% It is a new message that woke us up so we have to receive it now
- loop_receive(Parent, Debug, S).
+ loop_receive(P, Debug, S).
%%%==========================================================================
%%% State Machine engine implementation of proc_lib/gen server
@@ -885,132 +899,82 @@ wakeup_from_hibernate(Parent, Debug, S) ->
%% and detours through sys:handle_system_message/7 and proc_lib:hibernate/3
%% Entry point for system_continue/3
-loop(Parent, Debug, #state{hibernate = true, cancel_timers = 0} = S) ->
- loop_hibernate(Parent, Debug, S);
-loop(Parent, Debug, S) ->
- loop_receive(Parent, Debug, S).
+loop(P, Debug, #state{hibernate = true} = S) ->
+ loop_hibernate(P, Debug, S);
+loop(P, Debug, S) ->
+ loop_receive(P, Debug, S).
-loop_hibernate(Parent, Debug, S) ->
+loop_hibernate(P, Debug, S) ->
%%
%% Does not return but restarts process at
%% wakeup_from_hibernate/3 that jumps to loop_receive/3
%%
- proc_lib:hibernate(
- ?MODULE, wakeup_from_hibernate, [Parent,Debug,S]),
+ proc_lib:hibernate(?MODULE, wakeup_from_hibernate, [P, Debug, S]),
error(
{should_not_have_arrived_here_but_instead_in,
- {wakeup_from_hibernate,3}}).
+ {?MODULE,wakeup_from_hibernate,3}}).
+
%% Entry point for wakeup_from_hibernate/3
loop_receive(
- Parent, Debug, #state{hibernate_after = HibernateAfterTimeout} = S) ->
+ #params{hibernate_after = HibernateAfterTimeout} = P, Debug, S) ->
%%
receive
Msg ->
case Msg of
+ {'$gen_call',From,Request} ->
+ loop_receive_result(P, Debug, S, {{call,From},Request});
+ {'$gen_cast',Cast} ->
+ loop_receive_result(P, Debug, S, {cast,Cast});
+ %%
+ {timeout,TimerRef,TimeoutMsg} ->
+ {TimerRefs,TimeoutTypes} = S#state.timers,
+ case TimerRefs of
+ #{TimerRef := TimeoutType} ->
+ %% Our timer
+ Timers =
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimeoutType, TimeoutTypes)},
+ S_1 = S#state{timers = Timers},
+ loop_receive_result(
+ P, Debug, S_1, {TimeoutType,TimeoutMsg});
+ #{} ->
+ loop_receive_result(P, Debug, S, {info,Msg})
+ end;
+ %%
{system,Pid,Req} ->
%% Does not return but tail recursively calls
%% system_continue/3 that jumps to loop/3
sys:handle_system_msg(
- Req, Pid, Parent, ?MODULE, Debug, S,
+ Req, Pid, P#params.parent, ?MODULE, Debug,
+ {P,S},
S#state.hibernate);
- {'EXIT',Parent,Reason} = EXIT ->
- %% EXIT is not a 2-tuple therefore
- %% not an event but this will stand out
- %% in the crash report...
- Q = [EXIT],
- terminate(exit, Reason, ?STACKTRACE(), Debug, S, Q);
- {timeout,TimerRef,TimerMsg} ->
- #state{
- timer_refs = TimerRefs,
- timer_types = TimerTypes} = S,
- case TimerRefs of
- #{TimerRef := TimerType} ->
- %% We know of this timer; is it a running
- %% timer or a timer being cancelled that
- %% managed to send a late timeout message?
- case TimerTypes of
- #{TimerType := TimerRef} ->
- %% The timer type maps back to this
- %% timer ref, so it was a running timer
- %% Unregister the triggered timeout
- NewTimerRefs =
- maps:remove(TimerRef, TimerRefs),
- NewTimerTypes =
- maps:remove(TimerType, TimerTypes),
- loop_receive_result(
- Parent, Debug,
- S#state{
- timer_refs = NewTimerRefs,
- timer_types = NewTimerTypes},
- TimerType, TimerMsg);
- _ ->
- %% This was a late timeout message
- %% from timer being cancelled, so
- %% ignore it and expect a cancel_timer
- %% msg shortly
- loop_receive(Parent, Debug, S)
- end;
- _ ->
- %% Not our timer; present it as an event
- loop_receive_result(Parent, Debug, S, info, Msg)
- end;
- {cancel_timer,TimerRef,_} ->
- #state{
- timer_refs = TimerRefs,
- cancel_timers = CancelTimers,
- hibernate = Hibernate} = S,
- case TimerRefs of
- #{TimerRef := _} ->
- %% We must have requested a cancel
- %% of this timer so it is already
- %% removed from TimerTypes
- NewTimerRefs =
- maps:remove(TimerRef, TimerRefs),
- NewCancelTimers = CancelTimers - 1,
- NewS =
- S#state{
- timer_refs = NewTimerRefs,
- cancel_timers = NewCancelTimers},
- if
- Hibernate =:= true, NewCancelTimers =:= 0 ->
- %% No more cancel_timer msgs to expect;
- %% we can hibernate
- loop_hibernate(Parent, Debug, NewS);
- NewCancelTimers >= 0 -> % Assert
- loop_receive(Parent, Debug, NewS)
- end;
- _ ->
- %% Not our cancel_timer msg;
- %% present it as an event
- loop_receive_result(Parent, Debug, S, info, Msg)
- end;
- _ ->
- %% External msg
- case Msg of
- {'$gen_call',From,Request} ->
- loop_receive_result(
- Parent, Debug, S, {call,From}, Request);
- {'$gen_cast',Cast} ->
- loop_receive_result(Parent, Debug, S, cast, Cast);
+ {'EXIT',Pid,Reason} ->
+ case P#params.parent of
+ Pid ->
+ terminate(
+ exit, Reason, ?STACKTRACE(), P, Debug, S, []);
_ ->
- loop_receive_result(Parent, Debug, S, info, Msg)
- end
+ loop_receive_result(P, Debug, S, {info,Msg})
+ end;
+ %%
+ _ ->
+ loop_receive_result(P, Debug, S, {info,Msg})
end
after
- HibernateAfterTimeout ->
- loop_hibernate(Parent, Debug, S)
+ HibernateAfterTimeout ->
+ loop_hibernate(P, Debug, S)
end.
-loop_receive_result(Parent, ?not_sys_debug, S, Type, Content) ->
+loop_receive_result(P, ?not_sys_debug = Debug, S, Event) ->
%% Here is the queue of not yet handled events created
Events = [],
- loop_event(Parent, ?not_sys_debug, S, Events, Type, Content);
+ loop_event(P, Debug, S, Event, Events);
loop_receive_result(
- Parent, Debug, #state{name = Name, state = State} = S, Type, Content) ->
- Event = {Type,Content},
- NewDebug =
- case S#state.callback_mode of
+ #params{name = Name, callback_mode = CallbackMode} = P, Debug,
+ #state{state_data = {State,_Data}} = S, Event) ->
+ Debug_1 =
+ case CallbackMode of
undefined ->
sys_debug(Debug, Name, {code_change,Event,State});
_ ->
@@ -1018,284 +982,415 @@ loop_receive_result(
end,
%% Here is the queue of not yet handled events created
Events = [],
- loop_event(Parent, NewDebug, S, Events, Type, Content).
+ loop_event(P, Debug_1, S, Event, Events).
-%% Entry point for handling an event, received or enqueued
+%% Entry point for handling an event; received or enqueued
loop_event(
- Parent, Debug, #state{hibernate = Hibernate} = S,
- Events, Type, Content) ->
+ P, Debug, #state{hibernate = true} = S, Event, Events) ->
%%
- case Hibernate of
- true ->
- %%
- %% If (this old) Hibernate is true here it can only be
- %% because it was set from an event action
- %% and we did not go into hibernation since there were
- %% events in queue, so we do what the user
- %% might rely on i.e collect garbage which
- %% would have happened if we actually hibernated
- %% and immediately was awakened.
- %%
- _ = garbage_collect(),
- loop_event_state_function(
- Parent, Debug, S, Events, Type, Content);
- false ->
- loop_event_state_function(
- Parent, Debug, S, Events, Type, Content)
- end.
+ %% If (this old) Hibernate is true here it can only be
+ %% because it was set from an event action
+ %% and we did not go into hibernation since there were
+ %% events in queue, so we do what the user
+ %% might rely on i.e collect garbage which
+ %% would have happened if we actually hibernated
+ %% and immediately was awakened.
+ %%
+ _ = garbage_collect(),
+ loop_event_handler(P, Debug, S, Event, Events);
+loop_event(P, Debug, S, Event, Events) ->
+ loop_event_handler(P, Debug, S, Event, Events).
+-compile({inline, [loop_event_handler/5]}).
%% Call the state function
-loop_event_state_function(
- Parent, Debug,
- #state{state = State, data = Data} = S,
- Events, Type, Content) ->
+loop_event_handler(
+ P, Debug, #state{state_data = State_Data} = S, Event, Events) ->
%%
%% The field 'hibernate' in S is now invalid and will be
- %% restored when looping back to loop/3 or loop_event/6.
+ %% restored when looping back to loop/3 or loop_event/5.
%%
- Event = {Type,Content},
- TransOpts = false,
- case call_state_function(S, Type, Content, State, Data) of
- {Result, NewS} ->
- loop_event_result(
- Parent, Debug, NewS,
- Events, Event, State, Data, TransOpts, Result);
- [Class,Reason,Stacktrace] ->
- terminate(
- Class, Reason, Stacktrace, Debug, S, [Event|Events])
- end.
+ Q = [Event|Events],
+ TransOpts = trans_opts, % Marker for state_call
+ loop_callback_mode(
+ P, Debug, S, Q, State_Data, TransOpts, Event).
+
+
+%% Predicate for state call i.e not state_enter call
+%%
+-define(state_call(TransOpts), ((TransOpts) =:= trans_opts)).
+
+%% Convert state call marker to valid #trans_opts{} record
+%%
+-compile({inline, [trans_opts/1]}).
+trans_opts(trans_opts) ->
+ #trans_opts{};
+trans_opts(TransOpts) ->
+ TransOpts.
+
+%% Access function for hibernate in record #trans_opts{}
+%% or when the record is a state call marker 'trans_opts'
+%%
+-compile({inline, [trans_opts__hibernate/1]}).
+trans_opts__hibernate(trans_opts) ->
+ (#trans_opts{})#trans_opts.hibernate;
+trans_opts__hibernate(#trans_opts{hibernate = Hibernate}) ->
+ Hibernate.
+
+
%% Make a state enter call to the state function
-loop_event_state_enter(
- Parent, Debug, #state{state = PrevState} = S,
- Events, Event, NextState, NewData, TransOpts) ->
+loop_state_enter(
+ P, Debug, #state{state_data = {PrevState,_PrevData}} = S,
+ Q, NextState_NewData, TransOpts) ->
+ %%
+ loop_callback_mode(
+ P, Debug, S, Q, NextState_NewData, TransOpts, {enter,PrevState}).
+
+loop_callback_mode(
+ #params{callback_mode = undefined} = P, Debug, S,
+ Q, State_Data, TransOpts, CallbackEvent) ->
%%
- case call_state_function(S, enter, PrevState, NextState, NewData) of
- {Result, NewS} ->
- loop_event_result(
- Parent, Debug, NewS,
- Events, Event, NextState, NewData, TransOpts, Result);
- [Class,Reason,Stacktrace] ->
+ Module = P#params.module,
+ try Module:callback_mode() of
+ CallbackMode ->
+ loop_callback_mode_result(
+ P, Debug, S,
+ Q, State_Data, TransOpts, CallbackEvent,
+ CallbackMode, listify(CallbackMode), undefined, false)
+ catch
+ CallbackMode ->
+ loop_callback_mode_result(
+ P, Debug, S,
+ Q, State_Data, TransOpts, CallbackEvent,
+ CallbackMode, listify(CallbackMode), undefined, false);
+ Class:Reason:Stacktrace ->
terminate(
- Class, Reason, Stacktrace, Debug, S, [Event|Events])
+ Class, Reason, Stacktrace, P, Debug, S, Q)
+ end;
+loop_callback_mode(
+ P, Debug, S,
+ Q, State_Data, TransOpts, CallbackEvent) ->
+ %%
+ loop_state_callback(
+ P, Debug, S,
+ Q, State_Data, TransOpts, CallbackEvent).
+
+loop_callback_mode_result(
+ P, Debug, S,
+ Q, State_Data, TransOpts, CallbackEvent,
+ CallbackMode, [H|T], NewCallbackMode, NewStateEnter) ->
+ %%
+ case callback_mode(H) of
+ true ->
+ loop_callback_mode_result(
+ P, Debug, S,
+ Q, State_Data, TransOpts, CallbackEvent,
+ CallbackMode, T, H, NewStateEnter);
+ false ->
+ case state_enter(H) of
+ true ->
+ loop_callback_mode_result(
+ P, Debug, S,
+ Q, State_Data, TransOpts, CallbackEvent,
+ CallbackMode, T, NewCallbackMode, true);
+ false ->
+ terminate(
+ error,
+ {bad_return_from_callback_mode,CallbackMode},
+ ?STACKTRACE(),
+ P, Debug, S, Q)
+ end
+ end;
+loop_callback_mode_result(
+ P, Debug, S,
+ Q, State_Data, TransOpts, CallbackEvent,
+ CallbackMode, [], NewCallbackMode, NewStateEnter) ->
+ %%
+ case NewCallbackMode of
+ undefined ->
+ terminate(
+ error,
+ {bad_return_from_callback_mode,CallbackMode},
+ ?STACKTRACE(),
+ P, Debug, S, Q);
+ _ ->
+ P_1 =
+ P#params{
+ callback_mode = NewCallbackMode,
+ state_enter = NewStateEnter},
+ loop_state_callback(
+ P_1, Debug, S,
+ Q, State_Data, TransOpts, CallbackEvent)
end.
+loop_state_callback(
+ #params{callback_mode = CallbackMode, module = Module} = P,
+ Debug, S,
+ Q, {State,Data} = State_Data, TransOpts, {Type,Content}) ->
+ try
+ case CallbackMode of
+ state_functions ->
+ Module:State(Type, Content, Data);
+ handle_event_function ->
+ Module:handle_event(Type, Content, State, Data)
+ end
+ of
+ Result ->
+ loop_state_callback_result(
+ P, Debug, S,
+ Q, State_Data, TransOpts, Result)
+ catch
+ Result ->
+ loop_state_callback_result(
+ P, Debug, S,
+ Q, State_Data, TransOpts, Result);
+ Class:Reason:Stacktrace ->
+ terminate(Class, Reason, Stacktrace, P, Debug, S, Q)
+ end;
+loop_state_callback(
+ P, Debug, S, Q, State_Data, TransOpts, Actions) ->
+ %% Tunneled actions from enter/8
+ CallEnter = true,
+ loop_actions_list(
+ P, Debug, S, Q, State_Data, TransOpts, Actions, CallEnter).
+
%% Process the result from the state function.
-%% When TransOpts =:= false it was a state function call,
+%% When TransOpts =:= trans_opts it was a state function call,
%% otherwise it is an option tuple and it was a state enter call.
%%
-loop_event_result(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts, Result) ->
+loop_state_callback_result(
+ P, Debug, S, Q, {State,_Data} = State_Data, TransOpts, Result) ->
%%
case Result of
{next_state,State,NewData} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
+ loop_actions(
+ P, Debug, S, Q, {State,NewData}, TransOpts,
[], false);
{next_state,NextState,NewData}
- when TransOpts =:= false ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, NextState, NewData, TransOpts,
+ when ?state_call(TransOpts) ->
+ loop_actions(
+ P, Debug, S, Q, {NextState,NewData}, TransOpts,
[], true);
{next_state,_NextState,_NewData} ->
terminate(
error,
{bad_state_enter_return_from_state_function,Result},
- ?STACKTRACE(), Debug,
+ ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = trans_opts__hibernate(TransOpts)},
+ Q);
{next_state,State,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
+ loop_actions(
+ P, Debug, S, Q, {State,NewData}, TransOpts,
Actions, false);
{next_state,NextState,NewData,Actions}
- when TransOpts =:= false ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, NextState, NewData, TransOpts,
+ when ?state_call(TransOpts) ->
+ loop_actions(
+ P, Debug, S, Q, {NextState,NewData}, TransOpts,
Actions, true);
{next_state,_NextState,_NewData,_Actions} ->
terminate(
error,
{bad_state_enter_return_from_state_function,Result},
- ?STACKTRACE(), Debug,
+ ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = trans_opts__hibernate(TransOpts)},
+ Q);
%%
{keep_state,NewData} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
+ loop_actions(
+ P, Debug, S, Q, {State,NewData}, TransOpts,
[], false);
{keep_state,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
+ loop_actions(
+ P, Debug, S, Q, {State,NewData}, TransOpts,
Actions, false);
%%
keep_state_and_data ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
+ loop_actions(
+ P, Debug, S, Q, State_Data, TransOpts,
[], false);
{keep_state_and_data,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
+ loop_actions(
+ P, Debug, S, Q, State_Data, TransOpts,
Actions, false);
%%
{repeat_state,NewData} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
+ loop_actions(
+ P, Debug, S, Q, {State,NewData}, TransOpts,
[], true);
{repeat_state,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
+ loop_actions(
+ P, Debug, S, Q, {State,NewData}, TransOpts,
Actions, true);
%%
repeat_state_and_data ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
+ loop_actions(
+ P, Debug, S, Q, State_Data, TransOpts,
[], true);
{repeat_state_and_data,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
+ loop_actions(
+ P, Debug, S, Q, State_Data, TransOpts,
Actions, true);
%%
stop ->
terminate(
- exit, normal, ?STACKTRACE(), Debug,
+ exit, normal, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = trans_opts__hibernate(TransOpts)},
+ Q);
{stop,Reason} ->
terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = trans_opts__hibernate(TransOpts)},
+ Q);
{stop,Reason,NewData} ->
terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = NewData,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = {State,NewData},
+ hibernate = trans_opts__hibernate(TransOpts)},
+ Q);
%%
{stop_and_reply,Reason,Replies} ->
reply_then_terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events], Replies);
+ state_data = State_Data,
+ hibernate = trans_opts__hibernate(TransOpts)},
+ Q, Replies);
{stop_and_reply,Reason,Replies,NewData} ->
reply_then_terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = NewData,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events], Replies);
+ state_data = {State,NewData},
+ hibernate = trans_opts__hibernate(TransOpts)},
+ Q, Replies);
%%
_ ->
terminate(
error,
{bad_return_from_state_function,Result},
- ?STACKTRACE(), Debug,
+ ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events])
+ state_data = State_Data,
+ hibernate = trans_opts__hibernate(TransOpts)},
+ Q)
end.
%% Ensure that Actions are a list
-loop_event_actions(
- Parent, Debug, S,
- Events, Event, NextState, NewerData, TransOpts,
+loop_actions(
+ P, Debug, S, Q, NextState_NewData, TransOpts,
+ [], CallEnter) ->
+ %%
+ %% Shortcut for no actions
+ case CallEnter andalso P#params.state_enter of
+ true ->
+ loop_state_enter(
+ P, Debug, S, Q, NextState_NewData, trans_opts(TransOpts));
+ false ->
+ loop_state_transition(
+ P, Debug, S, Q, NextState_NewData, trans_opts(TransOpts))
+ end;
+loop_actions(
+ P, Debug, S, Q, NextState_NewData, TransOpts,
Actions, CallEnter) ->
- loop_event_actions_list(
- Parent, Debug, S,
- Events, Event, NextState, NewerData, TransOpts,
+ %%
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData, TransOpts,
listify(Actions), CallEnter).
+-compile({inline, loop_actions_list/8}).
%% Process actions from the state function
-loop_event_actions_list(
- Parent, Debug, #state{state_enter = StateEnter} = S,
- Events, Event, NextState, NewerData, TransOpts,
+loop_actions_list(
+ #params{state_enter = StateEnter} = P, Debug, S,
+ Q, NextState_NewData, TransOpts,
Actions, CallEnter) ->
%%
- case parse_actions(TransOpts, Debug, S, Actions) of
- {NewDebug,NewTransOpts}
+ case parse_actions(P, Debug, S, TransOpts, Actions) of
+ {Debug_1,TransOpts_1}
when StateEnter, CallEnter ->
- loop_event_state_enter(
- Parent, NewDebug, S,
- Events, Event, NextState, NewerData, NewTransOpts);
- {NewDebug,NewTransOpts} ->
- loop_event_done(
- Parent, NewDebug, S,
- Events, Event, NextState, NewerData, NewTransOpts);
- [Class,Reason,Stacktrace,NewDebug] ->
+ loop_state_enter(
+ P, Debug_1, S, Q, NextState_NewData, TransOpts_1);
+ {Debug_1,TransOpts_1} ->
+ loop_state_transition(
+ P, Debug_1, S, Q, NextState_NewData, TransOpts_1);
+ [Class,Reason,Stacktrace,Debug_1] ->
terminate(
- Class, Reason, Stacktrace, NewDebug,
+ Class, Reason, Stacktrace, P, Debug_1,
S#state{
- state = NextState,
- data = NewerData,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events])
+ state_data = NextState_NewData,
+ hibernate = trans_opts__hibernate(TransOpts)},
+ Q)
end.
--compile({inline, [hibernate_in_trans_opts/1]}).
-hibernate_in_trans_opts(false) ->
- (#trans_opts{})#trans_opts.hibernate;
-hibernate_in_trans_opts(#trans_opts{hibernate = Hibernate}) ->
- Hibernate.
-parse_actions(false, Debug, S, Actions) ->
- parse_actions(true, Debug, S, Actions, #trans_opts{});
-parse_actions(TransOpts, Debug, S, Actions) ->
- parse_actions(false, Debug, S, Actions, TransOpts).
+
+%%---------------------------------------------------------------------------
+%% The server loop continues after parse_actions
+
+parse_actions(P, Debug, S, TransOpts, Actions) ->
+ if
+ ?state_call(TransOpts) ->
+ %% Inlining of trans_opts/1 under the condition
+ %% that ?state_call(TransOpts) is 'true'
+ %%
+ TimeoutsR = (#trans_opts{})#trans_opts.timeouts_r,
+ NextEventsR = (#trans_opts{})#trans_opts.next_events_r,
+ parse_actions(
+ P, Debug, S, #trans_opts{}, Actions, true,
+ TimeoutsR, NextEventsR);
+ true ->
+ TimeoutsR = TransOpts#trans_opts.timeouts_r,
+ NextEventsR = TransOpts#trans_opts.next_events_r,
+ parse_actions(
+ P, Debug, S, TransOpts, Actions, false,
+ TimeoutsR, NextEventsR)
+ end.
%%
-parse_actions(_StateCall, Debug, _S, [], TransOpts) ->
- {Debug,TransOpts};
-parse_actions(StateCall, Debug, S, [Action|Actions], TransOpts) ->
+parse_actions(
+ _P, Debug, _S, TransOpts, [], _StateCall, TimeoutsR, NextEventsR) ->
+ {Debug,
+ TransOpts#trans_opts{
+ timeouts_r = TimeoutsR,
+ next_events_r = NextEventsR}};
+parse_actions(
+ P, Debug, S, TransOpts, [Action|Actions], StateCall,
+ TimeoutsR, NextEventsR) ->
case Action of
%% Actual actions
{reply,From,Reply} ->
parse_actions_reply(
- StateCall, Debug, S, Actions, TransOpts, From, Reply);
+ P, Debug, S, TransOpts, Actions, StateCall,
+ TimeoutsR, NextEventsR, From, Reply);
%%
%% Actions that set options
- {hibernate,NewHibernate} when is_boolean(NewHibernate) ->
+ {hibernate,Hibernate} when is_boolean(Hibernate) ->
+ TransOpts_1 = TransOpts#trans_opts{hibernate = Hibernate},
parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{hibernate = NewHibernate});
+ P, Debug, S, TransOpts_1, Actions, StateCall,
+ TimeoutsR, NextEventsR);
hibernate ->
+ TransOpts_1 = TransOpts#trans_opts{hibernate = true},
parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{hibernate = true});
+ P, Debug, S, TransOpts_1, Actions, StateCall,
+ TimeoutsR, NextEventsR);
%%
- {postpone,NewPostpone} when not NewPostpone orelse StateCall ->
+ {postpone,Postpone} when not Postpone orelse StateCall ->
+ TransOpts_1 = TransOpts#trans_opts{postpone = Postpone},
parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{postpone = NewPostpone});
+ P, Debug, S, TransOpts_1, Actions, StateCall,
+ TimeoutsR, NextEventsR);
postpone when StateCall ->
+ TransOpts_1 = TransOpts#trans_opts{postpone = true},
parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{postpone = true});
+ P, Debug, S, TransOpts_1, Actions, StateCall,
+ TimeoutsR, NextEventsR);
postpone ->
[error,
{bad_state_enter_action_from_state_function,Action},
@@ -1304,35 +1399,29 @@ parse_actions(StateCall, Debug, S, [Action|Actions], TransOpts) ->
%%
{next_event,Type,Content} ->
parse_actions_next_event(
- StateCall, Debug, S, Actions, TransOpts, Type, Content);
+ P, Debug, S, TransOpts, Actions, StateCall,
+ TimeoutsR, NextEventsR, Type, Content);
%%
_ ->
parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts, Action)
+ P, Debug, S, TransOpts, Actions, StateCall,
+ TimeoutsR, NextEventsR, Action)
end.
parse_actions_reply(
- StateCall, ?not_sys_debug = Debug, S, Actions, TransOpts,
- From, Reply) ->
- %%
- case from(From) of
- true ->
- reply(From, Reply),
- parse_actions(StateCall, ?not_sys_debug, S, Actions, TransOpts);
- false ->
- [error,
- {bad_action_from_state_function,{reply,From,Reply}},
- ?STACKTRACE(), Debug]
- end;
-parse_actions_reply(
- StateCall, Debug, #state{name = Name} = S,
- Actions, TransOpts, From, Reply) ->
+ P, Debug, S, TransOpts, Actions, StateCall,
+ TimeoutsR, NextEventsR, 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),
- NewDebug = sys_debug(Debug, Name, {out,Reply,From}),
- parse_actions(StateCall, NewDebug, S, Actions, TransOpts);
+ Debug_1 = ?sys_debug(Debug, P#params.name, {out,Reply,From}),
+ parse_actions(
+ P, Debug_1, S, TransOpts, Actions, StateCall,
+ TimeoutsR, NextEventsR);
false ->
[error,
{bad_action_from_state_function,{reply,From,Reply}},
@@ -1340,32 +1429,25 @@ parse_actions_reply(
end.
parse_actions_next_event(
- StateCall, ?not_sys_debug = Debug, S,
- Actions, TransOpts, Type, Content) ->
- case event_type(Type) of
- true when StateCall ->
- NextEventsR = TransOpts#trans_opts.next_events_r,
- parse_actions(
- StateCall, ?not_sys_debug, S, Actions,
- TransOpts#trans_opts{
- next_events_r = [{Type,Content}|NextEventsR]});
- _ ->
- [error,
- {bad_state_enter_action_from_state_function,
- {next_event,Type,Content}},
- ?STACKTRACE(), Debug]
- end;
-parse_actions_next_event(
- StateCall, Debug, #state{name = Name, state = State} = S,
- Actions, TransOpts, Type, Content) ->
+ P, Debug, S, TransOpts, Actions, StateCall,
+ TimeoutsR, NextEventsR, Type, Content) ->
case event_type(Type) of
true when StateCall ->
- NewDebug = sys_debug(Debug, Name, {in,{Type,Content},State}),
- NextEventsR = TransOpts#trans_opts.next_events_r,
- parse_actions(
- StateCall, NewDebug, S, Actions,
- TransOpts#trans_opts{
- next_events_r = [{Type,Content}|NextEventsR]});
+ NextEvent = {Type,Content},
+ case Debug of
+ ?not_sys_debug ->
+ parse_actions(
+ P, Debug, S, TransOpts, Actions, StateCall,
+ TimeoutsR, [NextEvent|NextEventsR]);
+ _ ->
+ Name = P#params.name,
+ {State,_Data} = S#state.state_data,
+ Debug_1 =
+ sys_debug(Debug, Name, {in,{Type,Content},State}),
+ parse_actions(
+ P, Debug_1, S, TransOpts, Actions, StateCall,
+ TimeoutsR, [NextEvent|NextEventsR])
+ end;
_ ->
[error,
{bad_state_enter_action_from_state_function,
@@ -1375,470 +1457,419 @@ parse_actions_next_event(
end.
parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts,
- {TimeoutType,Time,TimerMsg,TimerOpts} = AbsoluteTimeout) ->
+ P, Debug, S, TransOpts, Actions, StateCall, TimeoutsR, NextEventsR,
+ {TimeoutType,Time,TimeoutMsg,TimeoutOpts} = AbsoluteTimeout) ->
%%
- case classify_timeout(TimeoutType, Time, listify(TimerOpts)) of
- absolute ->
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, AbsoluteTimeout);
- relative ->
- RelativeTimeout = {TimeoutType,Time,TimerMsg},
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, RelativeTimeout);
- badarg ->
+ case timeout_event_type(TimeoutType) of
+ true ->
+ case listify(TimeoutOpts) of
+ %% Optimization cases
+ [] when ?relative_timeout(Time) ->
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
+ parse_actions(
+ P, Debug, S,
+ TransOpts, Actions, StateCall,
+ [RelativeTimeout|TimeoutsR], NextEventsR);
+ [{abs,true}] when ?absolute_timeout(Time) ->
+ parse_actions(
+ P, Debug, S,
+ TransOpts, Actions, StateCall,
+ [AbsoluteTimeout|TimeoutsR], NextEventsR);
+ [{abs,false}] when ?relative_timeout(Time) ->
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
+ parse_actions(
+ P, Debug, S,
+ TransOpts, Actions, StateCall,
+ [RelativeTimeout|TimeoutsR], NextEventsR);
+ %% Generic case
+ TimeoutOptsList ->
+ case parse_timeout_opts_abs(TimeoutOptsList) of
+ true when ?absolute_timeout(Time) ->
+ parse_actions(
+ P, Debug, S,
+ TransOpts, Actions, StateCall,
+ [AbsoluteTimeout|TimeoutsR], NextEventsR);
+ false when ?relative_timeout(Time) ->
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
+ parse_actions(
+ P, Debug, S,
+ TransOpts, Actions, StateCall,
+ [RelativeTimeout|TimeoutsR], NextEventsR);
+ badarg ->
+ [error,
+ {bad_action_from_state_function,
+ AbsoluteTimeout},
+ ?STACKTRACE(),
+ Debug]
+ end
+ end;
+ false ->
[error,
{bad_action_from_state_function,AbsoluteTimeout},
?STACKTRACE(),
Debug]
end;
parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts,
+ P, Debug, S, TransOpts, Actions, StateCall, TimeoutsR, NextEventsR,
{TimeoutType,Time,_} = RelativeTimeout) ->
- case classify_timeout(TimeoutType, Time, []) of
- relative ->
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, RelativeTimeout);
- badarg ->
+ %%
+ case timeout_event_type(TimeoutType) of
+ true when ?relative_timeout(Time) ->
+ parse_actions(
+ P, Debug, S, TransOpts, Actions, StateCall,
+ [RelativeTimeout|TimeoutsR], NextEventsR);
+ _ ->
[error,
{bad_action_from_state_function,RelativeTimeout},
?STACKTRACE(),
Debug]
end;
parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts,
+ P, Debug, S, TransOpts, Actions, StateCall, TimeoutsR, NextEventsR,
Time) ->
- case classify_timeout(timeout, Time, []) of
- relative ->
+ %%
+ if
+ ?relative_timeout(Time) ->
RelativeTimeout = {timeout,Time,Time},
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, RelativeTimeout);
- badarg ->
+ parse_actions(
+ P, Debug, S, TransOpts, Actions, StateCall,
+ [RelativeTimeout|TimeoutsR], NextEventsR);
+ true ->
[error,
{bad_action_from_state_function,Time},
?STACKTRACE(),
Debug]
end.
-parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- #trans_opts{timeouts_r = TimeoutsR} = TransOpts, Timeout) ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{timeouts_r = [Timeout|TimeoutsR]}).
+%%---------------------------------------------------------------------------
+%% Here the server loop continues
+
+
%% Do the state transition
-loop_event_done(
- Parent, ?not_sys_debug,
- #state{postponed = P} = S,
-%% #state{postponed = will_not_happen = P} = S,
- Events, Event, NextState, NewData,
- #trans_opts{
- postpone = Postpone, hibernate = Hibernate,
- timeouts_r = [], next_events_r = NextEventsR}) ->
- %%
- %% Optimize the simple cases i.e no debug and no timer changes,
- %% by duplicate stripped down code
- %%
- %% Fast path
- %%
- case Postpone of
- true ->
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, [Event|P], NextState, NewData, NextEventsR);
- false ->
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR)
- end;
-loop_event_done(
- Parent, Debug_0,
- #state{
- state = State, postponed = P_0,
- timer_refs = TimerRefs_0, timer_types = TimerTypes_0,
- cancel_timers = CancelTimers_0} = S,
- Events_0, Event_0, NextState, NewData,
- #trans_opts{
- hibernate = Hibernate, timeouts_r = TimeoutsR,
- postpone = Postpone, next_events_r = NextEventsR}) ->
+loop_state_transition(
+ P, Debug, #state{state_data = {State,_Data}, postponed = Postponed} = S,
+ [Event|Events], {NextState,_NewData} = NextState_NewData,
+ #trans_opts{postpone = Postpone} = TransOpts) ->
%%
%% All options have been collected and next_events are buffered.
%% Do the actual state transition.
%%
- %% Full feature path
- %%
- [Debug_1|P_1] = % Move current event to postponed if Postpone
+ Postponed_1 = % Move current event to postponed if Postpone
case Postpone of
true ->
- [?sys_debug(
- Debug_0,
- S#state.name,
- {postpone,Event_0,State,NextState}),
- Event_0|P_0];
+ [Event|Postponed];
false ->
- [?sys_debug(
- Debug_0,
- S#state.name,
- {consume,Event_0,State,NextState})|P_0]
+ Postponed
end,
- {Events_2,P_2,
- Timers_2} =
- %% Cancel the event timeout
- if
- NextState =:= State ->
- {Events_0,P_1,
- cancel_timer_by_type(
- timeout, {TimerTypes_0,CancelTimers_0})};
- true ->
- %% Move all postponed events to queue
- %% and cancel the state timeout
- {lists:reverse(P_1, Events_0),[],
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes_0,CancelTimers_0}))}
- end,
- {TimerRefs_3,{TimerTypes_3,CancelTimers_3},TimeoutEvents} =
- %% Stop and start timers
- parse_timers(TimerRefs_0, Timers_2, TimeoutsR),
- %% Place next events last in reversed queue
- Events_3R = lists:reverse(Events_2, NextEventsR),
- %% Enqueue immediate timeout events
- [Debug_2|Events_4R] =
- prepend_timeout_events(
- NextState, Debug_1, S, TimeoutEvents, Events_3R),
- loop_event_done(
- Parent, Debug_2,
- S#state{
- state = NextState,
- data = NewData,
- postponed = P_2,
- timer_refs = TimerRefs_3,
- timer_types = TimerTypes_3,
- cancel_timers = CancelTimers_3,
- hibernate = Hibernate},
- lists:reverse(Events_4R)).
-
-loop_event_done(Parent, Debug, S, Q) ->
-%% io:format(
-%% "loop_event_done:~n"
-%% " state = ~p, data = ~p, postponed = ~p~n "
-%% " timer_refs = ~p, timer_types = ~p, cancel_timers = ~p.~n"
-%% " Q = ~p.~n",
-%% [S#state.state,S#state.data,S#state.postponed,
-%% S#state.timer_refs,S#state.timer_types,S#state.cancel_timers,
-%% Q]),
- case Q of
- [] ->
- %% Get a new event
- loop(Parent, Debug, S);
- [{Type,Content}|Events] ->
- %% Loop until out of enqueued events
- loop_event(Parent, Debug, S, Events, Type, Content)
+ case Debug of
+ ?not_sys_debug ->
+ %% Optimization for no sys_debug
+ %% - avoid calling sys_debug/3
+ if
+ NextState =:= State ->
+ loop_keep_state(
+ P, Debug, S,
+ Events, NextState_NewData, TransOpts, Postponed_1);
+ true ->
+ loop_state_change(
+ P, Debug, S,
+ Events, NextState_NewData, TransOpts, Postponed_1)
+ end;
+ _ ->
+ %% With sys_debug
+ Name = P#params.name,
+ Debug_1 =
+ case Postpone of
+ true ->
+ sys_debug(
+ Debug, Name,
+ {postpone,Event,State,NextState});
+ false ->
+ sys_debug(
+ Debug, Name,
+ {consume,Event,State,NextState})
+ end,
+ if
+ NextState =:= State ->
+ loop_keep_state(
+ P, Debug_1, S,
+ Events, NextState_NewData, TransOpts, Postponed_1);
+ true ->
+ loop_state_change(
+ P, Debug_1, S,
+ Events, NextState_NewData, TransOpts, Postponed_1)
+ end
end.
+loop_keep_state(
+ P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S,
+ Events, NextState_NewData, TransOpts, Postponed) ->
+ %%
+ %% Cancel event timeout
+ %%
+ case TimeoutTypes of
+ %% Optimization
+ %% - only cancel timer when there is a timer to cancel
+ #{timeout := TimerRef} ->
+ %% Event timeout active
+ loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData, TransOpts, Postponed,
+ cancel_timer_by_ref_and_type(
+ TimerRef, timeout, TimerRefs, TimeoutTypes));
+ _ ->
+ %% No event timeout active
+ loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData, TransOpts, Postponed,
+ Timers)
+ end.
-%% Fast path
-%%
-%% Cancel event timer and state timer only if running
-loop_event_done_fast(
- Parent, Hibernate,
- #state{
- state = NextState,
- timer_types = TimerTypes,
- cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData, NextEventsR) ->
- %% Same state
- case TimerTypes of
- #{timeout := _} ->
- %% Event timeout active
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers}));
- _ ->
- %% No event timeout active
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- {TimerTypes,CancelTimers})
- end;
-loop_event_done_fast(
- Parent, Hibernate,
- #state{
- timer_types = TimerTypes,
- cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData, NextEventsR) ->
- %% State change
- case TimerTypes of
- #{timeout := _} ->
- %% Event timeout active
- %% - cancel state_timeout too since it is faster than inspecting
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers})));
- #{state_timeout := _} ->
- %% State_timeout active but not event timeout
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- cancel_timer_by_type(
- state_timeout, {TimerTypes,CancelTimers}));
+loop_state_change(
+ P, Debug, S, Events, NextState_NewData, TransOpts, Postponed) ->
+ %%
+ %% Retry postponed events
+ %%
+ case Postponed of
+ [] ->
+ loop_state_change(
+ P, Debug, S,
+ Events,
+ NextState_NewData, TransOpts);
+ [E1] ->
+ loop_state_change(
+ P, Debug, S,
+ [E1|Events],
+ NextState_NewData, TransOpts);
+ [E2,E1] ->
+ loop_state_change(
+ P, Debug, S,
+ [E1,E2|Events],
+ NextState_NewData, TransOpts);
_ ->
- %% No event nor state_timeout active
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- {TimerTypes,CancelTimers})
+ loop_state_change(
+ P, Debug, S,
+ lists:reverse(Postponed, Events),
+ NextState_NewData, TransOpts)
end.
%%
-%% Retry postponed events
-loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR, TimerTypes_CancelTimers) ->
- case P of
- %% Handle 0..2 postponed events without list reversal since
- %% that will move out all live registers and back again
- [] ->
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- Events, [], NextState, NewData, NextEventsR,
- TimerTypes_CancelTimers);
- [E] ->
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- [E|Events], [], NextState, NewData, NextEventsR,
- TimerTypes_CancelTimers);
- [E1,E2] ->
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- [E2,E1|Events], [], NextState, NewData, NextEventsR,
- TimerTypes_CancelTimers);
+loop_state_change(
+ P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S,
+ Events, NextState_NewData, TransOpts) ->
+ %%
+ %% Cancel state and event timeout
+ %%
+ case TimeoutTypes of
+ %% Optimization
+ %% - only cancel timeout when there is an active timeout
+ %%
+ #{state_timeout := TimerRef} ->
+ %% State timeout active
+ %% - cancel event timeout too since it is faster than inspecting
+ loop_next_events(
+ P, Debug, S, Events, NextState_NewData, TransOpts, [],
+ cancel_timer_by_type(
+ timeout,
+ cancel_timer_by_ref_and_type(
+ TimerRef, state_timeout, TimerRefs, TimeoutTypes)));
+ #{timeout := TimerRef} ->
+ %% Event timeout active but not state timeout
+ %% - cancel event timeout only
+ loop_next_events(
+ P, Debug, S, Events, NextState_NewData, TransOpts, [],
+ cancel_timer_by_ref_and_type(
+ TimerRef, timeout, TimerRefs, TimeoutTypes));
_ ->
- %% A bit slower path
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- lists:reverse(P, Events), [], NextState, NewData, NextEventsR,
- TimerTypes_CancelTimers)
+ %% No state nor event timeout active.
+ loop_next_events(
+ P, Debug, S, Events, NextState_NewData, TransOpts, [],
+ Timers)
end.
-%%
-%% Fast path
-%%
-loop_event_done_fast_2(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- {TimerTypes,CancelTimers}) ->
+
+loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData,
+ #trans_opts{timeouts_r = []} = TransOpts,
+ Postponed, Timers) ->
%%
- NewS =
+ %% Optimization when there are no timeout actions
+ %% hence no timeout zero events to append to Events
+ %% - avoid calling parse_timers/2
+ loop_done(
+ P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ postponed = Postponed,
+ timers = Timers,
+ hibernate = TransOpts#trans_opts.hibernate},
+ TransOpts#trans_opts.next_events_r, Events);
+loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData,
+ #trans_opts{timeouts_r = TimeoutsR} = TransOpts,
+ Postponed, Timers) ->
+ %%
+ {Timers_1,TimeoutEvents} = parse_timers(Timers, TimeoutsR),
+ S_1 =
S#state{
- state = NextState,
- data = NewData,
- postponed = P,
- timer_types = TimerTypes,
- cancel_timers = CancelTimers,
- hibernate = Hibernate},
- case NextEventsR of
- %% Handle 0..2 next events without list reversal since
- %% that will move out all live registers and back again
+ state_data = NextState_NewData,
+ postponed = Postponed,
+ timers = Timers_1,
+ hibernate = TransOpts#trans_opts.hibernate},
+ NextEventsR = TransOpts#trans_opts.next_events_r,
+ case TimeoutEvents of
[] ->
- loop_event_done(Parent, ?not_sys_debug, NewS, Events);
- [E] ->
- loop_event_done(Parent, ?not_sys_debug, NewS, [E|Events]);
- [E2,E1] ->
- loop_event_done(Parent, ?not_sys_debug, NewS, [E1,E2|Events]);
+ loop_done(P, Debug, S_1, NextEventsR, Events);
_ ->
- %% A bit slower path
- loop_event_done(
- Parent, ?not_sys_debug, NewS, lists:reverse(NextEventsR, Events))
+ case Events of
+ [] ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ NextEventsR);
+ [E1] ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ [E1|NextEventsR]);
+ [E2,E1] ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ [E1,E2|NextEventsR]);
+ _ ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ lists:reverse(Events, NextEventsR))
+ end
end.
-%%---------------------------------------------------------------------------
-%% Server loop helpers
+loop_prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR) ->
+ {Debug_1,Events_1R} =
+ prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR),
+ loop_done(P, Debug_1, S, Events_1R, []).
-call_callback_mode(#state{module = Module} = S) ->
- try Module:callback_mode() of
- CallbackMode ->
- callback_mode_result(S, CallbackMode)
- catch
- CallbackMode ->
- callback_mode_result(S, CallbackMode);
- Class:Reason:Stacktrace ->
- [Class,Reason,Stacktrace]
+loop_done(P, Debug, S, NextEventsR, Events) ->
+ case NextEventsR of
+ [] ->
+ loop_done(P, Debug, S, Events);
+ [E1] ->
+ loop_done(P, Debug, S, [E1|Events]);
+ [E2,E1] ->
+ loop_done(P, Debug, S, [E1,E2|Events]);
+ _ ->
+ loop_done(P, Debug, S, lists:reverse(NextEventsR, Events))
end.
-
-callback_mode_result(S, CallbackMode) ->
- callback_mode_result(
- S, CallbackMode, listify(CallbackMode), undefined, false).
%%
-callback_mode_result(_S, CallbackMode, [], undefined, _StateEnter) ->
- [error,
- {bad_return_from_callback_mode,CallbackMode},
- ?STACKTRACE()];
-callback_mode_result(S, _CallbackMode, [], CBMode, StateEnter) ->
- S#state{callback_mode = CBMode, state_enter = StateEnter};
-callback_mode_result(S, CallbackMode, [H|T], CBMode, StateEnter) ->
- case callback_mode(H) of
- true ->
- callback_mode_result(S, CallbackMode, T, H, StateEnter);
- false ->
- case state_enter(H) of
- true ->
- callback_mode_result(S, CallbackMode, T, CBMode, true);
- false ->
- [error,
- {bad_return_from_callback_mode,CallbackMode},
- ?STACKTRACE()]
- end
- end.
-
-
-call_state_function(
- #state{callback_mode = undefined} = S, Type, Content, State, Data) ->
- case call_callback_mode(S) of
- #state{} = NewS ->
- call_state_function(NewS, Type, Content, State, Data);
- Error ->
- Error
- end;
-call_state_function(
- #state{callback_mode = CallbackMode, module = Module} = S,
- Type, Content, State, Data) ->
- try
- case CallbackMode of
- state_functions ->
- Module:State(Type, Content, Data);
- handle_event_function ->
- Module:handle_event(Type, Content, State, Data)
- end
- of
- Result ->
- {Result,S}
- catch
- Result ->
- {Result,S};
- Class:Reason:Stacktrace ->
- [Class,Reason,Stacktrace]
+loop_done(P, Debug, S, Q) ->
+%%% io:format(
+%%% "loop_done: state_data = ~p,~n"
+%%% " postponed = ~p, Q = ~p,~n",
+%%% " timers = ~p.~n"
+%%% [S#state.state_data,,S#state.postponed,Q,S#state.timers]),
+ case Q of
+ [] ->
+ %% Get a new event
+ loop(P, Debug, S);
+ [Event|Events] ->
+ %% Loop until out of enqueued events
+ loop_event(P, Debug, S, Event, Events)
end.
+%%---------------------------------------------------------------------------
+%% Server loop helpers
-%% -> absolute | relative | badarg
-classify_timeout(TimeoutType, Time, Opts) ->
- case timeout_event_type(TimeoutType) of
- true ->
- classify_time(false, Time, Opts);
- false ->
- badarg
- end.
-
-classify_time(Abs, Time, []) ->
- case Abs of
- true when
- is_integer(Time);
- Time =:= infinity ->
- absolute;
- false when
- is_integer(Time), 0 =< Time;
- Time =:= infinity ->
- relative;
+parse_timeout_opts_abs(Opts) ->
+ parse_timeout_opts_abs(Opts, false).
+%%
+parse_timeout_opts_abs(Opts, Abs) ->
+ case Opts of
+ [] ->
+ Abs;
+ [{abs,Abs_1}|Opts] when is_boolean(Abs_1) ->
+ parse_timeout_opts_abs(Opts, Abs_1);
_ ->
badarg
- end;
-classify_time(_, Time, [{abs,Abs}|Opts]) when is_boolean(Abs) ->
- classify_time(Abs, Time, Opts);
-classify_time(_, _, Opts) when is_list(Opts) ->
- badarg.
+ end.
%% Stop and start timers as well as create timeout zero events
%% and pending event timer
%%
%% Stop and start timers non-event timers
-parse_timers(TimerRefs, Timers, TimeoutsR) ->
- parse_timers(TimerRefs, Timers, TimeoutsR, #{}, []).
+parse_timers(Timers, TimeoutsR) ->
+ parse_timers(Timers, TimeoutsR, #{}, []).
%%
-parse_timers(
- TimerRefs, Timers, [], _Seen, TimeoutEvents) ->
+parse_timers(Timers, [], _Seen, TimeoutEvents) ->
%%
- {TimerRefs,Timers,TimeoutEvents};
-parse_timers(
- TimerRefs, Timers, [Timeout|TimeoutsR], Seen, TimeoutEvents) ->
+ {Timers,TimeoutEvents};
+parse_timers(Timers, [Timeout|TimeoutsR], Seen, TimeoutEvents) ->
%%
case Timeout of
- {TimerType,Time,TimerMsg,TimerOpts} ->
+ {TimeoutType,Time,TimeoutMsg,TimeoutOpts} ->
%% Absolute timer
parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, Time, TimerMsg, listify(TimerOpts));
+ Timers, TimeoutsR, Seen, TimeoutEvents,
+ TimeoutType, Time, TimeoutMsg, listify(TimeoutOpts));
%% Relative timers below
- {TimerType,0,TimerMsg} ->
+ {TimeoutType,0,TimeoutMsg} ->
parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, zero, TimerMsg, []);
- {TimerType,Time,TimerMsg} ->
+ Timers, TimeoutsR, Seen, TimeoutEvents,
+ TimeoutType, zero, TimeoutMsg, []);
+ {TimeoutType,Time,TimeoutMsg} ->
parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, Time, TimerMsg, [])
+ Timers, TimeoutsR, Seen, TimeoutEvents,
+ TimeoutType, Time, TimeoutMsg, [])
end.
parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, Time, TimerMsg, TimerOpts) ->
+ Timers, TimeoutsR, Seen, TimeoutEvents,
+ TimeoutType, Time, TimeoutMsg, TimeoutOpts) ->
case Seen of
- #{TimerType := _} ->
+ #{TimeoutType := _} ->
%% Type seen before - ignore
parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents);
+ Timers, TimeoutsR, Seen, TimeoutEvents);
#{} ->
%% Unseen type - handle
- NewSeen = Seen#{TimerType => true},
+ Seen_1 = Seen#{TimeoutType => true},
case Time of
infinity ->
%% Cancel any running timer
parse_timers(
- TimerRefs, cancel_timer_by_type(TimerType, Timers),
- TimeoutsR, NewSeen, TimeoutEvents);
+ cancel_timer_by_type(TimeoutType, Timers),
+ TimeoutsR, Seen_1, TimeoutEvents);
zero ->
%% Cancel any running timer
%% Handle zero time timeouts later
parse_timers(
- TimerRefs, cancel_timer_by_type(TimerType, Timers),
- TimeoutsR, NewSeen,
- [{TimerType,TimerMsg}|TimeoutEvents]);
+ cancel_timer_by_type(TimeoutType, Timers),
+ TimeoutsR, Seen_1,
+ [{TimeoutType,TimeoutMsg}|TimeoutEvents]);
_ ->
%% (Re)start the timer
TimerRef =
erlang:start_timer(
- Time, self(), TimerMsg, TimerOpts),
- case Timers of
- {#{TimerType := OldTimerRef} = TimerTypes,
- CancelTimers} ->
- %% Cancel the running timer
- cancel_timer(OldTimerRef),
- NewCancelTimers = CancelTimers + 1,
- %% Insert the new timer into
- %% both TimerRefs and TimerTypes
- parse_timers(
- TimerRefs#{TimerRef => TimerType},
- {TimerTypes#{TimerType => TimerRef},
- NewCancelTimers},
- TimeoutsR, NewSeen, TimeoutEvents);
- {#{} = TimerTypes,CancelTimers} ->
- %% Insert the new timer into
- %% both TimerRefs and TimerTypes
- parse_timers(
- TimerRefs#{TimerRef => TimerType},
- {TimerTypes#{TimerType => TimerRef},
- CancelTimers},
- TimeoutsR, NewSeen, TimeoutEvents)
+ Time, self(), TimeoutMsg, TimeoutOpts),
+ {TimerRefs,TimeoutTypes} = Timers,
+ case TimeoutTypes of
+ #{TimeoutType := OldTimerRef} ->
+ %% Cancel the running timer,
+ %% update the timeout type,
+ %% insert the new timer ref,
+ %% and remove the old timer ref
+ cancel_timer(OldTimerRef),
+ Timers_1 =
+ {maps:remove(
+ OldTimerRef,
+ TimerRefs#{TimerRef => TimeoutType}),
+ TimeoutTypes#{TimeoutType := TimerRef}},
+ parse_timers(
+ Timers_1, TimeoutsR, Seen_1, TimeoutEvents);
+ #{} ->
+ %% Insert the new timer type and ref
+ Timers_1 =
+ {TimerRefs#{TimerRef => TimeoutType},
+ TimeoutTypes#{TimeoutType => TimerRef}},
+ parse_timers(
+ Timers_1, TimeoutsR, Seen_1, TimeoutEvents)
end
end
end.
@@ -1855,72 +1886,89 @@ parse_timers(
%% belong to timers that were started after the event timer
%% timeout 0 event fired, so they do not cancel the event timer.
%%
-prepend_timeout_events(_NextState, Debug, _S, [], EventsR) ->
- [Debug|EventsR];
+prepend_timeout_events(_P, Debug, _S, [], EventsR) ->
+ {Debug,EventsR};
prepend_timeout_events(
- NextState, Debug, S, [{timeout,_} = TimeoutEvent|TimeoutEvents], []) ->
- prepend_timeout_events(
- NextState,
- sys_debug(Debug, S#state.name, {in,TimeoutEvent,NextState}),
- S, TimeoutEvents, [TimeoutEvent]);
+ P, Debug, S, [{timeout,_} = TimeoutEvent|TimeoutEvents], []) ->
+ %% Prepend this since there are no other events in queue
+ case Debug of
+ ?not_sys_debug ->
+ prepend_timeout_events(
+ P, Debug, S, TimeoutEvents, [TimeoutEvent]);
+ _ ->
+ {State,_Data} = S#state.state_data,
+ Debug_1 =
+ sys_debug(
+ Debug, P#params.name, {in,TimeoutEvent,State}),
+ prepend_timeout_events(
+ P, Debug_1, S, TimeoutEvents, [TimeoutEvent])
+ end;
prepend_timeout_events(
- NextState, Debug, S, [{timeout,_}|TimeoutEvents], EventsR) ->
+ P, Debug, S, [{timeout,_}|TimeoutEvents], EventsR) ->
%% Ignore since there are other events in queue
%% so they have cancelled the event timeout 0.
- prepend_timeout_events(NextState, Debug, S, TimeoutEvents, EventsR);
+ prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR);
prepend_timeout_events(
- NextState, Debug, S, [TimeoutEvent|TimeoutEvents], EventsR) ->
+ P, Debug, S, [TimeoutEvent|TimeoutEvents], EventsR) ->
%% Just prepend all others
- prepend_timeout_events(
- NextState,
- sys_debug(Debug, S#state.name, {in,TimeoutEvent,NextState}),
- S, TimeoutEvents, [TimeoutEvent|EventsR]).
+ case Debug of
+ ?not_sys_debug ->
+ prepend_timeout_events(
+ P, Debug, S, TimeoutEvents, [TimeoutEvent|EventsR]);
+ _ ->
+ {State,_Data} = S#state.state_data,
+ Debug_1 =
+ sys_debug(
+ Debug, P#params.name, {in,TimeoutEvent,State}),
+ prepend_timeout_events(
+ P, Debug_1, S, TimeoutEvents, [TimeoutEvent|EventsR])
+ end.
%%---------------------------------------------------------------------------
%% Server helpers
-reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, Replies) ->
+reply_then_terminate(Class, Reason, Stacktrace, P, Debug, S, Q, Replies) ->
do_reply_then_terminate(
- Class, Reason, Stacktrace, Debug, S, Q, listify(Replies)).
+ Class, Reason, Stacktrace, P, Debug, S, Q, listify(Replies)).
%%
do_reply_then_terminate(
- Class, Reason, Stacktrace, Debug, S, Q, []) ->
- terminate(Class, Reason, Stacktrace, Debug, S, Q);
+ Class, Reason, Stacktrace, P, Debug, S, Q, []) ->
+ terminate(Class, Reason, Stacktrace, P, Debug, S, Q);
do_reply_then_terminate(
- Class, Reason, Stacktrace, Debug, S, Q, [R|Rs]) ->
+ Class, Reason, Stacktrace, P, Debug, S, Q, [R|Rs]) ->
case R of
{reply,From,Reply} ->
case from(From) of
true ->
reply(From, Reply),
- NewDebug =
+ Debug_1 =
?sys_debug(
Debug,
- S#state.name,
+ P#params.name,
{out,Reply,From}),
do_reply_then_terminate(
- Class, Reason, Stacktrace, NewDebug, S, Q, Rs);
+ Class, Reason, Stacktrace, P, Debug_1, S, Q, Rs);
false ->
terminate(
error,
{bad_reply_action_from_state_function,R},
?STACKTRACE(),
- Debug, S, Q)
+ P, Debug, S, Q)
end;
_ ->
terminate(
error,
{bad_reply_action_from_state_function,R},
?STACKTRACE(),
- Debug, S, Q)
+ P, Debug, S, Q)
end.
terminate(
- Class, Reason, Stacktrace, Debug,
- #state{module = Module, state = State, data = Data} = S,
- Q) ->
+ Class, Reason, Stacktrace,
+ #params{module = Module} = P, Debug,
+ #state{state_data = {State,Data}} = S, Q) ->
case erlang:function_exported(Module, terminate, 3) of
true ->
try Module:terminate(Reason, State, Data) of
@@ -1928,7 +1976,7 @@ terminate(
catch
_ -> ok;
C:R:ST ->
- error_info(C, R, ST, Debug, S, Q),
+ error_info(C, R, ST, Debug, P, S, Q),
erlang:raise(C, R, ST)
end;
false ->
@@ -1937,13 +1985,13 @@ terminate(
_ =
case Reason of
normal ->
- terminate_sys_debug(Debug, S, State, Reason);
+ terminate_sys_debug(Debug, P, State, Reason);
shutdown ->
- terminate_sys_debug(Debug, S, State, Reason);
+ terminate_sys_debug(Debug, P, State, Reason);
{shutdown,_} ->
- terminate_sys_debug(Debug, S, State, Reason);
+ terminate_sys_debug(Debug, P, State, Reason);
_ ->
- error_info(Class, Reason, Stacktrace, Debug, S, Q)
+ error_info(Class, Reason, Stacktrace, Debug, P, S, Q)
end,
case Stacktrace of
[] ->
@@ -1952,26 +2000,26 @@ terminate(
erlang:raise(Class, Reason, Stacktrace)
end.
-terminate_sys_debug(Debug, S, State, Reason) ->
- ?sys_debug(Debug, S#state.name, {terminate,Reason,State}).
+terminate_sys_debug(Debug, P, State, Reason) ->
+ ?sys_debug(Debug, P#params.name, {terminate,Reason,State}).
error_info(
Class, Reason, Stacktrace, Debug,
- #state{
+ #params{
name = Name,
callback_mode = CallbackMode,
- state_enter = StateEnter,
- postponed = P} = S,
+ state_enter = StateEnter} = P,
+ #state{postponed = Postponed} = S,
Q) ->
Log = sys:get_log(Debug),
?LOG_ERROR(#{label=>{gen_statem,terminate},
name=>Name,
queue=>Q,
- postponed=>P,
+ postponed=>Postponed,
callback_mode=>CallbackMode,
state_enter=>StateEnter,
- state=>format_status(terminate, get(), S),
+ state=>format_status(terminate, get(), P, S),
log=>Log,
reason=>{Class,Reason,Stacktrace},
client_info=>client_stacktrace(Q)},
@@ -2006,7 +2054,7 @@ client_stacktrace([_|_]) ->
format_log(#{label:={gen_statem,terminate},
name:=Name,
queue:=Q,
- postponed:=P,
+ postponed:=Postponed,
callback_mode:=CallbackMode,
state_enter:=StateEnter,
state:=FmtData,
@@ -2057,7 +2105,7 @@ format_log(#{label:={gen_statem,terminate},
[_,_|_] -> "** Queued = ~tp~n";
_ -> ""
end ++
- case P of
+ case Postponed of
[] -> "";
_ -> "** Postponed = ~tp~n"
end ++
@@ -2081,9 +2129,9 @@ format_log(#{label:={gen_statem,terminate},
[_|[_|_] = Events] -> [error_logger:limit_term(Events)];
_ -> []
end ++
- case P of
+ case Postponed of
[] -> [];
- _ -> [error_logger:limit_term(P)]
+ _ -> [error_logger:limit_term(Postponed)]
end ++
case FixedStacktrace of
[] -> [];
@@ -2109,7 +2157,8 @@ format_client_log({_Pid,{Name,Stacktrace}}) ->
%% Call Module:format_status/2 or return a default value
format_status(
Opt, PDict,
- #state{module = Module, state = State, data = Data}) ->
+ #params{module = Module},
+ #state{state_data = {State,Data} = State_Data}) ->
case erlang:function_exported(Module, format_status, 2) of
true ->
try Module:format_status(Opt, [PDict,State,Data])
@@ -2117,21 +2166,21 @@ format_status(
Result -> Result;
_:_ ->
format_status_default(
- Opt, State,
- atom_to_list(Module) ++ ":format_status/2 crashed")
+ Opt,
+ {State,
+ atom_to_list(Module) ++ ":format_status/2 crashed"})
end;
false ->
- format_status_default(Opt, State, Data)
+ format_status_default(Opt, State_Data)
end.
-%% The default Module:format_status/2
-format_status_default(Opt, State, Data) ->
- StateData = {State,Data},
+%% The default Module:format_status/3
+format_status_default(Opt, State_Data) ->
case Opt of
terminate ->
- StateData;
+ State_Data;
_ ->
- [{data,[{"State",StateData}]}]
+ [{data,[{"State",State_Data}]}]
end.
-compile({inline, [listify/1]}).
@@ -2140,24 +2189,41 @@ listify(Item) when is_list(Item) ->
listify(Item) ->
[Item].
+
+-define(cancel_timer(TimerRef),
+ case erlang:cancel_timer(TimerRef) of
+ false ->
+ %% No timer found and we have not seen the timeout message
+ receive
+ {timeout,(TimerRef),_} ->
+ ok
+ end;
+ _ ->
+ %% Timer was running
+ ok
+ end).
+
+-compile({inline, [cancel_timer/1]}).
+cancel_timer(TimerRef) ->
+ ?cancel_timer(TimerRef).
+
%% Cancel timer if running, otherwise no op
%%
-%% This is an asynchronous cancel so the timer is not really cancelled
-%% until we get a cancel_timer msg i.e {cancel_timer,TimerRef,_}.
-%% In the mean time we might get a timeout message.
-%%
-%% Remove the timer from TimerTypes.
-%% When we get the cancel_timer msg we remove it from TimerRefs.
+%% Remove the timer from Timers
-compile({inline, [cancel_timer_by_type/2]}).
-cancel_timer_by_type(TimerType, {TimerTypes,CancelTimers} = TT_CT) ->
- case TimerTypes of
- #{TimerType := TimerRef} ->
- ok = erlang:cancel_timer(TimerRef, [{async,true}]),
- {maps:remove(TimerType, TimerTypes),CancelTimers + 1};
- #{} ->
- TT_CT
+cancel_timer_by_type(TimeoutType, {TimerRefs,TimeoutTypes} = Timers) ->
+ case TimeoutTypes of
+ #{TimeoutType := TimerRef} ->
+ ?cancel_timer(TimerRef),
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimeoutType, TimeoutTypes)};
+ #{} ->
+ Timers
end.
--compile({inline, [cancel_timer/1]}).
-cancel_timer(TimerRef) ->
- ok = erlang:cancel_timer(TimerRef, [{async,true}]).
+-compile({inline, [cancel_timer_by_ref_and_type/4]}).
+cancel_timer_by_ref_and_type(
+ TimerRef, TimeoutType, TimerRefs, TimeoutTypes) ->
+ ?cancel_timer(TimerRef),
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimeoutType, TimeoutTypes)}.