From f986565050ac30075ef3c0a451bf6dad91c7c446 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 13 Sep 2016 11:15:32 +0200 Subject: Implement call/3 dirty_timeout --- lib/stdlib/src/gen_statem.erl | 110 ++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 42 deletions(-) (limited to 'lib/stdlib/src') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 3b3477b282..46c0e92a9b 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -385,53 +385,79 @@ call(ServerRef, Request) -> -spec call( ServerRef :: server_ref(), Request :: term(), - Timeout :: timeout()) -> + Timeout :: + timeout() | + {'clean_timeout',T :: timeout()} | + {'dirty_timeout',T :: timeout()}) -> Reply :: term(). -call(ServerRef, Request, infinity) -> - try gen:call(ServerRef, '$gen_call', Request, infinity) of - {ok,Reply} -> - Reply - catch - Class:Reason -> - erlang:raise( - Class, - {Reason,{?MODULE,call,[ServerRef,Request,infinity]}}, - erlang:get_stacktrace()) - end; call(ServerRef, Request, Timeout) -> - %% Call server through proxy process to dodge any late reply - Ref = make_ref(), - Self = self(), - Pid = spawn( - fun () -> - Self ! - try gen:call( - ServerRef, '$gen_call', Request, Timeout) of - Result -> - {Ref,Result} - catch Class:Reason -> - {Ref,Class,Reason,erlang:get_stacktrace()} - end - end), - Mref = monitor(process, Pid), - receive - {Ref,Result} -> - demonitor(Mref, [flush]), - case Result of + case parse_timeout(Timeout) of + {dirty_timeout,T} -> + try gen:call(ServerRef, '$gen_call', Request, T) of {ok,Reply} -> Reply + catch + Class:Reason -> + erlang:raise( + Class, + {Reason,{?MODULE,call,[ServerRef,Request,Timeout]}}, + erlang:get_stacktrace()) + end; + {clean_timeout,T} -> + %% Call server through proxy process to dodge any late reply + Ref = make_ref(), + Self = self(), + Pid = spawn( + fun () -> + Self ! + try gen:call( + ServerRef, '$gen_call', Request, T) of + Result -> + {Ref,Result} + catch Class:Reason -> + {Ref,Class,Reason, + erlang:get_stacktrace()} + end + end), + Mref = monitor(process, Pid), + receive + {Ref,Result} -> + demonitor(Mref, [flush]), + case Result of + {ok,Reply} -> + Reply + end; + {Ref,Class,Reason,Stacktrace} -> + demonitor(Mref, [flush]), + erlang:raise( + Class, + {Reason,{?MODULE,call,[ServerRef,Request,Timeout]}}, + Stacktrace); + {'DOWN',Mref,_,_,Reason} -> + %% There is a theoretical possibility that the + %% proxy process gets killed between try--of and ! + %% so this clause is in case of that + exit(Reason) end; - {Ref,Class,Reason,Stacktrace} -> - demonitor(Mref, [flush]), - erlang:raise( - Class, - {Reason,{?MODULE,call,[ServerRef,Request,Timeout]}}, - Stacktrace); - {'DOWN',Mref,_,_,Reason} -> - %% There is a theoretical possibility that the - %% proxy process gets killed between try--of and ! - %% so this clause is in case of that - exit(Reason) + Error when is_atom(Error) -> + erlang:error(Error, [ServerRef,Request,Timeout]) + end. + +parse_timeout(Timeout) -> + case Timeout of + {clean_timeout,infinity} -> + {dirty_timeout,infinity}; + {clean_timeout,_} -> + Timeout; + {dirty_timeout,_} -> + Timeout; + {_,_} -> + %% Be nice and throw a badarg for speling errors + badarg; + infinity -> + {dirty_timeout,infinity}; + T -> + {clean_timeout,T} end. %% Reply from a state machine callback to whom awaits in call/2 -- cgit v1.2.3 From 6ee0aefd8a0ea9c165211c42d5244182b5aa9210 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 13 Sep 2016 14:00:04 +0200 Subject: Implement state entry events --- lib/stdlib/src/gen_statem.erl | 208 ++++++++++++++++++++++++++++++------------ 1 file changed, 150 insertions(+), 58 deletions(-) (limited to 'lib/stdlib/src') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 46c0e92a9b..7f437404ed 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -53,7 +53,7 @@ action/0]). %% Fix problem for doc build --export_type([transition_option/0]). +-export_type([state_entry_mode/0,transition_option/0]). %%%========================================================================== %%% Interface functions. @@ -72,9 +72,10 @@ -type event_type() :: {'call',From :: from()} | 'cast' | - 'info' | 'timeout' | 'internal'. + 'info' | 'timeout' | 'enter' | 'internal'. -type callback_mode() :: 'state_functions' | 'handle_event_function'. +-type state_entry_mode() :: 'state_entry_events'. -type transition_option() :: postpone() | hibernate() | event_timeout(). @@ -183,7 +184,9 @@ %% %% It is called once after init/0 and code_change/4 but before %% the first state callback StateName/3 or handle_event/4. --callback callback_mode() -> callback_mode(). +-callback callback_mode() -> + callback_mode() | + [callback_mode() | state_entry_mode()]. %% Example state callback for StateName = 'state_name' %% when callback_mode() =:= state_functions. @@ -556,19 +559,27 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) -> end, S = #{ callback_mode => undefined, + state_entry_events => false, module => Module, name => Name, - %% All fields below will be replaced according to the arguments to + %% The rest of the fields are set from to the arguments to %% loop_event_actions/10 when it finally loops back to loop/3 - state => State, - data => Data, - postponed => P, - hibernate => false, - timer => undefined}, + %% in loop_events_done/8 + %% + %% Marker for initial state, cleared immediately when used + init_state => true + }, NewDebug = sys_debug(Debug, S, State, {enter,Event,State}), - loop_event_actions( - Parent, NewDebug, S, Events, - State, Data, P, Event, State, NewActions). + case call_callback_mode(S) of + {ok,NewS} -> + loop_event_actions( + Parent, NewDebug, NewS, Events, + State, Data, P, Event, State, NewActions); + {Class,Reason,Stacktrace} -> + terminate( + Class, Reason, Stacktrace, + NewDebug, S, [Event|Events], State, Data, P) + end. %%%========================================================================== %%% gen callbacks @@ -866,13 +877,93 @@ loop_events( loop_events_done(Parent, Debug, S, Timer, State, Data, P, Hibernate) -> NewS = S#{ - state := State, - data := Data, - postponed := P, - hibernate := Hibernate, - timer := Timer}, + state => State, + data => Data, + postponed => P, + hibernate => Hibernate, + timer => Timer}, loop(Parent, Debug, NewS). + + +parse_callback_mode([], CBMode, SEntry) -> + {CBMode,SEntry}; +parse_callback_mode([H|T], CBMode, SEntry) -> + case callback_mode(H) of + true -> + parse_callback_mode(T, H, SEntry); + false -> + case H of + state_entry_events -> + parse_callback_mode(T, CBMode, true); + _ -> + {undefined,SEntry} + end + end; +parse_callback_mode(_, _CBMode, SEntry) -> + {undefined,SEntry}. + +call_callback_mode(S, CallbackMode) -> + case + parse_callback_mode( + if + is_atom(CallbackMode) -> + [CallbackMode]; + true -> + CallbackMode + end, undefined, false) + of + {undefined,_} -> + {error, + {bad_return_from_callback_mode,CallbackMode}, + ?STACKTRACE()}; + {CBMode,SEntry} -> + {ok, + S#{ + callback_mode := CBMode, + state_entry_events := SEntry}} + end. + +call_callback_mode(#{module := Module} = S) -> + try Module:callback_mode() of + CallbackMode -> + call_callback_mode(S, CallbackMode) + catch + CallbackMode -> + call_callback_mode(S, CallbackMode); + error:undef -> + %% Process undef to check for the simple mistake + %% of calling a nonexistent state function + %% to make the undef more precise + case erlang:get_stacktrace() of + [{Module,callback_mode,[]=Args,_} + |Stacktrace] -> + {error, + {undef_callback,{Module,callback_mode,Args}}, + Stacktrace}; + Stacktrace -> + {error,undef,Stacktrace} + end; + Class:Reason -> + {Class,Reason,erlang:get_stacktrace()} + end. + +loop_event( + Parent, Debug, + #{callback_mode := undefined} = S, + Events, + State, Data, P, Event, Hibernate) -> + %% This happens after code_change/4 + case call_callback_mode(S) of + {ok,NewS} -> + loop_event( + Parent, Debug, NewS, Events, + State, Data, P, Event, Hibernate); + {Class,Reason,Stacktrace} -> + terminate( + Class, Reason, Stacktrace, + Debug, S, [Event|Events], State, Data, P) + end; loop_event( Parent, Debug, #{callback_mode := CallbackMode, @@ -891,24 +982,16 @@ loop_event( %% try case CallbackMode of - undefined -> - Module:callback_mode(); state_functions -> erlang:apply(Module, State, [Type,Content,Data]); handle_event_function -> Module:handle_event(Type, Content, State, Data) end of - Result when CallbackMode =:= undefined -> - loop_event_callback_mode( - Parent, Debug, S, Events, State, Data, P, Event, Result); Result -> loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result) catch - Result when CallbackMode =:= undefined -> - loop_event_callback_mode( - Parent, Debug, S, Events, State, Data, P, Event, Result); Result -> loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result); @@ -936,14 +1019,6 @@ loop_event( %% of calling a nonexistent state function %% to make the undef more precise case erlang:get_stacktrace() of - [{Module,callback_mode,[]=Args,_} - |Stacktrace] - when CallbackMode =:= undefined -> - terminate( - error, - {undef_callback,{Module,callback_mode,Args}}, - Stacktrace, - Debug, S, [Event|Events], State, Data, P); [{Module,State,[Type,Content,Data]=Args,_} |Stacktrace] when CallbackMode =:= state_functions -> @@ -972,25 +1047,6 @@ loop_event( Debug, S, [Event|Events], State, Data, P) end. -%% Interpret callback_mode() result -loop_event_callback_mode( - Parent, Debug, S, Events, State, Data, P, Event, CallbackMode) -> - case callback_mode(CallbackMode) of - true -> - Hibernate = false, % We have already GC:ed recently - loop_event( - Parent, Debug, - S#{callback_mode := CallbackMode}, - Events, - State, Data, P, Event, Hibernate); - false -> - terminate( - error, - {bad_return_from_callback_mode,CallbackMode}, - ?STACKTRACE(), - Debug, S, [Event|Events], State, Data, P) - end. - %% Interpret all callback return variants loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result) -> @@ -1174,7 +1230,7 @@ loop_event_actions( %% %% End of actions list loop_event_actions( - Parent, Debug, S, Events, + Parent, Debug, #{state_entry_events := SEEvents} = S, Events, State, NewData, P0, Event, NextState, [], Postpone, Hibernate, Timeout, NextEvents) -> %% @@ -1196,7 +1252,25 @@ loop_event_actions( {lists:reverse(P1, Events),[]} end, %% Place next events first in queue - Q = lists:reverse(NextEvents, Q2), + Q3 = lists:reverse(NextEvents, Q2), + %% State entry events + Q = + case SEEvents of + true -> + %% Generate state entry events + case + (NextState =/= State) + orelse maps:is_key(init_state, S) + of + true -> + %% State change or initial state + [{enter,State}|Q3]; + false -> + Q3 + end; + false -> + Q3 + end, %% NewDebug = sys_debug( @@ -1208,7 +1282,15 @@ loop_event_actions( {consume,Event,NextState} end), loop_events( - Parent, NewDebug, S, Q, NextState, NewData, P, Hibernate, Timeout). + Parent, NewDebug, + %% Avoid infinite loop in initial state with state entry events + case maps:is_key(init_state, S) of + true -> + maps:remove(init_state, S); + false -> + S + end, + Q, NextState, NewData, P, Hibernate, Timeout). %%--------------------------------------------------------------------------- %% Server helpers @@ -1285,7 +1367,9 @@ terminate( error_info( Class, Reason, Stacktrace, - #{name := Name, callback_mode := CallbackMode}, + #{name := Name, + callback_mode := CallbackMode, + state_entry_events := SEEvents}, Q, P, FmtData) -> {FixedReason,FixedStacktrace} = case Stacktrace of @@ -1312,6 +1396,13 @@ error_info( end; _ -> {Reason,Stacktrace} end, + CBMode = + case SEEvents of + true -> + [CallbackMode,state_entry_events]; + false -> + CallbackMode + end, error_logger:format( "** State machine ~p terminating~n" ++ case Q of @@ -1338,8 +1429,9 @@ error_info( [] -> []; [Event|_] -> [Event] end] ++ - [FmtData,Class,FixedReason, - CallbackMode] ++ + [FmtData, + Class,FixedReason, + CBMode] ++ case Q of [_|[_|_] = Events] -> [Events]; _ -> [] -- cgit v1.2.3 From 04d40c5cd18aca449606c19608e8044f593ee99e Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Thu, 22 Sep 2016 17:40:47 +0200 Subject: Change state entry events into state enter calls --- lib/stdlib/src/gen_statem.erl | 582 +++++++++++++++++++++++------------------- 1 file changed, 314 insertions(+), 268 deletions(-) (limited to 'lib/stdlib/src') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 7f437404ed..aedcfc932f 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -47,13 +47,16 @@ %% Type exports for templates -export_type( [event_type/0, - callback_mode/0, + state_name/0, + callback_mode_result/0, state_function_result/0, + state_function_enter_result/0, handle_event_result/0, + handle_event_enter_result/0, action/0]). %% Fix problem for doc build --export_type([state_entry_mode/0,transition_option/0]). +-export_type([transition_option/0]). %%%========================================================================== %%% Interface functions. @@ -72,10 +75,12 @@ -type event_type() :: {'call',From :: from()} | 'cast' | - 'info' | 'timeout' | 'enter' | 'internal'. + 'info' | 'timeout' | 'internal'. +-type callback_mode_result() :: + callback_mode() | [callback_mode() | state_enter()]. -type callback_mode() :: 'state_functions' | 'handle_event_function'. --type state_entry_mode() :: 'state_entry_events'. +-type state_enter() :: 'state_enter'. -type transition_option() :: postpone() | hibernate() | event_timeout(). @@ -109,6 +114,14 @@ 'postpone' | % Set the postpone option {'postpone', Postpone :: postpone()} | %% + %% All 'next_event' events are kept in a list and then + %% inserted at state changes so the first in the + %% action() list is the first to be delivered. + {'next_event', % Insert event as the next to handle + EventType :: event_type(), + EventContent :: term()} | + enter_action(). +-type enter_action() :: 'hibernate' | % Set the hibernate option {'hibernate', Hibernate :: hibernate()} | %% @@ -116,14 +129,7 @@ {'timeout', % Set the event timeout option Time :: event_timeout(), EventContent :: term()} | %% - reply_action() | - %% - %% All 'next_event' events are kept in a list and then - %% inserted at state changes so the first in the - %% action() list is the first to be delivered. - {'next_event', % Insert event as the next to handle - EventType :: event_type(), - EventContent :: term()}. + reply_action(). -type reply_action() :: {'reply', % Reply to a caller From :: from(), Reply :: term()}. @@ -137,6 +143,16 @@ NewData :: data(), Actions :: [action()] | action()} | common_state_callback_result(). +-type state_function_enter_result() :: + {'next_state', % {next_state,NextStateName,NewData,[]} + NextStateName :: state_name(), + NewData :: data()} | + {'next_state', % State transition, maybe to the same state + NextStateName :: state_name(), + NewData :: data(), + Actions :: [enter_action()] | enter_action()} | + common_state_callback_result(). + -type handle_event_result() :: {'next_state', % {next_state,NextState,NewData,[]} NextState :: state(), @@ -146,6 +162,16 @@ NewData :: data(), Actions :: [action()] | action()} | common_state_callback_result(). +-type handle_event_enter_result() :: + {'next_state', % {next_state,NextState,NewData,[]} + NextState :: state(), + NewData :: data()} | + {'next_state', % State transition, maybe to the same state + NextState :: state(), + NewData :: data(), + Actions :: [enter_action()] | enter_action()} | + common_state_callback_result(). + -type common_state_callback_result() :: 'stop' | % {stop,normal} {'stop', % Stop the server @@ -164,10 +190,10 @@ NewData :: data()} | {'keep_state', % Keep state, change data NewData :: data(), - Actions :: [action()] | action()} | + Actions :: [ActionType] | ActionType} | 'keep_state_and_data' | % {keep_state_and_data,[]} {'keep_state_and_data', % Keep state and data -> only actions - Actions :: [action()] | action()}. + Actions :: [ActionType] | ActionType}. %% The state machine init function. It is called only once and @@ -184,9 +210,7 @@ %% %% It is called once after init/0 and code_change/4 but before %% the first state callback StateName/3 or handle_event/4. --callback callback_mode() -> - callback_mode() | - [callback_mode() | state_entry_mode()]. +-callback callback_mode() -> callback_mode_result(). %% Example state callback for StateName = 'state_name' %% when callback_mode() =:= state_functions. @@ -197,7 +221,11 @@ %% StateName/3 callbacks and terminate/3, so the state name %% 'terminate' is unusable in this mode. -callback state_name( - event_type(), + 'enter', + OldStateName :: state_name(), + Data :: data()) -> + state_function_enter_result(); + (event_type(), EventContent :: term(), Data :: data()) -> state_function_result(). @@ -205,7 +233,12 @@ %% State callback for all states %% when callback_mode() =:= handle_event_function. -callback handle_event( - event_type(), + 'enter', + OldState :: state(), + State :: state(), % Current state + Data :: data()) -> + handle_event_enter_result(); + (event_type(), EventContent :: term(), State :: state(), % Current state Data :: data()) -> @@ -547,7 +580,7 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) -> Name = gen:get_proc_name(Server), Debug = gen:debug_options(Name, Opts), P = Events = [], - Event = {internal,initial_state}, + Event = {internal,init_state}, %% We enforce {postpone,false} to ensure that %% our fake Event gets discarded, thought it might get logged NewActions = @@ -559,7 +592,7 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) -> end, S = #{ callback_mode => undefined, - state_entry_events => false, + state_enter => false, module => Module, name => Name, %% The rest of the fields are set from to the arguments to @@ -886,51 +919,13 @@ loop_events_done(Parent, Debug, S, Timer, State, Data, P, Hibernate) -> -parse_callback_mode([], CBMode, SEntry) -> - {CBMode,SEntry}; -parse_callback_mode([H|T], CBMode, SEntry) -> - case callback_mode(H) of - true -> - parse_callback_mode(T, H, SEntry); - false -> - case H of - state_entry_events -> - parse_callback_mode(T, CBMode, true); - _ -> - {undefined,SEntry} - end - end; -parse_callback_mode(_, _CBMode, SEntry) -> - {undefined,SEntry}. - -call_callback_mode(S, CallbackMode) -> - case - parse_callback_mode( - if - is_atom(CallbackMode) -> - [CallbackMode]; - true -> - CallbackMode - end, undefined, false) - of - {undefined,_} -> - {error, - {bad_return_from_callback_mode,CallbackMode}, - ?STACKTRACE()}; - {CBMode,SEntry} -> - {ok, - S#{ - callback_mode := CBMode, - state_entry_events := SEntry}} - end. - call_callback_mode(#{module := Module} = S) -> try Module:callback_mode() of CallbackMode -> - call_callback_mode(S, CallbackMode) + call_callback_mode_result(S, CallbackMode) catch CallbackMode -> - call_callback_mode(S, CallbackMode); + call_callback_mode_result(S, CallbackMode); error:undef -> %% Process undef to check for the simple mistake %% of calling a nonexistent state function @@ -948,38 +943,57 @@ call_callback_mode(#{module := Module} = S) -> {Class,Reason,erlang:get_stacktrace()} end. -loop_event( - Parent, Debug, +call_callback_mode_result(S, CallbackMode) -> + case + parse_callback_mode( + if + is_atom(CallbackMode) -> + [CallbackMode]; + true -> + CallbackMode + end, undefined, false) + of + {undefined,_} -> + {error, + {bad_return_from_callback_mode,CallbackMode}, + ?STACKTRACE()}; + {CBMode,StateEnter} -> + {ok, + S#{ + callback_mode := CBMode, + state_enter := StateEnter}} + end. + +parse_callback_mode([], CBMode, StateEnter) -> + {CBMode,StateEnter}; +parse_callback_mode([H|T], CBMode, StateEnter) -> + case callback_mode(H) of + true -> + parse_callback_mode(T, H, StateEnter); + false -> + case H of + state_enter -> + parse_callback_mode(T, CBMode, true); + _ -> + {undefined,StateEnter} + end + end; +parse_callback_mode(_, _CBMode, StateEnter) -> + {undefined,StateEnter}. + +call_state_function( #{callback_mode := undefined} = S, - Events, - State, Data, P, Event, Hibernate) -> - %% This happens after code_change/4 + Type, Content, State, Data) -> case call_callback_mode(S) of {ok,NewS} -> - loop_event( - Parent, Debug, NewS, Events, - State, Data, P, Event, Hibernate); - {Class,Reason,Stacktrace} -> - terminate( - Class, Reason, Stacktrace, - Debug, S, [Event|Events], State, Data, P) + call_state_function(NewS, Type, Content, State, Data); + Error -> + Error end; -loop_event( - Parent, Debug, +call_state_function( #{callback_mode := CallbackMode, module := Module} = S, - Events, - State, Data, P, {Type,Content} = Event, Hibernate) -> - %% - %% If 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 depend on i.e collect garbage which - %% would have happened if we actually hibernated - %% and immediately was awakened - Hibernate andalso garbage_collect(), - %% + Type, Content, State, Data) -> try case CallbackMode of state_functions -> @@ -989,12 +1003,10 @@ loop_event( end of Result -> - loop_event_result( - Parent, Debug, S, Events, State, Data, P, Event, Result) + {ok,Result,S} catch Result -> - loop_event_result( - Parent, Debug, S, Events, State, Data, P, Event, Result); + {ok,Result,S}; error:badarg -> case erlang:get_stacktrace() of [{erlang,apply, @@ -1004,15 +1016,11 @@ loop_event( when CallbackMode =:= state_functions -> %% We get here e.g if apply fails %% due to State not being an atom - terminate( - error, - {undef_state_function,{Module,State,Args}}, - Stacktrace, - Debug, S, [Event|Events], State, Data, P); + {error, + {undef_state_function,{Module,State,Args}}, + Stacktrace}; Stacktrace -> - terminate( - error, badarg, Stacktrace, - Debug, S, [Event|Events], State, Data, P) + {error,badarg,Stacktrace} end; error:undef -> %% Process undef to check for the simple mistake @@ -1022,34 +1030,54 @@ loop_event( [{Module,State,[Type,Content,Data]=Args,_} |Stacktrace] when CallbackMode =:= state_functions -> - terminate( - error, - {undef_state_function,{Module,State,Args}}, - Stacktrace, - Debug, S, [Event|Events], State, Data, P); + {error, + {undef_state_function,{Module,State,Args}}, + Stacktrace}; [{Module,handle_event,[Type,Content,State,Data]=Args,_} |Stacktrace] when CallbackMode =:= handle_event_function -> - terminate( - error, - {undef_state_function,{Module,handle_event,Args}}, - Stacktrace, - Debug, S, [Event|Events], State, Data, P); + {error, + {undef_state_function,{Module,handle_event,Args}}, + Stacktrace}; Stacktrace -> - terminate( - error, undef, Stacktrace, - Debug, S, [Event|Events], State, Data, P) + {error,undef,Stacktrace} end; Class:Reason -> - Stacktrace = erlang:get_stacktrace(), + {Class,Reason,erlang:get_stacktrace()} + end. + +loop_event( + Parent, Debug, S, Events, + State, Data, P, {Type,Content} = Event, Hibernate) -> + %% + %% If 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 depend on i.e collect garbage which + %% would have happened if we actually hibernated + %% and immediately was awakened + Hibernate andalso garbage_collect(), + case call_state_function(S, Type, Content, State, Data) of + {ok,Result,NewS} -> + {NewData,NextState,Actions} = + parse_event_result( + Parent, Debug, NewS, Events, + State, Data, P, Event, + Result, true), + loop_event_actions( + Parent, Debug, S, Events, + State, NewData, P, Event, NextState, Actions); + {Class,Reason,Stacktrace} -> terminate( Class, Reason, Stacktrace, Debug, S, [Event|Events], State, Data, P) end. %% Interpret all callback return variants -loop_event_result( - Parent, Debug, S, Events, State, Data, P, Event, Result) -> +parse_event_result( + _Parent, Debug, S, Events, State, Data, P, Event, + Result, AllowStateChange) -> case Result of stop -> terminate( @@ -1073,30 +1101,22 @@ loop_event_result( reply_then_terminate( exit, Reason, ?STACKTRACE(), Debug, S, Q, State, NewData, P, Replies); - {next_state,NextState,NewData} -> - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, []); - {next_state,NextState,NewData,Actions} -> - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, Actions); + {next_state,State,NewData} -> + {NewData,State,[]}; + {next_state,NextState,NewData} when AllowStateChange -> + {NewData,NextState,[]}; + {next_state,State,NewData,Actions} -> + {NewData,State,Actions}; + {next_state,NextState,NewData,Actions} when AllowStateChange -> + {NewData,NextState,Actions}; {keep_state,NewData} -> - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, State, []); + {NewData,State,[]}; {keep_state,NewData,Actions} -> - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, State, Actions); + {NewData,State,Actions}; keep_state_and_data -> - loop_event_actions( - Parent, Debug, S, Events, - State, Data, P, Event, State, []); + {Data,State,[]}; {keep_state_and_data,Actions} -> - loop_event_actions( - Parent, Debug, S, Events, - State, Data, P, Event, State, Actions); + {Data,State,Actions}; _ -> terminate( error, @@ -1105,134 +1125,178 @@ loop_event_result( Debug, S, [Event|Events], State, Data, P) end. -loop_event_actions( - Parent, Debug, S, Events, State, NewData, P, Event, NextState, Actions) -> - Postpone = false, % Shall we postpone this event; boolean() +parse_enter_actions(Debug, S, State, Actions, Hibernate, Timeout) -> + Postpone = forbidden, + NextEvents = forbidden, + parse_actions( + Debug, S, State, listify(Actions), + Hibernate, Timeout, Postpone, NextEvents). + +parse_actions(Debug, S, State, Actions) -> + Postpone = false, Hibernate = false, Timeout = undefined, NextEvents = [], - loop_event_actions( - Parent, Debug, S, Events, State, NewData, P, Event, NextState, - if - is_list(Actions) -> - Actions; - true -> - [Actions] - end, - Postpone, Hibernate, Timeout, NextEvents). + parse_actions( + Debug, S, State, listify(Actions), + Hibernate, Timeout, Postpone, NextEvents). %% -%% Process all actions -loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, [Action|Actions], - Postpone, Hibernate, Timeout, NextEvents) -> +parse_actions( + Debug, _S, _State, [], Hibernate, Timeout, Postpone, NextEvents) -> + {ok,Debug,Hibernate,Timeout,Postpone,NextEvents}; +parse_actions( + Debug, S, State, [Action|Actions], + Hibernate, Timeout, Postpone, NextEvents) -> case Action of %% Actual actions {reply,From,Reply} -> case from(From) of true -> NewDebug = do_reply(Debug, S, State, From, Reply), - loop_event_actions( - Parent, NewDebug, S, Events, - State, NewData, P, Event, NextState, Actions, - Postpone, Hibernate, Timeout, NextEvents); + parse_actions( + NewDebug, S, State, Actions, + Hibernate, Timeout, Postpone, NextEvents); false -> - terminate( - error, - {bad_action_from_state_function,Action}, - ?STACKTRACE(), - Debug, S, [Event|Events], State, NewData, P) - end; - {next_event,Type,Content} -> - case event_type(Type) of - true -> - NewDebug = - sys_debug(Debug, S, State, {in,{Type,Content}}), - loop_event_actions( - Parent, NewDebug, S, Events, - State, NewData, P, Event, NextState, Actions, - Postpone, Hibernate, Timeout, - [{Type,Content}|NextEvents]); - false -> - terminate( - error, - {bad_action_from_state_function,Action}, - ?STACKTRACE(), - Debug, S, [Event|Events], State, NewData, P) + {error, + {bad_action_from_state_function,Action}, + ?STACKTRACE()} end; %% Actions that set options - {postpone,NewPostpone} when is_boolean(NewPostpone) -> - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, Actions, - NewPostpone, Hibernate, Timeout, NextEvents); - {postpone,_} -> - terminate( - error, - {bad_action_from_state_function,Action}, - ?STACKTRACE(), - Debug, S, [Event|Events], State, NewData, P); - postpone -> - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, Actions, - true, Hibernate, Timeout, NextEvents); {hibernate,NewHibernate} when is_boolean(NewHibernate) -> - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, Actions, - Postpone, NewHibernate, Timeout, NextEvents); + parse_actions( + Debug, S, State, Actions, + NewHibernate, Timeout, Postpone, NextEvents); {hibernate,_} -> - terminate( - error, - {bad_action_from_state_function,Action}, - ?STACKTRACE(), - Debug, S, [Event|Events], State, NewData, P); + {error, + {bad_action_from_state_function,Action}, + ?STACKTRACE()}; hibernate -> - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, Actions, - Postpone, true, Timeout, NextEvents); + parse_actions( + Debug, S, State, Actions, + true, Timeout, Postpone, NextEvents); {timeout,infinity,_} -> % Clear timer - it will never trigger - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, Actions, - Postpone, Hibernate, undefined, NextEvents); + parse_actions( + Debug, S, State, Actions, + Hibernate, undefined, Postpone, NextEvents); {timeout,Time,_} = NewTimeout when is_integer(Time), Time >= 0 -> - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, Actions, - Postpone, Hibernate, NewTimeout, NextEvents); + parse_actions( + Debug, S, State, Actions, + Hibernate, NewTimeout, Postpone, NextEvents); {timeout,_,_} -> - terminate( - error, - {bad_action_from_state_function,Action}, - ?STACKTRACE(), - Debug, S, [Event|Events], State, NewData, P); + {error, + {bad_action_from_state_function,Action}, + ?STACKTRACE()}; infinity -> % Clear timer - it will never trigger - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, Actions, - Postpone, Hibernate, undefined, NextEvents); + parse_actions( + Debug, S, State, Actions, + Hibernate, undefined, Postpone, NextEvents); Time when is_integer(Time), Time >= 0 -> NewTimeout = {timeout,Time,Time}, - loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, Actions, - Postpone, Hibernate, NewTimeout, NextEvents); + parse_actions( + Debug, S, State, Actions, + Hibernate, NewTimeout, Postpone, NextEvents); + {postpone,NewPostpone} + when is_boolean(NewPostpone), Postpone =/= forbidden -> + parse_actions( + Debug, S, State, Actions, + Hibernate, Timeout, NewPostpone, NextEvents); + {postpone,_} -> + {error, + {bad_action_from_state_function,Action}, + ?STACKTRACE()}; + postpone when Postpone =/= forbidden -> + parse_actions( + Debug, S, State, Actions, + Hibernate, Timeout, true, NextEvents); + {next_event,Type,Content} -> + case event_type(Type) of + true when NextEvents =/= forbidden -> + NewDebug = + sys_debug(Debug, S, State, {in,{Type,Content}}), + parse_actions( + NewDebug, S, State, Actions, + Hibernate, Timeout, Postpone, + [{Type,Content}|NextEvents]); + _ -> + {error, + {bad_action_from_state_function,Action}, + ?STACKTRACE()} + end; _ -> + {error, + {bad_action_from_state_function,Action}, + ?STACKTRACE()} + end. + +loop_event_actions( + Parent, Debug, #{state_enter := StateEnter} = S, Events, + State, NewData, P, Event, NextState, Actions) -> + case parse_actions(Debug, S, State, Actions) of + {ok,NewDebug,Hibernate,Timeout,Postpone,NextEvents} -> + case + StateEnter andalso + ((NextState =/= State) + orelse maps:is_key(init_state, S)) of + true -> + loop_event_enter( + Parent, NewDebug, S, Events, + State, NewData, P, Event, NextState, + Hibernate, Timeout, Postpone, NextEvents); + false -> + loop_event_result( + Parent, NewDebug, S, Events, + State, NewData, P, Event, NextState, + Hibernate, Timeout, Postpone, NextEvents) + end; + {Class,Reason,Stacktrace} -> terminate( - error, - {bad_action_from_state_function,Action}, - ?STACKTRACE(), + Class, Reason, Stacktrace, Debug, S, [Event|Events], State, NewData, P) - end; -%% -%% End of actions list -loop_event_actions( - Parent, Debug, #{state_entry_events := SEEvents} = S, Events, - State, NewData, P0, Event, NextState, [], - Postpone, Hibernate, Timeout, NextEvents) -> + end. + +loop_event_enter( + Parent, Debug, S, Events, + State, NewData, P, Event, NextState, + Hibernate, Timeout, Postpone, NextEvents) -> + case call_state_function(S, enter, State, NextState, NewData) of + {ok,Result,NewS} -> + {NewerData,_,Actions} = + parse_event_result( + Parent, Debug, NewS, Events, + NextState, NewData, P, Event, + Result, false), + loop_event_enter_actions( + Parent, Debug, NewS, Events, + State, NewerData, P, Event, NextState, + Hibernate, Timeout, Postpone, NextEvents, Actions); + {Class,Reason,Stacktrace} -> + terminate( + Class, Reason, Stacktrace, + Debug, S, [Event|Events], NextState, NewData, P) + end. + +loop_event_enter_actions( + Parent, Debug, S, Events, + State, NewData, P, Event, NextState, + Hibernate, Timeout, Postpone, NextEvents, Actions) -> + case + parse_enter_actions(Debug, S, NextState, Actions, Hibernate, Timeout) + of + {ok,NewDebug,NewHibernate,NewTimeout,_,_} -> + loop_event_result( + Parent, NewDebug, S, Events, + State, NewData, P, Event, NextState, + NewHibernate, NewTimeout, Postpone, NextEvents); + {Class,Reason,Stacktrace} -> + terminate( + Class, Reason, Stacktrace, + Debug, S, [Event|Events], NextState, NewData, P) + end. + +loop_event_result( + Parent, Debug, S, Events, + State, NewData, P0, Event, NextState, + Hibernate, Timeout, Postpone, NextEvents) -> %% %% All options have been collected and next_events are buffered. %% Do the actual state transition. @@ -1252,44 +1316,21 @@ loop_event_actions( {lists:reverse(P1, Events),[]} end, %% Place next events first in queue - Q3 = lists:reverse(NextEvents, Q2), - %% State entry events - Q = - case SEEvents of - true -> - %% Generate state entry events - case - (NextState =/= State) - orelse maps:is_key(init_state, S) - of - true -> - %% State change or initial state - [{enter,State}|Q3]; - false -> - Q3 - end; - false -> - Q3 - end, + Q = lists:reverse(NextEvents, Q2), %% NewDebug = sys_debug( Debug, S, State, case Postpone of true -> - {postpone,Event,NextState}; + {postpone,Event,State}; false -> - {consume,Event,NextState} + {consume,Event,State} end), loop_events( Parent, NewDebug, %% Avoid infinite loop in initial state with state entry events - case maps:is_key(init_state, S) of - true -> - maps:remove(init_state, S); - false -> - S - end, + maps:remove(init_state, S), Q, NextState, NewData, P, Hibernate, Timeout). %%--------------------------------------------------------------------------- @@ -1369,7 +1410,7 @@ error_info( Class, Reason, Stacktrace, #{name := Name, callback_mode := CallbackMode, - state_entry_events := SEEvents}, + state_enter := StateEnter}, Q, P, FmtData) -> {FixedReason,FixedStacktrace} = case Stacktrace of @@ -1397,9 +1438,9 @@ error_info( _ -> {Reason,Stacktrace} end, CBMode = - case SEEvents of + case StateEnter of true -> - [CallbackMode,state_entry_events]; + [CallbackMode,state_enter]; false -> CallbackMode end, @@ -1471,3 +1512,8 @@ format_status_default(Opt, State, Data) -> _ -> [{data,[{"State",StateData}]}] end. + +listify(Item) when is_list(Item) -> + Item; +listify(Item) -> + [Item]. -- cgit v1.2.3 From 800265f49f912dcf66846b13aa8032bf2f380caf Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Fri, 30 Sep 2016 11:17:22 +0200 Subject: Improve docs and types --- lib/stdlib/src/gen_statem.erl | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) (limited to 'lib/stdlib/src') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index aedcfc932f..9f5573af86 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -142,7 +142,7 @@ NextStateName :: state_name(), NewData :: data(), Actions :: [action()] | action()} | - common_state_callback_result(). + keep_state_callback_result(). -type state_function_enter_result() :: {'next_state', % {next_state,NextStateName,NewData,[]} NextStateName :: state_name(), @@ -151,7 +151,7 @@ NextStateName :: state_name(), NewData :: data(), Actions :: [enter_action()] | enter_action()} | - common_state_callback_result(). + keep_state_callback_enter_result(). -type handle_event_result() :: {'next_state', % {next_state,NextState,NewData,[]} @@ -161,7 +161,7 @@ NextState :: state(), NewData :: data(), Actions :: [action()] | action()} | - common_state_callback_result(). + keep_state_callback_result(). -type handle_event_enter_result() :: {'next_state', % {next_state,NextState,NewData,[]} NextState :: state(), @@ -170,6 +170,28 @@ NextState :: state(), NewData :: data(), Actions :: [enter_action()] | enter_action()} | + keep_state_callback_enter_result(). + +-type keep_state_callback_result() :: + {'keep_state', % {keep_state,NewData,[]} + NewData :: data()} | + {'keep_state', % Keep state, change data + NewData :: data(), + Actions :: [action()] | action()} | + 'keep_state_and_data' | % {keep_state_and_data,[]} + {'keep_state_and_data', % Keep state and data -> only actions + Actions :: [action()] | action()} | + common_state_callback_result(). + +-type keep_state_callback_enter_result() :: + {'keep_state', % {keep_state,NewData,[]} + NewData :: data()} | + {'keep_state', % Keep state, change data + NewData :: data(), + Actions :: [enter_action()] | enter_action()} | + 'keep_state_and_data' | % {keep_state_and_data,[]} + {'keep_state_and_data', % Keep state and data -> only actions + Actions :: [enter_action()] | enter_action()} | common_state_callback_result(). -type common_state_callback_result() :: @@ -185,15 +207,7 @@ {'stop_and_reply', % Reply then stop the server Reason :: term(), Replies :: [reply_action()] | reply_action(), - NewData :: data()} | - {'keep_state', % {keep_state,NewData,[]} - NewData :: data()} | - {'keep_state', % Keep state, change data - NewData :: data(), - Actions :: [ActionType] | ActionType} | - 'keep_state_and_data' | % {keep_state_and_data,[]} - {'keep_state_and_data', % Keep state and data -> only actions - Actions :: [ActionType] | ActionType}. + NewData :: data()}. %% The state machine init function. It is called only once and -- cgit v1.2.3 From 77e175589b0ee3c1a4c94aef3cdcdf54cd84c53c Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Fri, 30 Sep 2016 18:00:38 +0200 Subject: Implement state timeouts --- lib/stdlib/src/gen_statem.erl | 464 ++++++++++++++++++++++++++---------------- 1 file changed, 288 insertions(+), 176 deletions(-) (limited to 'lib/stdlib/src') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 9f5573af86..bc33be22a2 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -75,7 +75,7 @@ -type event_type() :: {'call',From :: from()} | 'cast' | - 'info' | 'timeout' | 'internal'. + 'info' | 'timeout' | 'state_timeout' | 'internal'. -type callback_mode_result() :: callback_mode() | [callback_mode() | state_enter()]. @@ -95,6 +95,10 @@ %% Generate a ('timeout', EventContent, ...) event after Time %% unless some other event is delivered Time :: timeout(). +-type state_timeout() :: + %% Generate a ('state_timeout', EventContent, ...) event after Time + %% unless the state is changed + Time :: timeout(). -type action() :: %% During a state change: @@ -126,8 +130,10 @@ {'hibernate', Hibernate :: hibernate()} | %% (Timeout :: event_timeout()) | % {timeout,Timeout} - {'timeout', % Set the event timeout option + {'timeout', % Set the event_timeout option Time :: event_timeout(), EventContent :: term()} | + {'state_timeout', % Set the state_timeout option + Time :: state_timeout(), EventContent :: term()} | %% reply_action(). -type reply_action() :: @@ -593,7 +599,8 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) -> %% The values should already have been type checked Name = gen:get_proc_name(Server), Debug = gen:debug_options(Name, Opts), - P = Events = [], + Events = [], + P = [], Event = {internal,init_state}, %% We enforce {postpone,false} to ensure that %% our fake Event gets discarded, thought it might get logged @@ -609,9 +616,12 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) -> state_enter => false, module => Module, name => Name, + state => State, + data => Data, + postponed => P, %% The rest of the fields are set from to the arguments to - %% loop_event_actions/10 when it finally loops back to loop/3 - %% in loop_events_done/8 + %% loop_event_actions/9 when it finally loops back to loop/3 + %% in loop_events_done/9 %% %% Marker for initial state, cleared immediately when used init_state => true @@ -619,13 +629,14 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) -> NewDebug = sys_debug(Debug, S, State, {enter,Event,State}), case call_callback_mode(S) of {ok,NewS} -> + StateTimer = undefined, loop_event_actions( - Parent, NewDebug, NewS, Events, - State, Data, P, Event, State, NewActions); + Parent, NewDebug, NewS, StateTimer, + Events, Event, State, Data, NewActions); {Class,Reason,Stacktrace} -> terminate( Class, Reason, Stacktrace, - NewDebug, S, [Event|Events], State, Data, P) + NewDebug, S, [Event|Events]) end. %%%========================================================================== @@ -647,7 +658,9 @@ init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> proc_lib:init_ack(Starter, {error,Reason}), error_info( Class, Reason, Stacktrace, - #{name => Name, callback_mode => undefined}, + #{name => Name, + callback_mode => undefined, + state_enter => false}, [], [], undefined), erlang:raise(Class, Reason, Stacktrace) end. @@ -678,7 +691,9 @@ init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> proc_lib:init_ack(Starter, {error,Error}), error_info( error, Error, ?STACKTRACE(), - #{name => Name, callback_mode => undefined}, + #{name => Name, + callback_mode => undefined, + state_enter => false}, [], [], undefined), exit(Error) end. @@ -689,12 +704,10 @@ init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> system_continue(Parent, Debug, S) -> loop(Parent, Debug, S). -system_terminate( - Reason, _Parent, Debug, - #{state := State, data := Data, postponed := P} = S) -> +system_terminate(Reason, _Parent, Debug, S) -> terminate( exit, Reason, ?STACKTRACE(), - Debug, S, [], State, Data, P). + Debug, S, []). system_code_change( #{module := Module, @@ -731,7 +744,7 @@ system_replace_state( format_status( Opt, [PDict,SysState,Parent,Debug, - #{name := Name, postponed := P, state := State, data := Data} = S]) -> + #{name := Name, postponed := P} = S]) -> Header = gen:format_status_header("Status for state machine", Name), Log = sys:get_debug(log, Debug, []), [{header,Header}, @@ -740,7 +753,7 @@ format_status( {"Parent",Parent}, {"Logged Events",Log}, {"Postponed",P}]} | - case format_status(Opt, PDict, S, State, Data) of + case format_status(Opt, PDict, S) of L when is_list(L) -> L; T -> [T] end]. @@ -816,7 +829,8 @@ loop(Parent, Debug, #{hibernate := Hibernate} = S) -> end. %% Entry point for wakeup_from_hibernate/3 -loop_receive(Parent, Debug, #{timer := Timer} = S) -> +loop_receive( + Parent, Debug, #{timer := Timer, state_timer := StateTimer} = S) -> receive Msg -> case Msg of @@ -827,34 +841,23 @@ loop_receive(Parent, Debug, #{timer := Timer} = S) -> sys:handle_system_msg( Req, Pid, Parent, ?MODULE, Debug, S, Hibernate); {'EXIT',Parent,Reason} = EXIT -> - #{state := State, data := Data, postponed := P} = S, %% EXIT is not a 2-tuple and therefore %% not an event and has no event_type(), %% but this will stand out in the crash report... terminate( - exit, Reason, ?STACKTRACE(), - Debug, S, [EXIT], State, Data, P); - {timeout,Timer,Content} when Timer =/= undefined -> + exit, Reason, ?STACKTRACE(), Debug, S, [EXIT]); + {timeout,Timer,Content} + when Timer =/= undefined -> loop_receive_result( - Parent, Debug, S, {timeout,Content}); + Parent, Debug, S, StateTimer, + {timeout,Content}); + {timeout,StateTimer,Content} + when StateTimer =/= undefined -> + loop_receive_result( + Parent, Debug, S, undefined, + {state_timeout,Content}); _ -> - %% Cancel Timer if running - case Timer of - undefined -> - ok; - _ -> - case erlang:cancel_timer(Timer) of - TimeLeft when is_integer(TimeLeft) -> - ok; - false -> - receive - {timeout,Timer,_} -> - ok - after 0 -> - ok - end - end - end, + cancel_timer(Timer), Event = case Msg of {'$gen_call',From,Request} -> @@ -864,71 +867,93 @@ loop_receive(Parent, Debug, #{timer := Timer} = S) -> _ -> {info,Msg} end, - loop_receive_result(Parent, Debug, S, Event) + loop_receive_result( + Parent, Debug, S, StateTimer, Event) end end. -loop_receive_result( - Parent, Debug, - #{state := State, - data := Data, - postponed := P} = S, - Event) -> - %% The engine state map S is now dismantled - %% and will not be restored until we return to loop/3. - %% - %% The fields 'callback_mode', 'module', and 'name' are still valid. - %% The fields 'state', 'data', and 'postponed' are held in arguments. - %% The fields 'timer' and 'hibernate' will be recalculated. +loop_receive_result(Parent, Debug, #{state := State} = S, StateTimer, Event) -> + %% The fields 'timer', 'state_timer' and 'hibernate' + %% are now invalid in state map S - they will be recalculated + %% and restored when we return to loop/3 %% NewDebug = sys_debug(Debug, S, State, {in,Event}), %% Here the queue of not yet handled events is created Events = [], Hibernate = false, - loop_event( - Parent, NewDebug, S, Events, State, Data, P, Event, Hibernate). + loop_event(Parent, NewDebug, S, StateTimer, Events, Event, Hibernate). %% Process the event queue, or if it is empty %% loop back to loop/3 to receive a new event loop_events( - Parent, Debug, S, [Event|Events], - State, Data, P, Hibernate, _Timeout) -> + Parent, Debug, S, StateTimeout, + [Event|Events], _Timeout, State, Data, P, Hibernate) -> %% - %% If there was a state timer requested we just ignore that + %% If there was an event timer requested we just ignore that %% since we have events to handle which cancels the timer loop_event( - Parent, Debug, S, Events, State, Data, P, Event, Hibernate); + Parent, Debug, S, StateTimeout, + Events, Event, State, Data, P, Hibernate); +loop_events( + Parent, Debug, S, {state_timeout,Time,EventContent}, + [] = Events, Timeout, State, Data, P, Hibernate) -> + if + Time =:= 0 -> + %% Simulate an immediate timeout + %% so we do not get the timeout message + %% after any received event + %% + %% This faked event will cancel + %& any not yet started event timer + Event = {state_timeout,EventContent}, + StateTimer = undefined, + loop_event( + Parent, Debug, S, StateTimer, + Events, Event, State, Data, P, Hibernate); + true -> + StateTimer = erlang:start_timer(Time, self(), EventContent), + loop_events( + Parent, Debug, S, StateTimer, + Events, Timeout, State, Data, P, Hibernate) + end; loop_events( - Parent, Debug, S, [], - State, Data, P, Hibernate, Timeout) -> + Parent, Debug, S, StateTimer, + [] = Events, Timeout, State, Data, P, Hibernate) -> case Timeout of {timeout,0,EventContent} -> - %% Immediate timeout - simulate it + %% Simulate an immediate timeout %% so we do not get the timeout message %% after any received event + %% + Event = {timeout,EventContent}, loop_event( - Parent, Debug, S, [], - State, Data, P, {timeout,EventContent}, Hibernate); + Parent, Debug, S, StateTimer, + Events, Event, State, Data, P, Hibernate); {timeout,Time,EventContent} -> - %% Actually start a timer Timer = erlang:start_timer(Time, self(), EventContent), loop_events_done( - Parent, Debug, S, Timer, State, Data, P, Hibernate); + Parent, Debug, S, StateTimer, + State, Data, P, Hibernate, Timer); undefined -> - %% No state timeout has been requested + %% No event timeout has been requested Timer = undefined, loop_events_done( - Parent, Debug, S, Timer, State, Data, P, Hibernate) + Parent, Debug, S, StateTimer, + State, Data, P, Hibernate, Timer) end. -%% -loop_events_done(Parent, Debug, S, Timer, State, Data, P, Hibernate) -> + +%% Back to the top +loop_events_done( + Parent, Debug, S, StateTimer, + State, Data, P, Hibernate, Timer) -> NewS = S#{ - state => State, - data => Data, - postponed => P, + state := State, + data := Data, + postponed := P, hibernate => Hibernate, - timer => Timer}, + timer => Timer, + state_timer => StateTimer}, loop(Parent, Debug, NewS). @@ -936,10 +961,10 @@ loop_events_done(Parent, Debug, S, Timer, State, Data, P, Hibernate) -> call_callback_mode(#{module := Module} = S) -> try Module:callback_mode() of CallbackMode -> - call_callback_mode_result(S, CallbackMode) + callback_mode_result(S, CallbackMode) catch CallbackMode -> - call_callback_mode_result(S, CallbackMode); + callback_mode_result(S, CallbackMode); error:undef -> %% Process undef to check for the simple mistake %% of calling a nonexistent state function @@ -957,7 +982,7 @@ call_callback_mode(#{module := Module} = S) -> {Class,Reason,erlang:get_stacktrace()} end. -call_callback_mode_result(S, CallbackMode) -> +callback_mode_result(S, CallbackMode) -> case parse_callback_mode( if @@ -1060,15 +1085,26 @@ call_state_function( {Class,Reason,erlang:get_stacktrace()} end. +%% Update S and continue loop_event( - Parent, Debug, S, Events, - State, Data, P, {Type,Content} = Event, Hibernate) -> + Parent, Debug, S, StateTimer, + Events, Event, State, Data, P, Hibernate) -> + NewS = + S#{ + state := State, + data := Data, + postponed := P}, + loop_event(Parent, Debug, NewS, StateTimer, Events, Event, Hibernate). + +loop_event( + Parent, Debug, #{state := State, data := Data} = S, StateTimer, + Events, {Type,Content} = Event, Hibernate) -> %% %% If 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 depend on i.e collect garbage which + %% might rely on i.e collect garbage which %% would have happened if we actually hibernated %% and immediately was awakened Hibernate andalso garbage_collect(), @@ -1076,45 +1112,40 @@ loop_event( {ok,Result,NewS} -> {NewData,NextState,Actions} = parse_event_result( - Parent, Debug, NewS, Events, - State, Data, P, Event, - Result, true), + true, Debug, NewS, Result, + Events, Event, State, Data), loop_event_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, Actions); + Parent, Debug, S, StateTimer, + Events, Event, NextState, NewData, Actions); {Class,Reason,Stacktrace} -> terminate( - Class, Reason, Stacktrace, - Debug, S, [Event|Events], State, Data, P) + Class, Reason, Stacktrace, Debug, S, [Event|Events]) end. %% Interpret all callback return variants parse_event_result( - _Parent, Debug, S, Events, State, Data, P, Event, - Result, AllowStateChange) -> + AllowStateChange, Debug, S, Result, Events, Event, State, Data) -> case Result of stop -> terminate( - exit, normal, ?STACKTRACE(), - Debug, S, [Event|Events], State, Data, P); + exit, normal, ?STACKTRACE(), Debug, S, [Event|Events]); {stop,Reason} -> terminate( - exit, Reason, ?STACKTRACE(), - Debug, S, [Event|Events], State, Data, P); + exit, Reason, ?STACKTRACE(), Debug, S, [Event|Events]); {stop,Reason,NewData} -> terminate( exit, Reason, ?STACKTRACE(), - Debug, S, [Event|Events], State, NewData, P); + Debug, S#{data := NewData}, [Event|Events]); {stop_and_reply,Reason,Replies} -> Q = [Event|Events], reply_then_terminate( exit, Reason, ?STACKTRACE(), - Debug, S, Q, State, Data, P, Replies); + Debug, S, Q, Replies); {stop_and_reply,Reason,Replies,NewData} -> Q = [Event|Events], reply_then_terminate( exit, Reason, ?STACKTRACE(), - Debug, S, Q, State, NewData, P, Replies); + Debug, S#{data := NewData}, Q, Replies); {next_state,State,NewData} -> {NewData,State,[]}; {next_state,NextState,NewData} when AllowStateChange -> @@ -1136,31 +1167,35 @@ parse_event_result( error, {bad_return_from_state_function,Result}, ?STACKTRACE(), - Debug, S, [Event|Events], State, Data, P) + Debug, S, [Event|Events]) end. -parse_enter_actions(Debug, S, State, Actions, Hibernate, Timeout) -> +parse_enter_actions( + Debug, S, State, Actions, + Hibernate, Timeout, StateTimeout) -> Postpone = forbidden, NextEvents = forbidden, parse_actions( Debug, S, State, listify(Actions), - Hibernate, Timeout, Postpone, NextEvents). + Hibernate, Timeout, StateTimeout, Postpone, NextEvents). parse_actions(Debug, S, State, Actions) -> - Postpone = false, Hibernate = false, Timeout = undefined, + StateTimeout = undefined, + Postpone = false, NextEvents = [], parse_actions( Debug, S, State, listify(Actions), - Hibernate, Timeout, Postpone, NextEvents). + Hibernate, Timeout, StateTimeout, Postpone, NextEvents). %% parse_actions( - Debug, _S, _State, [], Hibernate, Timeout, Postpone, NextEvents) -> - {ok,Debug,Hibernate,Timeout,Postpone,NextEvents}; + Debug, _S, _State, [], + Hibernate, Timeout, StateTimeout, Postpone, NextEvents) -> + {ok,Debug,Hibernate,Timeout,StateTimeout,Postpone,NextEvents}; parse_actions( Debug, S, State, [Action|Actions], - Hibernate, Timeout, Postpone, NextEvents) -> + Hibernate, Timeout, StateTimeout, Postpone, NextEvents) -> case Action of %% Actual actions {reply,From,Reply} -> @@ -1169,7 +1204,8 @@ parse_actions( NewDebug = do_reply(Debug, S, State, From, Reply), parse_actions( NewDebug, S, State, Actions, - Hibernate, Timeout, Postpone, NextEvents); + Hibernate, Timeout, StateTimeout, + Postpone, NextEvents); false -> {error, {bad_action_from_state_function,Action}, @@ -1179,7 +1215,7 @@ parse_actions( {hibernate,NewHibernate} when is_boolean(NewHibernate) -> parse_actions( Debug, S, State, Actions, - NewHibernate, Timeout, Postpone, NextEvents); + NewHibernate, Timeout, StateTimeout, Postpone, NextEvents); {hibernate,_} -> {error, {bad_action_from_state_function,Action}, @@ -1187,15 +1223,25 @@ parse_actions( hibernate -> parse_actions( Debug, S, State, Actions, - true, Timeout, Postpone, NextEvents); + true, Timeout, StateTimeout, Postpone, NextEvents); + {state_timeout,Time,_} = NewStateTimeout + when is_integer(Time), Time >= 0; + Time =:= infinity -> + parse_actions( + Debug, S, State, Actions, + Hibernate, Timeout, NewStateTimeout, Postpone, NextEvents); + {state_timeout,_,_} -> + {error, + {bad_action_from_state_function,Action}, + ?STACKTRACE()}; {timeout,infinity,_} -> % Clear timer - it will never trigger parse_actions( Debug, S, State, Actions, - Hibernate, undefined, Postpone, NextEvents); + Hibernate, undefined, StateTimeout, Postpone, NextEvents); {timeout,Time,_} = NewTimeout when is_integer(Time), Time >= 0 -> parse_actions( Debug, S, State, Actions, - Hibernate, NewTimeout, Postpone, NextEvents); + Hibernate, NewTimeout, StateTimeout, Postpone, NextEvents); {timeout,_,_} -> {error, {bad_action_from_state_function,Action}, @@ -1203,17 +1249,17 @@ parse_actions( infinity -> % Clear timer - it will never trigger parse_actions( Debug, S, State, Actions, - Hibernate, undefined, Postpone, NextEvents); + Hibernate, undefined, StateTimeout, Postpone, NextEvents); Time when is_integer(Time), Time >= 0 -> NewTimeout = {timeout,Time,Time}, parse_actions( Debug, S, State, Actions, - Hibernate, NewTimeout, Postpone, NextEvents); + Hibernate, NewTimeout, StateTimeout, Postpone, NextEvents); {postpone,NewPostpone} when is_boolean(NewPostpone), Postpone =/= forbidden -> parse_actions( Debug, S, State, Actions, - Hibernate, Timeout, NewPostpone, NextEvents); + Hibernate, Timeout, StateTimeout, NewPostpone, NextEvents); {postpone,_} -> {error, {bad_action_from_state_function,Action}, @@ -1221,7 +1267,7 @@ parse_actions( postpone when Postpone =/= forbidden -> parse_actions( Debug, S, State, Actions, - Hibernate, Timeout, true, NextEvents); + Hibernate, Timeout, StateTimeout, true, NextEvents); {next_event,Type,Content} -> case event_type(Type) of true when NextEvents =/= forbidden -> @@ -1229,8 +1275,8 @@ parse_actions( sys_debug(Debug, S, State, {in,{Type,Content}}), parse_actions( NewDebug, S, State, Actions, - Hibernate, Timeout, Postpone, - [{Type,Content}|NextEvents]); + Hibernate, Timeout, StateTimeout, + Postpone, [{Type,Content}|NextEvents]); _ -> {error, {bad_action_from_state_function,Action}, @@ -1243,94 +1289,143 @@ parse_actions( end. loop_event_actions( - Parent, Debug, #{state_enter := StateEnter} = S, Events, - State, NewData, P, Event, NextState, Actions) -> + Parent, Debug, + #{state := State, state_enter := StateEnter} = S, StateTimer, + Events, Event, NextState, NewData, Actions) -> case parse_actions(Debug, S, State, Actions) of - {ok,NewDebug,Hibernate,Timeout,Postpone,NextEvents} -> - case - StateEnter andalso - ((NextState =/= State) - orelse maps:is_key(init_state, S)) of - true -> + {ok,NewDebug,Hibernate,Timeout,StateTimeout,Postpone,NextEvents} -> + if + StateEnter, NextState =/= State -> loop_event_enter( - Parent, NewDebug, S, Events, - State, NewData, P, Event, NextState, - Hibernate, Timeout, Postpone, NextEvents); - false -> + Parent, NewDebug, S, StateTimer, + Events, Event, NextState, NewData, + Hibernate, Timeout, StateTimeout, Postpone, NextEvents); + StateEnter -> + case maps:is_key(init_state, S) of + true -> + %% Avoid infinite loop in initial state + %% with state entry events + NewS = maps:remove(init_state, S), + loop_event_enter( + Parent, NewDebug, NewS, StateTimer, + Events, Event, NextState, NewData, + Hibernate, Timeout, StateTimeout, + Postpone, NextEvents); + false -> + loop_event_result( + Parent, NewDebug, S, StateTimer, + Events, Event, NextState, NewData, + Hibernate, Timeout, StateTimeout, + Postpone, NextEvents) + end; + true -> loop_event_result( - Parent, NewDebug, S, Events, - State, NewData, P, Event, NextState, - Hibernate, Timeout, Postpone, NextEvents) + Parent, NewDebug, S, StateTimer, + Events, Event, NextState, NewData, + Hibernate, Timeout, StateTimeout, Postpone, NextEvents) end; {Class,Reason,Stacktrace} -> terminate( Class, Reason, Stacktrace, - Debug, S, [Event|Events], State, NewData, P) + Debug, S#{data := NewData}, [Event|Events]) end. loop_event_enter( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, - Hibernate, Timeout, Postpone, NextEvents) -> + Parent, Debug, #{state := State} = S, StateTimer, + Events, Event, NextState, NewData, + Hibernate, Timeout, StateTimeout, Postpone, NextEvents) -> case call_state_function(S, enter, State, NextState, NewData) of {ok,Result,NewS} -> {NewerData,_,Actions} = parse_event_result( - Parent, Debug, NewS, Events, - NextState, NewData, P, Event, - Result, false), + false, Debug, NewS, Result, + Events, Event, NextState, NewData), loop_event_enter_actions( - Parent, Debug, NewS, Events, - State, NewerData, P, Event, NextState, - Hibernate, Timeout, Postpone, NextEvents, Actions); + Parent, Debug, NewS, StateTimer, + Events, Event, NextState, NewerData, + Hibernate, Timeout, StateTimeout, Postpone, NextEvents, Actions); {Class,Reason,Stacktrace} -> terminate( Class, Reason, Stacktrace, - Debug, S, [Event|Events], NextState, NewData, P) + Debug, S#{state := NextState, data := NewData}, + [Event|Events]) end. loop_event_enter_actions( - Parent, Debug, S, Events, - State, NewData, P, Event, NextState, - Hibernate, Timeout, Postpone, NextEvents, Actions) -> + Parent, Debug, S, StateTimer, + Events, Event, NextState, NewData, + Hibernate, Timeout, StateTimeout, Postpone, NextEvents, Actions) -> case - parse_enter_actions(Debug, S, NextState, Actions, Hibernate, Timeout) + parse_enter_actions( + Debug, S, NextState, Actions, + Hibernate, Timeout, StateTimeout) of - {ok,NewDebug,NewHibernate,NewTimeout,_,_} -> + {ok,NewDebug,NewHibernate,NewTimeout,NewStateTimeout,_,_} -> loop_event_result( - Parent, NewDebug, S, Events, - State, NewData, P, Event, NextState, - NewHibernate, NewTimeout, Postpone, NextEvents); + Parent, NewDebug, S, StateTimer, + Events, Event, NextState, NewData, + NewHibernate, NewTimeout, NewStateTimeout, Postpone, NextEvents); {Class,Reason,Stacktrace} -> terminate( Class, Reason, Stacktrace, - Debug, S, [Event|Events], NextState, NewData, P) + Debug, S#{state := NextState, data := NewData}, + [Event|Events]) end. loop_event_result( - Parent, Debug, S, Events, - State, NewData, P0, Event, NextState, - Hibernate, Timeout, Postpone, NextEvents) -> + Parent, Debug, + #{state := State, postponed := P_0} = S, StateTimer, + Events, Event, NextState, NewData, + Hibernate, Timeout, StateTimeout, Postpone, NextEvents) -> %% %% All options have been collected and next_events are buffered. %% Do the actual state transition. %% - P1 = % Move current event to postponed if Postpone + NewStateTimeout = + case StateTimeout of + {state_timeout,Time,_} -> + %% New timeout -> cancel timer + case StateTimer of + {state_timeout,_,_} -> + ok; + _ -> + cancel_timer(StateTimer) + end, + case Time of + infinity -> + undefined; + _ -> + StateTimeout + end; + undefined when NextState =/= State -> + %% State change -> cancel timer + case StateTimer of + {state_timeout,_,_} -> + ok; + _ -> + cancel_timer(StateTimer) + end, + undefined; + undefined -> + StateTimer + end, + %% + P_1 = % Move current event to postponed if Postpone case Postpone of true -> - [Event|P0]; + [Event|P_0]; false -> - P0 + P_0 end, - {Q2,P} = % Move all postponed events to queue if state change + {Events_1,NewP} = % Move all postponed events to queue if state change if NextState =:= State -> - {Events,P1}; + {Events,P_1}; true -> - {lists:reverse(P1, Events),[]} + {lists:reverse(P_1, Events),[]} end, %% Place next events first in queue - Q = lists:reverse(NextEvents, Q2), + NewEvents = lists:reverse(NextEvents, Events_1), %% NewDebug = sys_debug( @@ -1341,46 +1436,44 @@ loop_event_result( false -> {consume,Event,State} end), + %% loop_events( - Parent, NewDebug, - %% Avoid infinite loop in initial state with state entry events - maps:remove(init_state, S), - Q, NextState, NewData, P, Hibernate, Timeout). + Parent, NewDebug, S, NewStateTimeout, + NewEvents, Timeout, NextState, NewData, NewP, Hibernate). %%--------------------------------------------------------------------------- %% Server helpers reply_then_terminate( Class, Reason, Stacktrace, - Debug, S, Q, State, Data, P, Replies) -> + Debug, #{state := State} = S, Q, Replies) -> if is_list(Replies) -> do_reply_then_terminate( Class, Reason, Stacktrace, - Debug, S, Q, State, Data, P, Replies); + Debug, S, Q, Replies, State); true -> do_reply_then_terminate( Class, Reason, Stacktrace, - Debug, S, Q, State, Data, P, [Replies]) + Debug, S, Q, [Replies], State) end. %% do_reply_then_terminate( - Class, Reason, Stacktrace, Debug, S, Q, State, Data, P, []) -> - terminate(Class, Reason, Stacktrace, Debug, S, Q, State, Data, P); + Class, Reason, Stacktrace, Debug, S, Q, [], _State) -> + terminate(Class, Reason, Stacktrace, Debug, S, Q); do_reply_then_terminate( - Class, Reason, Stacktrace, Debug, S, Q, State, Data, P, [R|Rs]) -> + Class, Reason, Stacktrace, Debug, S, Q, [R|Rs], State) -> case R of {reply,{_To,_Tag}=From,Reply} -> NewDebug = do_reply(Debug, S, State, From, Reply), do_reply_then_terminate( - Class, Reason, Stacktrace, - NewDebug, S, Q, State, Data, P, Rs); + Class, Reason, Stacktrace, NewDebug, S, Q, Rs, State); _ -> terminate( error, {bad_reply_action_from_state_function,R}, ?STACKTRACE(), - Debug, S, Q, State, Data, P) + Debug, S, Q) end. do_reply(Debug, S, State, From, Reply) -> @@ -1390,7 +1483,9 @@ do_reply(Debug, S, State, From, Reply) -> terminate( Class, Reason, Stacktrace, - Debug, #{module := Module} = S, Q, State, Data, P) -> + Debug, + #{module := Module, state := State, data := Data, postponed := P} = S, + Q) -> try Module:terminate(Reason, State, Data) of _ -> ok catch @@ -1399,7 +1494,7 @@ terminate( ST = erlang:get_stacktrace(), error_info( C, R, ST, S, Q, P, - format_status(terminate, get(), S, State, Data)), + format_status(terminate, get(), S)), sys:print_log(Debug), erlang:raise(C, R, ST) end, @@ -1410,7 +1505,7 @@ terminate( _ -> error_info( Class, Reason, Stacktrace, S, Q, P, - format_status(terminate, get(), S, State, Data)), + format_status(terminate, get(), S)), sys:print_log(Debug) end, case Stacktrace of @@ -1502,7 +1597,9 @@ error_info( %% Call Module:format_status/2 or return a default value -format_status(Opt, PDict, #{module := Module}, State, Data) -> +format_status( + Opt, PDict, + #{module := Module, state := State, data := Data}) -> case erlang:function_exported(Module, format_status, 2) of true -> try Module:format_status(Opt, [PDict,State,Data]) @@ -1531,3 +1628,18 @@ listify(Item) when is_list(Item) -> Item; listify(Item) -> [Item]. + +cancel_timer(undefined) -> + ok; +cancel_timer(TRef) -> + case erlang:cancel_timer(TRef) of + TimeLeft when is_integer(TimeLeft) -> + ok; + false -> + receive + {timeout,TRef,_} -> + ok + after 0 -> + ok + end + end. -- cgit v1.2.3 From f4de3f5887be010db178a178e1f20027f3e5d22b Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Wed, 12 Oct 2016 17:49:26 +0200 Subject: Use parameterized types --- lib/stdlib/src/gen_statem.erl | 94 ++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 59 deletions(-) (limited to 'lib/stdlib/src') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index bc33be22a2..5c750cb93d 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -44,18 +44,20 @@ -export( [wakeup_from_hibernate/3]). -%% Type exports for templates +%% Type exports for templates and callback modules -export_type( [event_type/0, - state_name/0, + init_result/0, callback_mode_result/0, state_function_result/0, - state_function_enter_result/0, handle_event_result/0, - handle_event_enter_result/0, + state_enter_result/1, + event_handler_result/1, + reply_action/0, + enter_action/0, action/0]). -%% Fix problem for doc build +%% Type that is exported just to be documented -export_type([transition_option/0]). %%%========================================================================== @@ -66,7 +68,7 @@ {To :: pid(), Tag :: term()}. % Reply-to specifier for call -type state() :: - state_name() | % For StateName/3 callback functios + state_name() | % For StateName/3 callback functions term(). % For handle_event/4 callback function -type state_name() :: atom(). @@ -140,67 +142,45 @@ {'reply', % Reply to a caller From :: from(), Reply :: term()}. --type state_function_result() :: - {'next_state', % {next_state,NextStateName,NewData,[]} - NextStateName :: state_name(), - NewData :: data()} | - {'next_state', % State transition, maybe to the same state - NextStateName :: state_name(), - NewData :: data(), - Actions :: [action()] | action()} | - keep_state_callback_result(). --type state_function_enter_result() :: - {'next_state', % {next_state,NextStateName,NewData,[]} - NextStateName :: state_name(), - NewData :: data()} | - {'next_state', % State transition, maybe to the same state - NextStateName :: state_name(), - NewData :: data(), - Actions :: [enter_action()] | enter_action()} | - keep_state_callback_enter_result(). +-type init_result() :: + {ok, state(), data()} | + {ok, state(), data(), [action()] | action()} | + 'ignore' | + {'stop', Reason :: term()}. +%% Old, not advertised +-type state_function_result() :: + event_handler_result(state_name()). -type handle_event_result() :: + event_handler_result(state()). +%% +-type state_enter_result(StateType) :: {'next_state', % {next_state,NextState,NewData,[]} - NextState :: state(), + State :: StateType, NewData :: data()} | {'next_state', % State transition, maybe to the same state - NextState :: state(), + State :: StateType, NewData :: data(), - Actions :: [action()] | action()} | - keep_state_callback_result(). --type handle_event_enter_result() :: + Actions :: [enter_action()] | enter_action()} | + state_callback_result(enter_action()). +-type event_handler_result(StateType) :: {'next_state', % {next_state,NextState,NewData,[]} - NextState :: state(), + NextState :: StateType, NewData :: data()} | {'next_state', % State transition, maybe to the same state - NextState :: state(), + NextState :: StateType, NewData :: data(), - Actions :: [enter_action()] | enter_action()} | - keep_state_callback_enter_result(). - --type keep_state_callback_result() :: - {'keep_state', % {keep_state,NewData,[]} - NewData :: data()} | - {'keep_state', % Keep state, change data - NewData :: data(), - Actions :: [action()] | action()} | - 'keep_state_and_data' | % {keep_state_and_data,[]} - {'keep_state_and_data', % Keep state and data -> only actions Actions :: [action()] | action()} | - common_state_callback_result(). - --type keep_state_callback_enter_result() :: + state_callback_result(action()). +-type state_callback_result(ActionType) :: {'keep_state', % {keep_state,NewData,[]} NewData :: data()} | {'keep_state', % Keep state, change data NewData :: data(), - Actions :: [enter_action()] | enter_action()} | + Actions :: [ActionType] | ActionType} | 'keep_state_and_data' | % {keep_state_and_data,[]} {'keep_state_and_data', % Keep state and data -> only actions - Actions :: [enter_action()] | enter_action()} | - common_state_callback_result(). - --type common_state_callback_result() :: + Actions :: [ActionType] | ActionType} | 'stop' | % {stop,normal} {'stop', % Stop the server Reason :: term()} | @@ -220,11 +200,7 @@ %% the server is not running until this function has returned %% an {ok, ...} tuple. Thereafter the state callbacks are called %% for all events to this server. --callback init(Args :: term()) -> - {ok, state(), data()} | - {ok, state(), data(), [action()] | action()} | - 'ignore' | - {'stop', Reason :: term()}. +-callback init(Args :: term()) -> init_result(). %% This callback shall return the callback mode of the callback module. %% @@ -244,11 +220,11 @@ 'enter', OldStateName :: state_name(), Data :: data()) -> - state_function_enter_result(); + state_enter_result('state_name'); (event_type(), EventContent :: term(), Data :: data()) -> - state_function_result(). + event_handler_result(state_name()). %% %% State callback for all states %% when callback_mode() =:= handle_event_function. @@ -257,12 +233,12 @@ OldState :: state(), State :: state(), % Current state Data :: data()) -> - handle_event_enter_result(); + state_enter_result(state()); (event_type(), EventContent :: term(), State :: state(), % Current state Data :: data()) -> - handle_event_result(). + event_handler_result(state()). %% Clean up before the server terminates. -callback terminate( -- cgit v1.2.3 From 5e2d802e29f0a8f81de297f9a3e3922f2d6cd6c0 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Fri, 14 Oct 2016 10:08:49 +0200 Subject: Fix race condition in cancel_timer/1 --- lib/stdlib/src/gen_statem.erl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'lib/stdlib/src') diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 5c750cb93d..17d1ebecec 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -1609,13 +1609,14 @@ cancel_timer(undefined) -> ok; cancel_timer(TRef) -> case erlang:cancel_timer(TRef) of - TimeLeft when is_integer(TimeLeft) -> - ok; false -> + %% We have to assume that TRef is the ref of a running timer + %% and if so the timer has expired + %% hence we must wait for the timeout message receive {timeout,TRef,_} -> ok - after 0 -> - ok - end + end; + _TimeLeft -> + ok end. -- cgit v1.2.3