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