From a5c1cd352976d58577516c6d7b1db45c30f908ab Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Wed, 19 Dec 2018 14:24:28 +0100 Subject: Modify generic_statem_transit benchmark Instead of just changing states; also start a state_timeout that gets cancelled by the state change. I think it is important to have efficient timer handling. --- lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib/stdlib') diff --git a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl index cfeb974abd..b106619568 100644 --- a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl +++ b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl @@ -52,7 +52,9 @@ init(Data) -> state1({call, From}, {reply, M}, Data) -> {keep_state, Data, {reply, From, M}}; state1({call, From}, {transit, M}, Data) -> - {next_state, state2, Data, {reply, From, M}}. + {next_state, state2, Data, + [{reply, From, M},{state_timeout,5000,5000}]}. state2({call, From}, {transit, M}, Data) -> - {next_state, state1, Data, {reply, From, M}}. + {next_state, state1, Data, + [{reply, From, M},{state_timeout,5000,5000}]}. -- cgit v1.2.3 From 63a7892d0fbcd6463a21ba54622e61cf4a935b64 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Wed, 28 Nov 2018 15:59:00 +0100 Subject: 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 --- lib/stdlib/src/gen_statem.erl | 1726 +++++++++++++++++++++-------------------- 1 file changed, 896 insertions(+), 830 deletions(-) (limited to 'lib/stdlib') 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. @@ -373,6 +373,14 @@ timeout_event_type(Type) -> end. +-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))). @@ -380,44 +388,48 @@ timeout_event_type(Type) -> -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)}. -- cgit v1.2.3 From 1aea2e0e3756c79c5802041befcdc10f80abbcc9 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 15 Jan 2019 09:21:23 +0100 Subject: Handle #trans_opts{} fields in parse_actions() arguments --- lib/stdlib/src/gen_statem.erl | 168 ++++++++++++++++++++++-------------------- 1 file changed, 89 insertions(+), 79 deletions(-) (limited to 'lib/stdlib') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 7645f62856..268c4b14e6 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -420,6 +420,8 @@ timeout_event_type(Type) -> hibernate = false :: boolean() }). +%% Keep in sync with parse_actions/5 that constructs this record +%% from all fields -record(trans_opts, {hibernate = false :: boolean(), postpone = false :: boolean(), @@ -1019,7 +1021,8 @@ loop_event_handler( %% -define(state_call(TransOpts), ((TransOpts) =:= trans_opts)). -%% Convert state call marker to valid #trans_opts{} record +%% Convert state call marker to valid #trans_opts{} record, +%% to run when skipping running parse_actions/5 %% -compile({inline, [trans_opts/1]}). trans_opts(trans_opts) -> @@ -1030,10 +1033,10 @@ trans_opts(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) -> +-compile({inline, ['#trans_opts.hibernate'/1]}). +'#trans_opts.hibernate'(trans_opts) -> (#trans_opts{})#trans_opts.hibernate; -trans_opts__hibernate(#trans_opts{hibernate = Hibernate}) -> +'#trans_opts.hibernate'(#trans_opts{hibernate = Hibernate}) -> Hibernate. @@ -1178,7 +1181,7 @@ loop_state_callback_result( ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = trans_opts__hibernate(TransOpts)}, + hibernate = '#trans_opts.hibernate'(TransOpts)}, Q); {next_state,State,NewData,Actions} -> loop_actions( @@ -1196,7 +1199,7 @@ loop_state_callback_result( ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = trans_opts__hibernate(TransOpts)}, + hibernate = '#trans_opts.hibernate'(TransOpts)}, Q); %% {keep_state,NewData} -> @@ -1240,21 +1243,21 @@ loop_state_callback_result( exit, normal, ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = trans_opts__hibernate(TransOpts)}, + hibernate = '#trans_opts.hibernate'(TransOpts)}, Q); {stop,Reason} -> terminate( exit, Reason, ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = trans_opts__hibernate(TransOpts)}, + hibernate = '#trans_opts.hibernate'(TransOpts)}, Q); {stop,Reason,NewData} -> terminate( exit, Reason, ?STACKTRACE(), P, Debug, S#state{ state_data = {State,NewData}, - hibernate = trans_opts__hibernate(TransOpts)}, + hibernate = '#trans_opts.hibernate'(TransOpts)}, Q); %% {stop_and_reply,Reason,Replies} -> @@ -1262,14 +1265,14 @@ loop_state_callback_result( exit, Reason, ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = trans_opts__hibernate(TransOpts)}, + hibernate = '#trans_opts.hibernate'(TransOpts)}, Q, Replies); {stop_and_reply,Reason,Replies,NewData} -> reply_then_terminate( exit, Reason, ?STACKTRACE(), P, Debug, S#state{ state_data = {State,NewData}, - hibernate = trans_opts__hibernate(TransOpts)}, + hibernate = '#trans_opts.hibernate'(TransOpts)}, Q, Replies); %% _ -> @@ -1279,7 +1282,7 @@ loop_state_callback_result( ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = trans_opts__hibernate(TransOpts)}, + hibernate = '#trans_opts.hibernate'(TransOpts)}, Q) end. @@ -1312,7 +1315,7 @@ loop_actions_list( Q, NextState_NewData, TransOpts, Actions, CallEnter) -> %% - case parse_actions(P, Debug, S, TransOpts, Actions) of + case parse_actions(P, Debug, S, Actions, TransOpts) of {Debug_1,TransOpts_1} when StateEnter, CallEnter -> loop_state_enter( @@ -1325,72 +1328,74 @@ loop_actions_list( Class, Reason, Stacktrace, P, Debug_1, S#state{ state_data = NextState_NewData, - hibernate = trans_opts__hibernate(TransOpts)}, + hibernate = '#trans_opts.hibernate'(TransOpts)}, Q) end. - - %%--------------------------------------------------------------------------- %% The server loop continues after parse_actions -parse_actions(P, Debug, S, TransOpts, Actions) -> +parse_actions(P, Debug, S, Actions, TransOpts) -> if ?state_call(TransOpts) -> - %% Inlining of trans_opts/1 under the condition - %% that ?state_call(TransOpts) is 'true' + %% Inlining of trans_opts/1 for when + %% ?state_call(TransOpts) is 'true' %% + Hibernate = (#trans_opts{})#trans_opts.hibernate, + Postpone = (#trans_opts{})#trans_opts.postpone, 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); + P, Debug, S, Actions, true, + Hibernate, Postpone, TimeoutsR, NextEventsR); true -> - TimeoutsR = TransOpts#trans_opts.timeouts_r, - NextEventsR = TransOpts#trans_opts.next_events_r, + #trans_opts{ + hibernate = Hibernate, + postpone = Postpone, + timeouts_r = TimeoutsR, + next_events_r = NextEventsR} = TransOpts, parse_actions( - P, Debug, S, TransOpts, Actions, false, - TimeoutsR, NextEventsR) + P, Debug, S, Actions, false, + Hibernate, Postpone, TimeoutsR, NextEventsR) end. %% parse_actions( - _P, Debug, _S, TransOpts, [], _StateCall, TimeoutsR, NextEventsR) -> + _P, Debug, _S, [], _StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR) -> {Debug, - TransOpts#trans_opts{ - timeouts_r = TimeoutsR, - next_events_r = NextEventsR}}; + #trans_opts{ + hibernate = Hibernate, + postpone = Postpone, + timeouts_r = TimeoutsR, + next_events_r = NextEventsR}}; parse_actions( - P, Debug, S, TransOpts, [Action|Actions], StateCall, - TimeoutsR, NextEventsR) -> + P, Debug, S, [Action|Actions], StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR) -> case Action of %% Actual actions {reply,From,Reply} -> parse_actions_reply( - P, Debug, S, TransOpts, Actions, StateCall, - TimeoutsR, NextEventsR, From, Reply); + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR, From, Reply); %% %% Actions that set options - {hibernate,Hibernate} when is_boolean(Hibernate) -> - TransOpts_1 = TransOpts#trans_opts{hibernate = Hibernate}, + {hibernate,Hibernate_1} when is_boolean(Hibernate_1) -> parse_actions( - P, Debug, S, TransOpts_1, Actions, StateCall, - TimeoutsR, NextEventsR); + P, Debug, S, Actions, StateCall, + Hibernate_1, Postpone, TimeoutsR, NextEventsR); hibernate -> - TransOpts_1 = TransOpts#trans_opts{hibernate = true}, parse_actions( - P, Debug, S, TransOpts_1, Actions, StateCall, - TimeoutsR, NextEventsR); + P, Debug, S, Actions, StateCall, + true, Postpone, TimeoutsR, NextEventsR); %% - {postpone,Postpone} when not Postpone orelse StateCall -> - TransOpts_1 = TransOpts#trans_opts{postpone = Postpone}, + {postpone,Postpone_1} when not Postpone_1 orelse StateCall -> parse_actions( - P, Debug, S, TransOpts_1, Actions, StateCall, - TimeoutsR, NextEventsR); + P, Debug, S, Actions, StateCall, + Hibernate, Postpone_1, TimeoutsR, NextEventsR); postpone when StateCall -> - TransOpts_1 = TransOpts#trans_opts{postpone = true}, parse_actions( - P, Debug, S, TransOpts_1, Actions, StateCall, - TimeoutsR, NextEventsR); + P, Debug, S, Actions, StateCall, + Hibernate, true, TimeoutsR, NextEventsR); postpone -> [error, {bad_state_enter_action_from_state_function,Action}, @@ -1399,18 +1404,18 @@ parse_actions( %% {next_event,Type,Content} -> parse_actions_next_event( - P, Debug, S, TransOpts, Actions, StateCall, - TimeoutsR, NextEventsR, Type, Content); + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR, Type, Content); %% - _ -> + Timeout -> parse_actions_timeout( - P, Debug, S, TransOpts, Actions, StateCall, - TimeoutsR, NextEventsR, Action) + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR, Timeout) end. parse_actions_reply( - P, Debug, S, TransOpts, Actions, StateCall, - TimeoutsR, NextEventsR, From, Reply) -> + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR, From, Reply) -> %% case from(From) of true -> @@ -1420,8 +1425,8 @@ parse_actions_reply( reply(From, Reply), Debug_1 = ?sys_debug(Debug, P#params.name, {out,Reply,From}), parse_actions( - P, Debug_1, S, TransOpts, Actions, StateCall, - TimeoutsR, NextEventsR); + P, Debug_1, S, Actions, StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR); false -> [error, {bad_action_from_state_function,{reply,From,Reply}}, @@ -1429,24 +1434,26 @@ parse_actions_reply( end. parse_actions_next_event( - P, Debug, S, TransOpts, Actions, StateCall, - TimeoutsR, NextEventsR, Type, Content) -> + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR, Type, Content) -> case event_type(Type) of true when StateCall -> NextEvent = {Type,Content}, case Debug of ?not_sys_debug -> parse_actions( - P, Debug, S, TransOpts, Actions, StateCall, - TimeoutsR, [NextEvent|NextEventsR]); + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, 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]) + P, Debug_1, S, Actions, StateCall, + Hibernate, Postpone, TimeoutsR, + [NextEvent|NextEventsR]) end; _ -> [error, @@ -1457,7 +1464,8 @@ parse_actions_next_event( end. parse_actions_timeout( - P, Debug, S, TransOpts, Actions, StateCall, TimeoutsR, NextEventsR, + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR, {TimeoutType,Time,TimeoutMsg,TimeoutOpts} = AbsoluteTimeout) -> %% case timeout_event_type(TimeoutType) of @@ -1467,33 +1475,33 @@ parse_actions_timeout( [] when ?relative_timeout(Time) -> RelativeTimeout = {TimeoutType,Time,TimeoutMsg}, parse_actions( - P, Debug, S, - TransOpts, Actions, StateCall, + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, [RelativeTimeout|TimeoutsR], NextEventsR); [{abs,true}] when ?absolute_timeout(Time) -> parse_actions( - P, Debug, S, - TransOpts, Actions, StateCall, + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, [AbsoluteTimeout|TimeoutsR], NextEventsR); [{abs,false}] when ?relative_timeout(Time) -> RelativeTimeout = {TimeoutType,Time,TimeoutMsg}, parse_actions( - P, Debug, S, - TransOpts, Actions, StateCall, + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, [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, + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, [AbsoluteTimeout|TimeoutsR], NextEventsR); false when ?relative_timeout(Time) -> RelativeTimeout = {TimeoutType,Time,TimeoutMsg}, parse_actions( - P, Debug, S, - TransOpts, Actions, StateCall, + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, [RelativeTimeout|TimeoutsR], NextEventsR); badarg -> [error, @@ -1510,13 +1518,15 @@ parse_actions_timeout( Debug] end; parse_actions_timeout( - P, Debug, S, TransOpts, Actions, StateCall, TimeoutsR, NextEventsR, + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR, {TimeoutType,Time,_} = RelativeTimeout) -> %% case timeout_event_type(TimeoutType) of true when ?relative_timeout(Time) -> parse_actions( - P, Debug, S, TransOpts, Actions, StateCall, + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, [RelativeTimeout|TimeoutsR], NextEventsR); _ -> [error, @@ -1525,14 +1535,16 @@ parse_actions_timeout( Debug] end; parse_actions_timeout( - P, Debug, S, TransOpts, Actions, StateCall, TimeoutsR, NextEventsR, + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, TimeoutsR, NextEventsR, Time) -> %% if ?relative_timeout(Time) -> RelativeTimeout = {timeout,Time,Time}, parse_actions( - P, Debug, S, TransOpts, Actions, StateCall, + P, Debug, S, Actions, StateCall, + Hibernate, Postpone, [RelativeTimeout|TimeoutsR], NextEventsR); true -> [error, @@ -1544,8 +1556,6 @@ parse_actions_timeout( %%--------------------------------------------------------------------------- %% Here the server loop continues - - %% Do the state transition loop_state_transition( P, Debug, #state{state_data = {State,_Data}, postponed = Postponed} = S, -- cgit v1.2.3 From dfb91c491f1bd21b8c05ed38e5addedd6ddf7292 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 15 Jan 2019 13:36:42 +0100 Subject: Move out helper functions to engine loop This strategy improves speed benchmarks and reduces the amount of garbage that is produced. More: * Handle transition options as loop function arguments instead of in a record. * Shorten the state_enter loop and make it more efficient by making it more part of the loop and less a loop restart. * Simplify the internal timeout action handling somewhat. --- lib/stdlib/src/gen_statem.erl | 886 ++++++++++++++++++++++-------------------- 1 file changed, 473 insertions(+), 413 deletions(-) (limited to 'lib/stdlib') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 268c4b14e6..819efa57b8 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -420,19 +420,6 @@ timeout_event_type(Type) -> hibernate = false :: boolean() }). -%% Keep in sync with parse_actions/5 that constructs this record -%% from all fields --record(trans_opts, - {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 @@ -692,11 +679,14 @@ enter( name = Name, hibernate_after = HibernateAfterTimeout}, 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... + P, Debug_1, S, Q, {State,Data}, + %% Tunneling Actions through CallbackEvent here... + %% Special path to go to action handling, after first + %% finding out the callback mode. CallbackEvent is + %% a 2-tuple and Actions a list, which achieves this distinction. + Actions_1). %%%========================================================================== %%% gen callbacks @@ -1012,89 +1002,46 @@ loop_event_handler( %% restored when looping back to loop/3 or loop_event/5. %% 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, -%% to run when skipping running parse_actions/5 -%% --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_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(P, Debug, S, Q, State_Data, Event). loop_callback_mode( #params{callback_mode = undefined} = P, Debug, S, - Q, State_Data, TransOpts, CallbackEvent) -> + Q, State_Data, CallbackEvent) -> %% Module = P#params.module, try Module:callback_mode() of CallbackMode -> loop_callback_mode_result( P, Debug, S, - Q, State_Data, TransOpts, CallbackEvent, + Q, State_Data, CallbackEvent, CallbackMode, listify(CallbackMode), undefined, false) catch CallbackMode -> loop_callback_mode_result( P, Debug, S, - Q, State_Data, TransOpts, CallbackEvent, + Q, State_Data, CallbackEvent, CallbackMode, listify(CallbackMode), undefined, false); Class:Reason:Stacktrace -> terminate( 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(P, Debug, S, Q, State_Data, CallbackEvent) -> + loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent). loop_callback_mode_result( - P, Debug, S, - Q, State_Data, TransOpts, CallbackEvent, + P, Debug, S, Q, State_Data, CallbackEvent, CallbackMode, [H|T], NewCallbackMode, NewStateEnter) -> %% case callback_mode(H) of true -> loop_callback_mode_result( - P, Debug, S, - Q, State_Data, TransOpts, CallbackEvent, + P, Debug, S, Q, State_Data, CallbackEvent, CallbackMode, T, H, NewStateEnter); false -> case state_enter(H) of true -> loop_callback_mode_result( - P, Debug, S, - Q, State_Data, TransOpts, CallbackEvent, + P, Debug, S, Q, State_Data, CallbackEvent, CallbackMode, T, NewCallbackMode, true); false -> terminate( @@ -1105,8 +1052,7 @@ loop_callback_mode_result( end end; loop_callback_mode_result( - P, Debug, S, - Q, State_Data, TransOpts, CallbackEvent, + P, Debug, S, Q, State_Data, CallbackEvent, CallbackMode, [], NewCallbackMode, NewStateEnter) -> %% case NewCallbackMode of @@ -1122,14 +1068,39 @@ loop_callback_mode_result( callback_mode = NewCallbackMode, state_enter = NewStateEnter}, loop_state_callback( - P_1, Debug, S, - Q, State_Data, TransOpts, CallbackEvent) + P_1, Debug, S, Q, State_Data, CallbackEvent) end. + +%% Make a state enter call to the state function +loop_state_enter( + P, Debug, #state{state_data = {PrevState,_PrevData}} = S, + Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone) -> + %% + StateCall = false, + CallbackEvent = {enter,PrevState}, + loop_state_callback( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, CallbackEvent). + +loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent) -> + NextEventsR = [], + Hibernate = false, + TimeoutsR = [], + Postpone = false, + StateCall = true, + loop_state_callback( + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, CallbackEvent). +%% loop_state_callback( #params{callback_mode = CallbackMode, module = Module} = P, - Debug, S, - Q, {State,Data} = State_Data, TransOpts, {Type,Content}) -> + Debug, S, Q, {State,Data} = State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, {Type,Content}) -> try case CallbackMode of state_functions -> @@ -1140,40 +1111,50 @@ loop_state_callback( of Result -> loop_state_callback_result( - P, Debug, S, - Q, State_Data, TransOpts, Result) + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, Result) catch Result -> loop_state_callback_result( - P, Debug, S, - Q, State_Data, TransOpts, Result); + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, Result); Class:Reason:Stacktrace -> terminate(Class, Reason, Stacktrace, P, Debug, S, Q) end; loop_state_callback( - P, Debug, S, Q, State_Data, TransOpts, Actions) -> + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, Actions) when is_list(Actions) -> %% Tunneled actions from enter/8 CallEnter = true, loop_actions_list( - P, Debug, S, Q, State_Data, TransOpts, Actions, CallEnter). + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions). %% Process the result from the state function. %% When TransOpts =:= trans_opts it was a state function call, %% otherwise it is an option tuple and it was a state enter call. %% loop_state_callback_result( - P, Debug, S, Q, {State,_Data} = State_Data, TransOpts, Result) -> + P, Debug, S, Q, {State,_Data} = State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, Result) -> %% case Result of {next_state,State,NewData} -> loop_actions( - P, Debug, S, Q, {State,NewData}, TransOpts, - [], false); + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false); {next_state,NextState,NewData} - when ?state_call(TransOpts) -> + when StateCall -> loop_actions( - P, Debug, S, Q, {NextState,NewData}, TransOpts, - [], true); + P, Debug, S, Q, {NextState,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true); {next_state,_NextState,_NewData} -> terminate( error, @@ -1181,17 +1162,19 @@ loop_state_callback_result( ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = '#trans_opts.hibernate'(TransOpts)}, + hibernate = Hibernate}, Q); {next_state,State,NewData,Actions} -> loop_actions( - P, Debug, S, Q, {State,NewData}, TransOpts, - Actions, false); + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false, StateCall, Actions); {next_state,NextState,NewData,Actions} - when ?state_call(TransOpts) -> + when StateCall -> loop_actions( - P, Debug, S, Q, {NextState,NewData}, TransOpts, - Actions, true); + P, Debug, S, Q, {NextState,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true, StateCall, Actions); {next_state,_NextState,_NewData,_Actions} -> terminate( error, @@ -1199,65 +1182,73 @@ loop_state_callback_result( ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = '#trans_opts.hibernate'(TransOpts)}, + hibernate = Hibernate}, Q); %% {keep_state,NewData} -> loop_actions( - P, Debug, S, Q, {State,NewData}, TransOpts, - [], false); + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false); {keep_state,NewData,Actions} -> loop_actions( - P, Debug, S, Q, {State,NewData}, TransOpts, - Actions, false); + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false, StateCall, Actions); %% keep_state_and_data -> loop_actions( - P, Debug, S, Q, State_Data, TransOpts, - [], false); + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false); {keep_state_and_data,Actions} -> loop_actions( - P, Debug, S, Q, State_Data, TransOpts, - Actions, false); + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false, StateCall, Actions); %% {repeat_state,NewData} -> loop_actions( - P, Debug, S, Q, {State,NewData}, TransOpts, - [], true); + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true); {repeat_state,NewData,Actions} -> loop_actions( - P, Debug, S, Q, {State,NewData}, TransOpts, - Actions, true); + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true, StateCall, Actions); %% repeat_state_and_data -> loop_actions( - P, Debug, S, Q, State_Data, TransOpts, - [], true); + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true); {repeat_state_and_data,Actions} -> loop_actions( - P, Debug, S, Q, State_Data, TransOpts, - Actions, true); + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true, StateCall, Actions); %% stop -> terminate( exit, normal, ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = '#trans_opts.hibernate'(TransOpts)}, + hibernate = Hibernate}, Q); {stop,Reason} -> terminate( exit, Reason, ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = '#trans_opts.hibernate'(TransOpts)}, + hibernate = Hibernate}, Q); {stop,Reason,NewData} -> terminate( exit, Reason, ?STACKTRACE(), P, Debug, S#state{ state_data = {State,NewData}, - hibernate = '#trans_opts.hibernate'(TransOpts)}, + hibernate = Hibernate}, Q); %% {stop_and_reply,Reason,Replies} -> @@ -1265,14 +1256,14 @@ loop_state_callback_result( exit, Reason, ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = '#trans_opts.hibernate'(TransOpts)}, + hibernate = Hibernate}, Q, Replies); {stop_and_reply,Reason,Replies,NewData} -> reply_then_terminate( exit, Reason, ?STACKTRACE(), P, Debug, S#state{ state_data = {State,NewData}, - hibernate = '#trans_opts.hibernate'(TransOpts)}, + hibernate = Hibernate}, Q, Replies); %% _ -> @@ -1282,140 +1273,127 @@ loop_state_callback_result( ?STACKTRACE(), P, Debug, S#state{ state_data = State_Data, - hibernate = '#trans_opts.hibernate'(TransOpts)}, + hibernate = Hibernate}, Q) end. %% Ensure that Actions are a list loop_actions( - P, Debug, S, Q, NextState_NewData, TransOpts, - [], CallEnter) -> + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, _StateCall, []) -> + loop_actions( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter); +loop_actions( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions) -> + %% + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, listify(Actions)). +%% +%% Shortcut for no actions +-compile({inline, [loop_actions/10]}). +loop_actions( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + 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)); + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone); 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_actions_list( - P, Debug, S, Q, NextState_NewData, TransOpts, - listify(Actions), CallEnter). + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone) + end. --compile({inline, loop_actions_list/8}). -%% Process actions from the state function loop_actions_list( - #params{state_enter = StateEnter} = P, Debug, S, - Q, NextState_NewData, TransOpts, - Actions, CallEnter) -> + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, _StateCall, []) -> %% - case parse_actions(P, Debug, S, Actions, TransOpts) of - {Debug_1,TransOpts_1} - when StateEnter, CallEnter -> + case P#params.state_enter of + true when CallEnter -> loop_state_enter( - P, Debug_1, S, Q, NextState_NewData, TransOpts_1); - {Debug_1,TransOpts_1} -> + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone); + _ -> loop_state_transition( - P, Debug_1, S, Q, NextState_NewData, TransOpts_1); - [Class,Reason,Stacktrace,Debug_1] -> - terminate( - Class, Reason, Stacktrace, P, Debug_1, - S#state{ - state_data = NextState_NewData, - hibernate = '#trans_opts.hibernate'(TransOpts)}, - Q) - end. - -%%--------------------------------------------------------------------------- -%% The server loop continues after parse_actions - -parse_actions(P, Debug, S, Actions, TransOpts) -> - if - ?state_call(TransOpts) -> - %% Inlining of trans_opts/1 for when - %% ?state_call(TransOpts) is 'true' - %% - Hibernate = (#trans_opts{})#trans_opts.hibernate, - Postpone = (#trans_opts{})#trans_opts.postpone, - TimeoutsR = (#trans_opts{})#trans_opts.timeouts_r, - NextEventsR = (#trans_opts{})#trans_opts.next_events_r, - parse_actions( - P, Debug, S, Actions, true, - Hibernate, Postpone, TimeoutsR, NextEventsR); - true -> - #trans_opts{ - hibernate = Hibernate, - postpone = Postpone, - timeouts_r = TimeoutsR, - next_events_r = NextEventsR} = TransOpts, - parse_actions( - P, Debug, S, Actions, false, - Hibernate, Postpone, TimeoutsR, NextEventsR) - end. -%% -parse_actions( - _P, Debug, _S, [], _StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR) -> - {Debug, - #trans_opts{ - hibernate = Hibernate, - postpone = Postpone, - timeouts_r = TimeoutsR, - next_events_r = NextEventsR}}; -parse_actions( - P, Debug, S, [Action|Actions], StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR) -> + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone) + end; +loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, [Action|Actions]) -> + %% case Action of %% Actual actions {reply,From,Reply} -> - parse_actions_reply( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR, From, Reply); + loop_actions_reply( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, + From, Reply); %% %% Actions that set options {hibernate,Hibernate_1} when is_boolean(Hibernate_1) -> - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate_1, Postpone, TimeoutsR, NextEventsR); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate_1, TimeoutsR, Postpone, + CallEnter, StateCall, Actions); hibernate -> - parse_actions( - P, Debug, S, Actions, StateCall, - true, Postpone, TimeoutsR, NextEventsR); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, true, TimeoutsR, Postpone, + CallEnter, StateCall, Actions); %% {postpone,Postpone_1} when not Postpone_1 orelse StateCall -> - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone_1, TimeoutsR, NextEventsR); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone_1, + CallEnter, StateCall, Actions); postpone when StateCall -> - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate, true, TimeoutsR, NextEventsR); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, true, + CallEnter, StateCall, Actions); postpone -> - [error, - {bad_state_enter_action_from_state_function,Action}, - ?STACKTRACE(), - Debug]; + terminate( + error, + {bad_state_enter_action_from_state_function,Action}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q); %% {next_event,Type,Content} -> - parse_actions_next_event( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR, Type, Content); + loop_actions_next_event( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, Type, Content); %% Timeout -> - parse_actions_timeout( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR, Timeout) + loop_actions_timeout( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, Timeout) end. -parse_actions_reply( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR, From, Reply) -> +loop_actions_reply( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, + From, Reply) -> %% case from(From) of true -> @@ -1424,49 +1402,63 @@ parse_actions_reply( %% will cause swap out of all live registers anyway reply(From, Reply), Debug_1 = ?sys_debug(Debug, P#params.name, {out,Reply,From}), - parse_actions( - P, Debug_1, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR); + loop_actions_list( + P, Debug_1, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions); false -> - [error, - {bad_action_from_state_function,{reply,From,Reply}}, - ?STACKTRACE(), Debug] + terminate( + error, + {bad_action_from_state_function,{reply,From,Reply}}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end. -parse_actions_next_event( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR, Type, Content) -> +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 -> - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, - [NextEvent|NextEventsR]); + 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}), - parse_actions( - P, Debug_1, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, - [NextEvent|NextEventsR]) + loop_actions_list( + P, Debug_1, S, Q, NextState_NewData, + [NextEvent|NextEventsR], + Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions) end; _ -> - [error, - {bad_state_enter_action_from_state_function, - {next_event,Type,Content}}, - ?STACKTRACE(), - Debug] + terminate( + error, + {bad_state_enter_action_from_state_function, + {next_event,Type,Content}}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end. -parse_actions_timeout( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR, - {TimeoutType,Time,TimeoutMsg,TimeoutOpts} = AbsoluteTimeout) -> +loop_actions_timeout( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, + {TimeoutType,Time,TimeoutMsg,TimeoutOpts} = Timeout) -> %% case timeout_event_type(TimeoutType) of true -> @@ -1474,93 +1466,115 @@ parse_actions_timeout( %% Optimization cases [] when ?relative_timeout(Time) -> RelativeTimeout = {TimeoutType,Time,TimeoutMsg}, - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, - [RelativeTimeout|TimeoutsR], NextEventsR); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [RelativeTimeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); [{abs,true}] when ?absolute_timeout(Time) -> - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, - [AbsoluteTimeout|TimeoutsR], NextEventsR); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [Timeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); [{abs,false}] when ?relative_timeout(Time) -> RelativeTimeout = {TimeoutType,Time,TimeoutMsg}, - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, - [RelativeTimeout|TimeoutsR], NextEventsR); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [RelativeTimeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); %% Generic case TimeoutOptsList -> case parse_timeout_opts_abs(TimeoutOptsList) of true when ?absolute_timeout(Time) -> - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, - [AbsoluteTimeout|TimeoutsR], NextEventsR); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [Timeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); false when ?relative_timeout(Time) -> - RelativeTimeout = {TimeoutType,Time,TimeoutMsg}, - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, - [RelativeTimeout|TimeoutsR], NextEventsR); + RelativeTimeout = + {TimeoutType,Time,TimeoutMsg}, + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [RelativeTimeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); badarg -> - [error, - {bad_action_from_state_function, - AbsoluteTimeout}, - ?STACKTRACE(), - Debug] + terminate( + error, + {bad_action_from_state_function,Timeout}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end end; false -> - [error, - {bad_action_from_state_function,AbsoluteTimeout}, - ?STACKTRACE(), - Debug] + terminate( + error, + {bad_action_from_state_function,Timeout}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end; -parse_actions_timeout( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR, - {TimeoutType,Time,_} = RelativeTimeout) -> +loop_actions_timeout( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, + {TimeoutType,Time,_} = Timeout) -> %% case timeout_event_type(TimeoutType) of true when ?relative_timeout(Time) -> - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, - [RelativeTimeout|TimeoutsR], NextEventsR); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [Timeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); _ -> - [error, - {bad_action_from_state_function,RelativeTimeout}, - ?STACKTRACE(), - Debug] + terminate( + error, + {bad_action_from_state_function,Timeout}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end; -parse_actions_timeout( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, TimeoutsR, NextEventsR, - Time) -> +loop_actions_timeout( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, Time) -> %% if ?relative_timeout(Time) -> RelativeTimeout = {timeout,Time,Time}, - parse_actions( - P, Debug, S, Actions, StateCall, - Hibernate, Postpone, - [RelativeTimeout|TimeoutsR], NextEventsR); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [RelativeTimeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); true -> - [error, - {bad_action_from_state_function,Time}, - ?STACKTRACE(), - Debug] + terminate( + error, + {bad_action_from_state_function,Time}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end. -%%--------------------------------------------------------------------------- -%% Here the server loop continues - %% Do the state transition +%% loop_state_transition( P, Debug, #state{state_data = {State,_Data}, postponed = Postponed} = S, [Event|Events], {NextState,_NewData} = NextState_NewData, - #trans_opts{postpone = Postpone} = TransOpts) -> + NextEventsR, Hibernate, TimeoutsR, Postpone) -> %% %% All options have been collected and next_events are buffered. %% Do the actual state transition. @@ -1580,11 +1594,13 @@ loop_state_transition( NextState =:= State -> loop_keep_state( P, Debug, S, - Events, NextState_NewData, TransOpts, Postponed_1); + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed_1); true -> loop_state_change( P, Debug, S, - Events, NextState_NewData, TransOpts, Postponed_1) + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed_1) end; _ -> %% With sys_debug @@ -1604,17 +1620,20 @@ loop_state_transition( NextState =:= State -> loop_keep_state( P, Debug_1, S, - Events, NextState_NewData, TransOpts, Postponed_1); + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed_1); true -> loop_state_change( P, Debug_1, S, - Events, NextState_NewData, TransOpts, Postponed_1) + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed_1) end end. loop_keep_state( P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S, - Events, NextState_NewData, TransOpts, Postponed) -> + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed) -> %% %% Cancel event timeout %% @@ -1625,19 +1644,22 @@ loop_keep_state( %% Event timeout active loop_next_events( P, Debug, S, - Events, NextState_NewData, TransOpts, Postponed, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, 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, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, Timers) end. loop_state_change( - P, Debug, S, Events, NextState_NewData, TransOpts, Postponed) -> + P, Debug, S, Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed) -> %% %% Retry postponed events %% @@ -1645,28 +1667,29 @@ loop_state_change( [] -> loop_state_change( P, Debug, S, - Events, - NextState_NewData, TransOpts); + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR); [E1] -> loop_state_change( P, Debug, S, - [E1|Events], - NextState_NewData, TransOpts); + [E1|Events], NextState_NewData, + NextEventsR, Hibernate, TimeoutsR); [E2,E1] -> loop_state_change( P, Debug, S, - [E1,E2|Events], - NextState_NewData, TransOpts); + [E1,E2|Events], NextState_NewData, + NextEventsR, Hibernate, TimeoutsR); _ -> loop_state_change( P, Debug, S, - lists:reverse(Postponed, Events), - NextState_NewData, TransOpts) + lists:reverse(Postponed, Events), NextState_NewData, + NextEventsR, Hibernate, TimeoutsR) end. %% loop_state_change( P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S, - Events, NextState_NewData, TransOpts) -> + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR) -> %% %% Cancel state and event timeout %% @@ -1678,7 +1701,8 @@ loop_state_change( %% State timeout active %% - cancel event timeout too since it is faster than inspecting loop_next_events( - P, Debug, S, Events, NextState_NewData, TransOpts, [], + P, Debug, S, Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, [], cancel_timer_by_type( timeout, cancel_timer_by_ref_and_type( @@ -1687,47 +1711,61 @@ loop_state_change( %% Event timeout active but not state timeout %% - cancel event timeout only loop_next_events( - P, Debug, S, Events, NextState_NewData, TransOpts, [], + P, Debug, S, Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, [], cancel_timer_by_ref_and_type( TimerRef, timeout, TimerRefs, TimeoutTypes)); _ -> %% No state nor event timeout active. loop_next_events( - P, Debug, S, Events, NextState_NewData, TransOpts, [], + P, Debug, S, Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, [], Timers) end. loop_next_events( P, Debug, S, Events, NextState_NewData, - #trans_opts{timeouts_r = []} = TransOpts, - Postponed, Timers) -> + NextEventsR, Hibernate, [], Postponed, + Timers) -> %% %% Optimization when there are no timeout actions %% hence no timeout zero events to append to Events - %% - avoid calling parse_timers/2 + %% - avoid loop_timeouts 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); + hibernate = Hibernate}, + NextEventsR, Events); loop_next_events( P, Debug, S, Events, NextState_NewData, - #trans_opts{timeouts_r = TimeoutsR} = TransOpts, - Postponed, Timers) -> + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers) -> + %% + Seen = #{}, + TimeoutEvents = [], + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents). + +loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, [], Postponed, + Timers, _Seen, TimeoutEvents) -> %% - {Timers_1,TimeoutEvents} = parse_timers(Timers, TimeoutsR), S_1 = S#state{ state_data = NextState_NewData, postponed = Postponed, - timers = Timers_1, - hibernate = TransOpts#trans_opts.hibernate}, - NextEventsR = TransOpts#trans_opts.next_events_r, + timers = Timers, + hibernate = Hibernate}, case TimeoutEvents of [] -> loop_done(P, Debug, S_1, NextEventsR, Events); @@ -1750,6 +1788,112 @@ loop_next_events( P, Debug, S_1, TimeoutEvents, lists:reverse(Events, NextEventsR)) end + end; +loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, [Timeout|TimeoutsR], Postponed, + Timers, Seen, TimeoutEvents) -> + %% + case Timeout of + {TimeoutType,Time,TimeoutMsg} -> + %% Relative timeout + case Seen of + #{TimeoutType := _} -> + %% Type seen before - ignore + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents); + #{} -> + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents, + TimeoutType, Time, TimeoutMsg, []) + end; + {TimeoutType,Time,TimeoutMsg,TimeoutOpts} -> + %% Absolute timeout + case Seen of + #{TimeoutType := _} -> + %% Type seen before - ignore + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents); + #{} -> + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents, + TimeoutType, Time, TimeoutMsg, listify(TimeoutOpts)) + end + end. +%% +loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents, + TimeoutType, Time, TimeoutMsg, TimeoutOpts) -> + %% + case Time of + infinity -> + %% Cancel any running timer + Timers_1 = cancel_timer_by_type(TimeoutType, Timers), + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers_1, Seen#{TimeoutType => true}, TimeoutEvents); + 0 when TimeoutOpts =:= [] -> + %% Relative timeout zero + %% - cancel any running timer + %% handle timeout zero events later + Timers_1 = cancel_timer_by_type(TimeoutType, Timers), + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers_1, Seen#{TimeoutType => true}, + [{TimeoutType,TimeoutMsg}|TimeoutEvents]); + _ -> + %% (Re)start the timer + TimerRef = + erlang:start_timer(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 + Timers_1 = + {maps:remove( + OldTimerRef, + TimerRefs#{TimerRef => TimeoutType}), + TimeoutTypes#{TimeoutType := TimerRef}}, + cancel_timer(OldTimerRef), + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers_1, Seen#{TimeoutType => true}, TimeoutEvents); + #{} -> + %% Insert the new timer type and ref + Timers_1 = + {TimerRefs#{TimerRef => TimeoutType}, + TimeoutTypes#{TimeoutType => TimerRef}}, + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers_1, Seen#{TimeoutType => true}, TimeoutEvents) + end end. loop_prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR) -> @@ -1800,90 +1944,6 @@ parse_timeout_opts_abs(Opts, Abs) -> 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(Timers, TimeoutsR) -> - parse_timers(Timers, TimeoutsR, #{}, []). -%% -parse_timers(Timers, [], _Seen, TimeoutEvents) -> - %% - {Timers,TimeoutEvents}; -parse_timers(Timers, [Timeout|TimeoutsR], Seen, TimeoutEvents) -> - %% - case Timeout of - {TimeoutType,Time,TimeoutMsg,TimeoutOpts} -> - %% Absolute timer - parse_timers( - Timers, TimeoutsR, Seen, TimeoutEvents, - TimeoutType, Time, TimeoutMsg, listify(TimeoutOpts)); - %% Relative timers below - {TimeoutType,0,TimeoutMsg} -> - parse_timers( - Timers, TimeoutsR, Seen, TimeoutEvents, - TimeoutType, zero, TimeoutMsg, []); - {TimeoutType,Time,TimeoutMsg} -> - parse_timers( - Timers, TimeoutsR, Seen, TimeoutEvents, - TimeoutType, Time, TimeoutMsg, []) - end. - -parse_timers( - Timers, TimeoutsR, Seen, TimeoutEvents, - TimeoutType, Time, TimeoutMsg, TimeoutOpts) -> - case Seen of - #{TimeoutType := _} -> - %% Type seen before - ignore - parse_timers( - Timers, TimeoutsR, Seen, TimeoutEvents); - #{} -> - %% Unseen type - handle - Seen_1 = Seen#{TimeoutType => true}, - case Time of - infinity -> - %% Cancel any running timer - parse_timers( - cancel_timer_by_type(TimeoutType, Timers), - TimeoutsR, Seen_1, TimeoutEvents); - zero -> - %% Cancel any running timer - %% Handle zero time timeouts later - parse_timers( - cancel_timer_by_type(TimeoutType, Timers), - TimeoutsR, Seen_1, - [{TimeoutType,TimeoutMsg}|TimeoutEvents]); - _ -> - %% (Re)start the timer - TimerRef = - erlang:start_timer( - 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. - %% Enqueue immediate timeout events (timeout 0 events) %% %% Event timer timeout 0 events gets special treatment since -- cgit v1.2.3 From 7c0df9ac2879e5d5bcdf511fc81b1465105c4b61 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Thu, 17 Jan 2019 12:17:30 +0100 Subject: Work around a compiler mis-optimization The common subexpression optimization accidentally caused unnecessary spilling of live registers. A rather ugly workaround can be found in loop_timeouts_cancel. --- lib/stdlib/src/gen_statem.erl | 48 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) (limited to 'lib/stdlib') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 819efa57b8..beb37e43e0 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -1838,7 +1838,7 @@ loop_timeouts( P, Debug, S, Events, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postponed, - Timers, Seen, TimeoutEvents, + {TimerRefs,TimeoutTypes} = Timers, Seen, TimeoutEvents, TimeoutType, Time, TimeoutMsg, TimeoutOpts) -> %% case Time of @@ -1854,13 +1854,32 @@ loop_timeouts( %% Relative timeout zero %% - cancel any running timer %% handle timeout zero events later - Timers_1 = cancel_timer_by_type(TimeoutType, Timers), - loop_timeouts( - P, Debug, S, - Events, NextState_NewData, - NextEventsR, Hibernate, TimeoutsR, Postponed, - Timers_1, Seen#{TimeoutType => true}, - [{TimeoutType,TimeoutMsg}|TimeoutEvents]); + %% + %% Instead of using cancel_timer_by_type/2 here, duplicate + %% the code, so we can make the function exit differ, + %% so the common subexpression optimization does not + %% kick in and accidentally force spilling all live + %% registers for the no TimeoutType match path + case TimeoutTypes of + #{TimeoutType := TimerRef} -> + Timers_1 = + cancel_timer_by_ref_and_type( + TimerRef, TimeoutType, TimerRefs, TimeoutTypes), + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers_1, Seen, + [{TimeoutType,TimeoutMsg}|TimeoutEvents], + TimeoutType); + #{} -> + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen#{TimeoutType => true}, + [{TimeoutType,TimeoutMsg}|TimeoutEvents]) + end; _ -> %% (Re)start the timer TimerRef = @@ -1895,6 +1914,19 @@ loop_timeouts( Timers_1, Seen#{TimeoutType => true}, TimeoutEvents) end end. +%% +loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents, + TimeoutType) -> + %% + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen#{TimeoutType => true}, TimeoutEvents). loop_prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR) -> {Debug_1,Events_1R} = -- cgit v1.2.3 From 12cecd07628cb28d7bc80c17bfc01ca783b8683c Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Thu, 17 Jan 2019 16:08:01 +0100 Subject: Write some more comments in the engine loop --- lib/stdlib/src/gen_statem.erl | 193 +++++++++++++++++++++++++++++++++--------- 1 file changed, 155 insertions(+), 38 deletions(-) (limited to 'lib/stdlib') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index beb37e43e0..2c03b4fff6 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -815,7 +815,10 @@ format_status( T -> [T] end]. - +%% Update #params.parent only if it differs. This should not +%% be possible today (OTP-22.0), but could happen for example +%% if someone implements changing a server's parent +%% in a new sys call. -compile({inline, update_parent/2}). update_parent(P, Parent) -> case P of @@ -885,17 +888,31 @@ wakeup_from_hibernate(P, Debug, S) -> loop_receive(P, Debug, S). %%%========================================================================== -%%% State Machine engine implementation of proc_lib/gen server +%%% State Machine engine implementation on proc_lib/gen %% Server loop, consists of all loop* functions %% and detours through sys:handle_system_message/7 and proc_lib:hibernate/3 +%% +%% The loop tries to keep all temporary values in arguments +%% and takes shortcuts for ?not_sys_debug, empty lists, etc. +%% The engine state #state{} is picked apart during the loop, +%% new values are kept in arguments, and a new #state{} is +%% composed at the end of the loop. #params{} collect engine +%% state fields that rarely changes. +%% +%% The loop is optimized a bit for staying in the loop, assuming that +%% system events are rare. So a detour to sys requires re-packing +%% of the engine state. %% Entry point for system_continue/3 +%% loop(P, Debug, #state{hibernate = true} = S) -> loop_hibernate(P, Debug, S); loop(P, Debug, S) -> loop_receive(P, Debug, S). +%% Go to hibernation +%% loop_hibernate(P, Debug, S) -> %% %% Does not return but restarts process at @@ -908,6 +925,9 @@ loop_hibernate(P, Debug, S) -> %% Entry point for wakeup_from_hibernate/3 +%% +%% Receive a new process message +%% loop_receive( #params{hibernate_after = HibernateAfterTimeout} = P, Debug, S) -> %% @@ -958,6 +978,8 @@ loop_receive( loop_hibernate(P, Debug, S) end. +%% We have received an event +%% loop_receive_result(P, ?not_sys_debug = Debug, S, Event) -> %% Here is the queue of not yet handled events created Events = [], @@ -976,7 +998,8 @@ loop_receive_result( Events = [], loop_event(P, Debug_1, S, Event, Events). -%% Entry point for handling an event; received or enqueued +%% Handle one event; received or enqueued +%% loop_event( P, Debug, #state{hibernate = true} = S, Event, Events) -> %% @@ -993,8 +1016,9 @@ loop_event( loop_event(P, Debug, S, Event, Events) -> loop_event_handler(P, Debug, S, Event, Events). +%% Call the state function, eventually +%% -compile({inline, [loop_event_handler/5]}). -%% Call the state function loop_event_handler( P, Debug, #state{state_data = State_Data} = S, Event, Events) -> %% @@ -1004,6 +1028,8 @@ loop_event_handler( Q = [Event|Events], loop_callback_mode(P, Debug, S, Q, State_Data, Event). +%% Figure out the callback mode +%% loop_callback_mode( #params{callback_mode = undefined} = P, Debug, S, Q, State_Data, CallbackEvent) -> @@ -1028,6 +1054,8 @@ loop_callback_mode( loop_callback_mode(P, Debug, S, Q, State_Data, CallbackEvent) -> loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent). +%% Check the result of Module:callback_mode() +%% loop_callback_mode_result( P, Debug, S, Q, State_Data, CallbackEvent, CallbackMode, [H|T], NewCallbackMode, NewStateEnter) -> @@ -1072,7 +1100,9 @@ loop_callback_mode_result( end. -%% Make a state enter call to the state function +%% Make a state enter call to the state function, we loop back here +%% from further down if state enter calls are enabled +%% loop_state_enter( P, Debug, #state{state_data = {PrevState,_PrevData}} = S, Q, NextState_NewData, @@ -1085,6 +1115,8 @@ loop_state_enter( NextEventsR, Hibernate, TimeoutsR, Postpone, StateCall, CallbackEvent). +%% Make a state call (not state enter call) to the state function +%% loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent) -> NextEventsR = [], Hibernate = false, @@ -1134,9 +1166,7 @@ loop_state_callback( NextEventsR, Hibernate, TimeoutsR, Postpone, CallEnter, StateCall, Actions). -%% Process the result from the state function. -%% When TransOpts =:= trans_opts it was a state function call, -%% otherwise it is an option tuple and it was a state enter call. +%% Process the result from the state function %% loop_state_callback_result( P, Debug, S, Q, {State,_Data} = State_Data, @@ -1278,6 +1308,7 @@ loop_state_callback_result( end. %% Ensure that Actions are a list +%% loop_actions( P, Debug, S, Q, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postpone, @@ -1315,6 +1346,8 @@ loop_actions( NextEventsR, Hibernate, TimeoutsR, Postpone) end. +%% Process the returned actions +%% loop_actions_list( P, Debug, S, Q, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postpone, @@ -1389,6 +1422,8 @@ loop_actions_list( CallEnter, StateCall, Actions, Timeout) end. +%% Process a reply action +%% loop_actions_reply( P, Debug, S, Q, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postpone, @@ -1417,6 +1452,8 @@ loop_actions_reply( Q) end. +%% Process a next_event action +%% loop_actions_next_event( P, Debug, S, Q, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postpone, @@ -1445,7 +1482,12 @@ loop_actions_next_event( _ -> terminate( error, - {bad_state_enter_action_from_state_function, + {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{ @@ -1454,6 +1496,8 @@ loop_actions_next_event( Q) end. +%% Process a timeout action, or also any unrecognized action +%% loop_actions_timeout( P, Debug, S, Q, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postpone, @@ -1630,6 +1674,8 @@ loop_state_transition( end end. +%% State transition to the same state +%% loop_keep_state( P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S, Events, NextState_NewData, @@ -1657,6 +1703,8 @@ loop_keep_state( Timers) end. +%% State transition to a different state +%% loop_state_change( P, Debug, S, Events, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postponed) -> @@ -1723,6 +1771,9 @@ loop_state_change( Timers) end. +%% Continue state transition with processing of +%% inserted events and timeout events +%% loop_next_events( P, Debug, S, Events, NextState_NewData, @@ -1754,6 +1805,8 @@ loop_next_events( NextEventsR, Hibernate, TimeoutsR, Postponed, Timers, Seen, TimeoutEvents). +%% Continue state transition with processing of timeout events +%% loop_timeouts( P, Debug, S, Events, NextState_NewData, @@ -1838,48 +1891,29 @@ loop_timeouts( P, Debug, S, Events, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postponed, - {TimerRefs,TimeoutTypes} = Timers, Seen, TimeoutEvents, + Timers, Seen, TimeoutEvents, TimeoutType, Time, TimeoutMsg, TimeoutOpts) -> %% case Time of infinity -> %% Cancel any running timer - Timers_1 = cancel_timer_by_type(TimeoutType, Timers), - loop_timeouts( + loop_timeouts_cancel( P, Debug, S, Events, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postponed, - Timers_1, Seen#{TimeoutType => true}, TimeoutEvents); + Timers, Seen, TimeoutEvents, + TimeoutType); 0 when TimeoutOpts =:= [] -> %% Relative timeout zero %% - cancel any running timer %% handle timeout zero events later %% - %% Instead of using cancel_timer_by_type/2 here, duplicate - %% the code, so we can make the function exit differ, - %% so the common subexpression optimization does not - %% kick in and accidentally force spilling all live - %% registers for the no TimeoutType match path - case TimeoutTypes of - #{TimeoutType := TimerRef} -> - Timers_1 = - cancel_timer_by_ref_and_type( - TimerRef, TimeoutType, TimerRefs, TimeoutTypes), - loop_timeouts( - P, Debug, S, - Events, NextState_NewData, - NextEventsR, Hibernate, TimeoutsR, Postponed, - Timers_1, Seen, - [{TimeoutType,TimeoutMsg}|TimeoutEvents], - TimeoutType); - #{} -> - loop_timeouts( - P, Debug, S, - Events, NextState_NewData, - NextEventsR, Hibernate, TimeoutsR, Postponed, - Timers, Seen#{TimeoutType => true}, - [{TimeoutType,TimeoutMsg}|TimeoutEvents]) - end; + loop_timeouts_cancel( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, [{TimeoutType,TimeoutMsg}|TimeoutEvents], + TimeoutType); _ -> %% (Re)start the timer TimerRef = @@ -1915,6 +1949,11 @@ loop_timeouts( end end. %% +%% Helper function for the ugly mis-optimization workaround +%% loop_timeouts_cancel below +%% +%% Do not inline! +%% loop_timeouts( P, Debug, S, Events, NextState_NewData, @@ -1928,11 +1967,83 @@ loop_timeouts( NextEventsR, Hibernate, TimeoutsR, Postponed, Timers, Seen#{TimeoutType => true}, TimeoutEvents). +%% Loop helper to cancel a timeout +%% +loop_timeouts_cancel( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + {TimerRefs,TimeoutTypes} = Timers, Seen, TimeoutEvents, + TimeoutType) -> + %% Ugly mis-optimization workaround + %% + %% This function body should have been: + %% Timers_1 = cancel_timer_by_type(TimeoutType, Timers), + %% loop_timeouts( + %% P, Debug, S, + %% Events, NextState_NewData, + %% NextEventsR, Hibernate, TimeoutsR, Postponed, + %% Timers_1, Seen#{TimeoutType => true}, TimeoutEvents). + %% + %% Since cancel_timer_by_type is inlined there is a code path + %% that checks if TimeoutType is a key in the map TimeoutTypes + %% and if not simply makes Timers_1 = Timers and then does + %% the map update of Seen and loops back to loop_timeouts/12. + %% This code path does not contain any external call and the + %% map update is an instruction, so that should be a simple + %% and fast path. + %% + %% The other code path when TimeoutType is a key in the map + %% TimeoutTypes calls erlang:cancel_timer/1 which forces + %% all live registers (about 13 of them) out on the stack + %% and back again afterwards. + %% + %% Unfortunately the optimization of common subexpressions + %% sees that both these function exits are identical and + %% joins them. Then during register allocation the common + %% function exit is adapted for the code path that spills + %% all live registers to the stack. So also the simple + %% and fast path spills all live registers around its + %% map update... Bummer! + %% + %% So this workaround duplicates cancel_timer_by_type/2 here, + %% and makes the function exits differ - the slow case + %% calls a helper function above to update Seen, and + %% the fast case updates Seen in this function. This tricks + %% the common subexpression optimizer into not joining + %% these two code paths. + %% + %% So the helper function above must not be inlined, please! + %% + case TimeoutTypes of + #{TimeoutType := TimerRef} -> + Timers_1 = + cancel_timer_by_ref_and_type( + TimerRef, TimeoutType, TimerRefs, TimeoutTypes), + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers_1, Seen, TimeoutEvents, + TimeoutType); + #{} -> + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen#{TimeoutType => true}, TimeoutEvents) + end. + +%% Continue state transition with prepending timeout zero events +%% before event queue reversal i.e appending timeout zero events +%% 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, []). +%% Place inserted events first in the event queue +%% loop_done(P, Debug, S, NextEventsR, Events) -> case NextEventsR of [] -> @@ -1945,6 +2056,9 @@ loop_done(P, Debug, S, NextEventsR, Events) -> loop_done(P, Debug, S, lists:reverse(NextEventsR, Events)) end. %% +%% State transition is done, keep looping if there are +%% enqueued events, otherwise get a new event +%% loop_done(P, Debug, S, Q) -> %%% io:format( %%% "loop_done: state_data = ~p,~n" @@ -1963,6 +2077,9 @@ loop_done(P, Debug, S, Q) -> %%--------------------------------------------------------------------------- %% Server loop helpers +%% Parse an option list for erlang:start_timer/4 to figure out +%% if the timeout will be absolute or relative +%% parse_timeout_opts_abs(Opts) -> parse_timeout_opts_abs(Opts, false). %% -- cgit v1.2.3