%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2018. 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. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(supervisor). -behaviour(gen_server). %% External exports -export([start_link/2, start_link/3, start_child/2, restart_child/2, delete_child/2, terminate_child/2, which_children/1, count_children/1, check_childspecs/1, get_childspec/2]). %% Internal exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, format_status/2]). %% For release_handler only -export([get_callback_module/1]). -include("logger.hrl"). -define(report_error(Error, Reason, Child, SupName), ?LOG_ERROR(#{label=>{supervisor,Error}, report=>[{supervisor,SupName}, {errorContext,Error}, {reason,Reason}, {offender,extract_child(Child)}]}, #{domain=>[otp,sasl], report_cb=>fun logger:format_otp_report/1, logger_formatter=>#{title=>"SUPERVISOR REPORT"}, error_logger=>#{tag=>error_report, type=>supervisor_report}})). %%-------------------------------------------------------------------------- -export_type([sup_flags/0, child_spec/0, startchild_ret/0, strategy/0]). %%-------------------------------------------------------------------------- -type child() :: 'undefined' | pid(). -type child_id() :: term(). -type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}. -type modules() :: [module()] | 'dynamic'. -type restart() :: 'permanent' | 'transient' | 'temporary'. -type shutdown() :: 'brutal_kill' | timeout(). -type worker() :: 'worker' | 'supervisor'. -type sup_name() :: {'local', Name :: atom()} | {'global', Name :: atom()} | {'via', Module :: module(), Name :: any()}. -type sup_ref() :: (Name :: atom()) | {Name :: atom(), Node :: node()} | {'global', Name :: atom()} | {'via', Module :: module(), Name :: any()} | pid(). -type child_spec() :: #{id := child_id(), % mandatory start := mfargs(), % mandatory restart => restart(), % optional shutdown => shutdown(), % optional type => worker(), % optional modules => modules()} % optional | {Id :: child_id(), StartFunc :: mfargs(), Restart :: restart(), Shutdown :: shutdown(), Type :: worker(), Modules :: modules()}. -type strategy() :: 'one_for_all' | 'one_for_one' | 'rest_for_one' | 'simple_one_for_one'. -type sup_flags() :: #{strategy => strategy(), % optional intensity => non_neg_integer(), % optional period => pos_integer()} % optional | {RestartStrategy :: strategy(), Intensity :: non_neg_integer(), Period :: pos_integer()}. -type children() :: {Ids :: [child_id()], Db :: #{child_id() => child_rec()}}. %%-------------------------------------------------------------------------- %% Defaults -define(default_flags, #{strategy => one_for_one, intensity => 1, period => 5}). -define(default_child_spec, #{restart => permanent, type => worker}). %% Default 'shutdown' is 5000 for workers and infinity for supervisors. %% Default 'modules' is [M], where M comes from the child's start {M,F,A}. %%-------------------------------------------------------------------------- -record(child, {% pid is undefined when child is not running pid = undefined :: child() | {restarting, pid() | undefined} | [pid()], id :: child_id(), mfargs :: mfargs(), restart_type :: restart(), shutdown :: shutdown(), child_type :: worker(), modules = [] :: modules()}). -type child_rec() :: #child{}. -record(state, {name, strategy :: strategy() | 'undefined', children = {[],#{}} :: children(), % Ids in start order dynamics :: {'maps', #{pid() => list()}} | {'sets', sets:set(pid())} | 'undefined', intensity :: non_neg_integer() | 'undefined', period :: pos_integer() | 'undefined', restarts = [], dynamic_restarts = 0 :: non_neg_integer(), module, args}). -type state() :: #state{}. -define(is_simple(State), State#state.strategy =:= simple_one_for_one). -define(is_temporary(_Child_), _Child_#child.restart_type=:=temporary). -define(is_transient(_Child_), _Child_#child.restart_type=:=transient). -define(is_permanent(_Child_), _Child_#child.restart_type=:=permanent). -callback init(Args :: term()) -> {ok, {SupFlags :: sup_flags(), [ChildSpec :: child_spec()]}} | ignore. -define(restarting(_Pid_), {restarting,_Pid_}). %%% --------------------------------------------------- %%% This is a general process supervisor built upon gen_server.erl. %%% Servers/processes should/could also be built using gen_server.erl. %%% SupName = {local, atom()} | {global, atom()}. %%% --------------------------------------------------- -type startlink_err() :: {'already_started', pid()} | {'shutdown', term()} | term(). -type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}. -spec start_link(Module, Args) -> startlink_ret() when Module :: module(), Args :: term(). start_link(Mod, Args) -> gen_server:start_link(supervisor, {self, Mod, Args}, []). -spec start_link(SupName, Module, Args) -> startlink_ret() when SupName :: sup_name(), Module :: module(), Args :: term(). start_link(SupName, Mod, Args) -> gen_server:start_link(SupName, supervisor, {SupName, Mod, Args}, []). %%% --------------------------------------------------- %%% Interface functions. %%% --------------------------------------------------- -type startchild_err() :: 'already_present' | {'already_started', Child :: child()} | term(). -type startchild_ret() :: {'ok', Child :: child()} | {'ok', Child :: child(), Info :: term()} | {'error', startchild_err()}. -spec start_child(SupRef, ChildSpec) -> startchild_ret() when SupRef :: sup_ref(), ChildSpec :: child_spec() | (List :: [term()]). start_child(Supervisor, ChildSpec) -> call(Supervisor, {start_child, ChildSpec}). -spec restart_child(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: child_id(), Result :: {'ok', Child :: child()} | {'ok', Child :: child(), Info :: term()} | {'error', Error}, Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one' | term(). restart_child(Supervisor, Id) -> call(Supervisor, {restart_child, Id}). -spec delete_child(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: child_id(), Result :: 'ok' | {'error', Error}, Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one'. delete_child(Supervisor, Id) -> call(Supervisor, {delete_child, Id}). %%----------------------------------------------------------------- %% Func: terminate_child/2 %% Returns: ok | {error, Reason} %% Note that the child is *always* terminated in some %% way (maybe killed). %%----------------------------------------------------------------- -spec terminate_child(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: pid() | child_id(), Result :: 'ok' | {'error', Error}, Error :: 'not_found' | 'simple_one_for_one'. terminate_child(Supervisor, Id) -> call(Supervisor, {terminate_child, Id}). -spec get_childspec(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: pid() | child_id(), Result :: {'ok', child_spec()} | {'error', Error}, Error :: 'not_found'. get_childspec(Supervisor, Id) -> call(Supervisor, {get_childspec, Id}). -spec which_children(SupRef) -> [{Id,Child,Type,Modules}] when SupRef :: sup_ref(), Id :: child_id() | undefined, Child :: child() | 'restarting', Type :: worker(), Modules :: modules(). which_children(Supervisor) -> call(Supervisor, which_children). -spec count_children(SupRef) -> PropListOfCounts when SupRef :: sup_ref(), PropListOfCounts :: [Count], Count :: {specs, ChildSpecCount :: non_neg_integer()} | {active, ActiveProcessCount :: non_neg_integer()} | {supervisors, ChildSupervisorCount :: non_neg_integer()} |{workers, ChildWorkerCount :: non_neg_integer()}. count_children(Supervisor) -> call(Supervisor, count_children). call(Supervisor, Req) -> gen_server:call(Supervisor, Req, infinity). -spec check_childspecs(ChildSpecs) -> Result when ChildSpecs :: [child_spec()], Result :: 'ok' | {'error', Error :: term()}. check_childspecs(ChildSpecs) when is_list(ChildSpecs) -> case check_startspec(ChildSpecs) of {ok, _} -> ok; Error -> {error, Error} end; check_childspecs(X) -> {error, {badarg, X}}. %%%----------------------------------------------------------------- %%% Called by release_handler during upgrade -spec get_callback_module(Pid) -> Module when Pid :: pid(), Module :: atom(). get_callback_module(Pid) -> {status, _Pid, {module, _Mod}, [_PDict, _SysState, _Parent, _Dbg, Misc]} = sys:get_status(Pid), case lists:keyfind(supervisor, 1, Misc) of {supervisor, [{"Callback", Mod}]} -> Mod; _ -> [_Header, _Data, {data, [{"State", State}]} | _] = Misc, State#state.module end. %%% --------------------------------------------------- %%% %%% Initialize the supervisor. %%% %%% --------------------------------------------------- -type init_sup_name() :: sup_name() | 'self'. -type stop_rsn() :: {'shutdown', term()} | {'bad_return', {module(),'init', term()}} | {'bad_start_spec', term()} | {'start_spec', term()} | {'supervisor_data', term()}. -spec init({init_sup_name(), module(), [term()]}) -> {'ok', state()} | 'ignore' | {'stop', stop_rsn()}. init({SupName, Mod, Args}) -> process_flag(trap_exit, true), case Mod:init(Args) of {ok, {SupFlags, StartSpec}} -> case init_state(SupName, SupFlags, Mod, Args) of {ok, State} when ?is_simple(State) -> init_dynamic(State, StartSpec); {ok, State} -> init_children(State, StartSpec); Error -> {stop, {supervisor_data, Error}} end; ignore -> ignore; Error -> {stop, {bad_return, {Mod, init, Error}}} end. init_children(State, StartSpec) -> SupName = State#state.name, case check_startspec(StartSpec) of {ok, Children} -> case start_children(Children, SupName) of {ok, NChildren} -> {ok, State#state{children = NChildren}}; {error, NChildren, Reason} -> _ = terminate_children(NChildren, SupName), {stop, {shutdown, Reason}} end; Error -> {stop, {start_spec, Error}} end. init_dynamic(State, [StartSpec]) -> case check_startspec([StartSpec]) of {ok, Children} -> {ok, dyn_init(State#state{children = Children})}; Error -> {stop, {start_spec, Error}} end; init_dynamic(_State, StartSpec) -> {stop, {bad_start_spec, StartSpec}}. %%----------------------------------------------------------------- %% Func: start_children/2 %% Args: Children = children() % Ids in start order %% SupName = {local, atom()} | {global, atom()} | {pid(), Mod} %% Purpose: Start all children. The new map contains #child's %% with pids. %% Returns: {ok, NChildren} | {error, NChildren, Reason} %% NChildren = children() % Ids in termination order %% (reversed start order) %%----------------------------------------------------------------- start_children(Children, SupName) -> Start = fun(Id,Child) -> case do_start_child(SupName, Child) of {ok, undefined} when ?is_temporary(Child) -> remove; {ok, Pid} -> {update,Child#child{pid = Pid}}; {ok, Pid, _Extra} -> {update,Child#child{pid = Pid}}; {error, Reason} -> ?report_error(start_error, Reason, Child, SupName), {abort,{failed_to_start_child,Id,Reason}} end end, children_map(Start,Children). do_start_child(SupName, Child) -> #child{mfargs = {M, F, Args}} = Child, case do_start_child_i(M, F, Args) of {ok, Pid} when is_pid(Pid) -> NChild = Child#child{pid = Pid}, report_progress(NChild, SupName), {ok, Pid}; {ok, Pid, Extra} when is_pid(Pid) -> NChild = Child#child{pid = Pid}, report_progress(NChild, SupName), {ok, Pid, Extra}; Other -> Other end. do_start_child_i(M, F, A) -> case catch apply(M, F, A) of {ok, Pid} when is_pid(Pid) -> {ok, Pid}; {ok, Pid, Extra} when is_pid(Pid) -> {ok, Pid, Extra}; ignore -> {ok, undefined}; {error, Error} -> {error, Error}; What -> {error, What} end. %%% --------------------------------------------------- %%% %%% Callback functions. %%% %%% --------------------------------------------------- -type call() :: 'which_children' | 'count_children' | {_, _}. % XXX: refine -spec handle_call(call(), term(), state()) -> {'reply', term(), state()}. handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> Child = get_dynamic_child(State), #child{mfargs = {M, F, A}} = Child, Args = A ++ EArgs, case do_start_child_i(M, F, Args) of {ok, undefined} -> {reply, {ok, undefined}, State}; {ok, Pid} -> NState = dyn_store(Pid, Args, State), {reply, {ok, Pid}, NState}; {ok, Pid, Extra} -> NState = dyn_store(Pid, Args, State), {reply, {ok, Pid, Extra}, NState}; What -> {reply, What, State} end; handle_call({start_child, ChildSpec}, _From, State) -> case check_childspec(ChildSpec) of {ok, Child} -> {Resp, NState} = handle_start_child(Child, State), {reply, Resp, NState}; What -> {reply, {error, What}, State} end; %% terminate_child for simple_one_for_one can only be done with pid handle_call({terminate_child, Id}, _From, State) when not is_pid(Id), ?is_simple(State) -> {reply, {error, simple_one_for_one}, State}; handle_call({terminate_child, Id}, _From, State) -> case find_child(Id, State) of {ok, Child} -> do_terminate(Child, State#state.name), {reply, ok, del_child(Child, State)}; error -> {reply, {error, not_found}, State} end; %% restart_child request is invalid for simple_one_for_one supervisors handle_call({restart_child, _Id}, _From, State) when ?is_simple(State) -> {reply, {error, simple_one_for_one}, State}; handle_call({restart_child, Id}, _From, State) -> case find_child(Id, State) of {ok, Child} when Child#child.pid =:= undefined -> case do_start_child(State#state.name, Child) of {ok, Pid} -> NState = set_pid(Pid, Id, State), {reply, {ok, Pid}, NState}; {ok, Pid, Extra} -> NState = set_pid(Pid, Id, State), {reply, {ok, Pid, Extra}, NState}; Error -> {reply, Error, State} end; {ok, #child{pid=?restarting(_)}} -> {reply, {error, restarting}, State}; {ok, _} -> {reply, {error, running}, State}; _ -> {reply, {error, not_found}, State} end; %% delete_child request is invalid for simple_one_for_one supervisors handle_call({delete_child, _Id}, _From, State) when ?is_simple(State) -> {reply, {error, simple_one_for_one}, State}; handle_call({delete_child, Id}, _From, State) -> case find_child(Id, State) of {ok, Child} when Child#child.pid =:= undefined -> NState = remove_child(Id, State), {reply, ok, NState}; {ok, #child{pid=?restarting(_)}} -> {reply, {error, restarting}, State}; {ok, _} -> {reply, {error, running}, State}; _ -> {reply, {error, not_found}, State} end; handle_call({get_childspec, Id}, _From, State) -> case find_child(Id, State) of {ok, Child} -> {reply, {ok, child_to_spec(Child)}, State}; error -> {reply, {error, not_found}, State} end; handle_call(which_children, _From, State) when ?is_simple(State) -> #child{child_type = CT,modules = Mods} = get_dynamic_child(State), Reply = dyn_map(fun(?restarting(_)) -> {undefined, restarting, CT, Mods}; (Pid) -> {undefined, Pid, CT, Mods} end, State), {reply, Reply, State}; handle_call(which_children, _From, State) -> Resp = children_to_list( fun(Id,#child{pid = ?restarting(_), child_type = ChildType, modules = Mods}) -> {Id, restarting, ChildType, Mods}; (Id,#child{pid = Pid, child_type = ChildType, modules = Mods}) -> {Id, Pid, ChildType, Mods} end, State#state.children), {reply, Resp, State}; handle_call(count_children, _From, #state{dynamic_restarts = Restarts} = State) when ?is_simple(State) -> #child{child_type = CT} = get_dynamic_child(State), Sz = dyn_size(State), Active = Sz - Restarts, % Restarts is always 0 for temporary children Reply = case CT of supervisor -> [{specs, 1}, {active, Active}, {supervisors, Sz}, {workers, 0}]; worker -> [{specs, 1}, {active, Active}, {supervisors, 0}, {workers, Sz}] end, {reply, Reply, State}; handle_call(count_children, _From, State) -> %% Specs and children are together on the children list... {Specs, Active, Supers, Workers} = children_fold(fun(_Id, Child, Counts) -> count_child(Child, Counts) end, {0,0,0,0}, State#state.children), %% Reformat counts to a property list. Reply = [{specs, Specs}, {active, Active}, {supervisors, Supers}, {workers, Workers}], {reply, Reply, State}. count_child(#child{pid = Pid, child_type = worker}, {Specs, Active, Supers, Workers}) -> case is_pid(Pid) andalso is_process_alive(Pid) of true -> {Specs+1, Active+1, Supers, Workers+1}; false -> {Specs+1, Active, Supers, Workers+1} end; count_child(#child{pid = Pid, child_type = supervisor}, {Specs, Active, Supers, Workers}) -> case is_pid(Pid) andalso is_process_alive(Pid) of true -> {Specs+1, Active+1, Supers+1, Workers}; false -> {Specs+1, Active, Supers+1, Workers} end. %%% If a restart attempt failed, this message is cast %%% from restart/2 in order to give gen_server the chance to %%% check it's inbox before trying again. -spec handle_cast({try_again_restart, child_id() | {'restarting',pid()}}, state()) -> {'noreply', state()} | {stop, shutdown, state()}. handle_cast({try_again_restart,TryAgainId}, State) -> case find_child_and_args(TryAgainId, State) of {ok, Child = #child{pid=?restarting(_)}} -> case restart(Child,State) of {ok, State1} -> {noreply, State1}; {shutdown, State1} -> {stop, shutdown, State1} end; _ -> {noreply,State} end. %% %% Take care of terminated children. %% -spec handle_info(term(), state()) -> {'noreply', state()} | {'stop', 'shutdown', state()}. handle_info({'EXIT', Pid, Reason}, State) -> case restart_child(Pid, Reason, State) of {ok, State1} -> {noreply, State1}; {shutdown, State1} -> {stop, shutdown, State1} end; handle_info(Msg, State) -> ?LOG_ERROR("Supervisor received unexpected message: ~tp~n",[Msg], #{domain=>[otp], error_logger=>#{tag=>error}}), {noreply, State}. %% %% Terminate this server. %% -spec terminate(term(), state()) -> 'ok'. terminate(_Reason, State) when ?is_simple(State) -> terminate_dynamic_children(State); terminate(_Reason, State) -> terminate_children(State#state.children, State#state.name). %% %% Change code for the supervisor. %% Call the new call-back module and fetch the new start specification. %% Combine the new spec. with the old. If the new start spec. is %% not valid the code change will not succeed. %% Use the old Args as argument to Module:init/1. %% NOTE: This requires that the init function of the call-back module %% does not have any side effects. %% -spec code_change(term(), state(), term()) -> {'ok', state()} | {'error', term()}. code_change(_, State, _) -> case (State#state.module):init(State#state.args) of {ok, {SupFlags, StartSpec}} -> case set_flags(SupFlags, State) of {ok, State1} -> update_childspec(State1, StartSpec); {invalid_type, SupFlags} -> {error, {bad_flags, SupFlags}}; % backwards compatibility Error -> {error, Error} end; ignore -> {ok, State}; Error -> Error end. update_childspec(State, StartSpec) when ?is_simple(State) -> case check_startspec(StartSpec) of {ok, {[_],_}=Children} -> {ok, State#state{children = Children}}; Error -> {error, Error} end; update_childspec(State, StartSpec) -> case check_startspec(StartSpec) of {ok, Children} -> OldC = State#state.children, % In reverse start order ! NewC = update_childspec1(OldC, Children, []), {ok, State#state{children = NewC}}; Error -> {error, Error} end. update_childspec1({[Id|OldIds], OldDb}, {Ids,Db}, KeepOld) -> case update_chsp(maps:get(Id,OldDb), Db) of {ok,NewDb} -> update_childspec1({OldIds,OldDb}, {Ids,NewDb}, KeepOld); false -> update_childspec1({OldIds,OldDb}, {Ids,Db}, [Id|KeepOld]) end; update_childspec1({[],OldDb}, {Ids,Db}, KeepOld) -> KeepOldDb = maps:with(KeepOld,OldDb), %% Return them in (kept) reverse start order. {lists:reverse(Ids ++ KeepOld),maps:merge(KeepOldDb,Db)}. update_chsp(#child{id=Id}=OldChild, NewDb) -> case maps:find(Id, NewDb) of {ok,Child} -> {ok,NewDb#{Id => Child#child{pid = OldChild#child.pid}}}; error -> % Id not found in new spec. false end. %%% --------------------------------------------------- %%% Start a new child. %%% --------------------------------------------------- handle_start_child(Child, State) -> case find_child(Child#child.id, State) of error -> case do_start_child(State#state.name, Child) of {ok, undefined} when ?is_temporary(Child) -> {{ok, undefined}, State}; {ok, Pid} -> {{ok, Pid}, save_child(Child#child{pid = Pid}, State)}; {ok, Pid, Extra} -> {{ok, Pid, Extra}, save_child(Child#child{pid = Pid}, State)}; {error, What} -> {{error, {What, Child}}, State} end; {ok, OldChild} when is_pid(OldChild#child.pid) -> {{error, {already_started, OldChild#child.pid}}, State}; {ok, _OldChild} -> {{error, already_present}, State} end. %%% --------------------------------------------------- %%% Restart. A process has terminated. %%% Returns: {ok, state()} | {shutdown, state()} %%% --------------------------------------------------- restart_child(Pid, Reason, State) -> case find_child_and_args(Pid, State) of {ok, Child} -> do_restart(Reason, Child, State); error -> {ok, State} end. do_restart(Reason, Child, State) when ?is_permanent(Child) -> ?report_error(child_terminated, Reason, Child, State#state.name), restart(Child, State); do_restart(normal, Child, State) -> NState = del_child(Child, State), {ok, NState}; do_restart(shutdown, Child, State) -> NState = del_child(Child, State), {ok, NState}; do_restart({shutdown, _Term}, Child, State) -> NState = del_child(Child, State), {ok, NState}; do_restart(Reason, Child, State) when ?is_transient(Child) -> ?report_error(child_terminated, Reason, Child, State#state.name), restart(Child, State); do_restart(Reason, Child, State) when ?is_temporary(Child) -> ?report_error(child_terminated, Reason, Child, State#state.name), NState = del_child(Child, State), {ok, NState}. restart(Child, State) -> case add_restart(State) of {ok, NState} -> case restart(NState#state.strategy, Child, NState) of {{try_again, TryAgainId}, NState2} -> %% Leaving control back to gen_server before %% trying again. This way other incoming requsts %% for the supervisor can be handled - e.g. a %% shutdown request for the supervisor or the %% child. try_again_restart(TryAgainId), {ok,NState2}; Other -> Other end; {terminate, NState} -> ?report_error(shutdown, reached_max_restart_intensity, Child, State#state.name), {shutdown, del_child(Child, NState)} end. restart(simple_one_for_one, Child, State0) -> #child{pid = OldPid, mfargs = {M, F, A}} = Child, State1 = case OldPid of ?restarting(_) -> NRes = State0#state.dynamic_restarts - 1, State0#state{dynamic_restarts = NRes}; _ -> State0 end, State2 = dyn_erase(OldPid, State1), case do_start_child_i(M, F, A) of {ok, Pid} -> NState = dyn_store(Pid, A, State2), {ok, NState}; {ok, Pid, _Extra} -> NState = dyn_store(Pid, A, State2), {ok, NState}; {error, Error} -> ROldPid = restarting(OldPid), NRestarts = State2#state.dynamic_restarts + 1, State3 = State2#state{dynamic_restarts = NRestarts}, NState = dyn_store(ROldPid, A, State3), ?report_error(start_error, Error, Child, NState#state.name), {{try_again, ROldPid}, NState} end; restart(one_for_one, #child{id=Id} = Child, State) -> OldPid = Child#child.pid, case do_start_child(State#state.name, Child) of {ok, Pid} -> NState = set_pid(Pid, Id, State), {ok, NState}; {ok, Pid, _Extra} -> NState = set_pid(Pid, Id, State), {ok, NState}; {error, Reason} -> NState = set_pid(restarting(OldPid), Id, State), ?report_error(start_error, Reason, Child, State#state.name), {{try_again,Id}, NState} end; restart(rest_for_one, #child{id=Id} = Child, #state{name=SupName} = State) -> {ChAfter, ChBefore} = split_child(Id, State#state.children), {Return, ChAfter2} = restart_multiple_children(Child, ChAfter, SupName), {Return, State#state{children = append(ChAfter2,ChBefore)}}; restart(one_for_all, Child, #state{name=SupName} = State) -> Children1 = del_child(Child#child.id, State#state.children), {Return, NChildren} = restart_multiple_children(Child, Children1, SupName), {Return, State#state{children = NChildren}}. restart_multiple_children(Child, Children, SupName) -> Children1 = terminate_children(Children, SupName), case start_children(Children1, SupName) of {ok, NChildren} -> {ok, NChildren}; {error, NChildren, {failed_to_start_child, FailedId, _Reason}} -> NewPid = if FailedId =:= Child#child.id -> restarting(Child#child.pid); true -> ?restarting(undefined) end, {{try_again, FailedId}, set_pid(NewPid,FailedId,NChildren)} end. restarting(Pid) when is_pid(Pid) -> ?restarting(Pid); restarting(RPid) -> RPid. -spec try_again_restart(child_id() | {'restarting',pid()}) -> 'ok'. try_again_restart(TryAgainId) -> gen_server:cast(self(), {try_again_restart, TryAgainId}). %%----------------------------------------------------------------- %% Func: terminate_children/2 %% Args: Children = children() % Ids in termination order %% SupName = {local, atom()} | {global, atom()} | {pid(),Mod} %% Returns: NChildren = children() % Ids in startup order %% % (reversed termination order) %%----------------------------------------------------------------- terminate_children(Children, SupName) -> Terminate = fun(_Id,Child) when ?is_temporary(Child) -> %% Temporary children should not be restarted and thus should %% be skipped when building the list of terminated children. do_terminate(Child, SupName), remove; (_Id,Child) -> do_terminate(Child, SupName), {update,Child#child{pid=undefined}} end, {ok,NChildren} = children_map(Terminate, Children), NChildren. do_terminate(Child, SupName) when is_pid(Child#child.pid) -> case shutdown(Child#child.pid, Child#child.shutdown) of ok -> ok; {error, normal} when not (?is_permanent(Child)) -> ok; {error, OtherReason} -> ?report_error(shutdown_error, OtherReason, Child, SupName) end, ok; do_terminate(_Child, _SupName) -> ok. %%----------------------------------------------------------------- %% Shutdowns a child. We must check the EXIT value %% of the child, because it might have died with another reason than %% the wanted. In that case we want to report the error. We put a %% monitor on the child an check for the 'DOWN' message instead of %% checking for the 'EXIT' message, because if we check the 'EXIT' %% message a "naughty" child, who does unlink(Sup), could hang the %% supervisor. %% Returns: ok | {error, OtherReason} (this should be reported) %%----------------------------------------------------------------- shutdown(Pid, brutal_kill) -> case monitor_child(Pid) of ok -> exit(Pid, kill), receive {'DOWN', _MRef, process, Pid, killed} -> ok; {'DOWN', _MRef, process, Pid, OtherReason} -> {error, OtherReason} end; {error, Reason} -> {error, Reason} end; shutdown(Pid, Time) -> case monitor_child(Pid) of ok -> exit(Pid, shutdown), %% Try to shutdown gracefully receive {'DOWN', _MRef, process, Pid, shutdown} -> ok; {'DOWN', _MRef, process, Pid, OtherReason} -> {error, OtherReason} after Time -> exit(Pid, kill), %% Force termination. receive {'DOWN', _MRef, process, Pid, OtherReason} -> {error, OtherReason} end end; {error, Reason} -> {error, Reason} end. %% Help function to shutdown/2 switches from link to monitor approach monitor_child(Pid) -> %% Do the monitor operation first so that if the child dies %% before the monitoring is done causing a 'DOWN'-message with %% reason noproc, we will get the real reason in the 'EXIT'-message %% unless a naughty child has already done unlink... erlang:monitor(process, Pid), unlink(Pid), receive %% If the child dies before the unlik we must empty %% the mail-box of the 'EXIT'-message and the 'DOWN'-message. {'EXIT', Pid, Reason} -> receive {'DOWN', _, process, Pid, _} -> {error, Reason} end after 0 -> %% If a naughty child did unlink and the child dies before %% monitor the result will be that shutdown/2 receives a %% 'DOWN'-message with reason noproc. %% If the child should die after the unlink there %% will be a 'DOWN'-message with a correct reason %% that will be handled in shutdown/2. ok end. %%----------------------------------------------------------------- %% Func: terminate_dynamic_children/1 %% Args: State %% Returns: ok %% %% Shutdown all dynamic children. This happens when the supervisor is %% stopped. Because the supervisor can have millions of dynamic children, we %% can have a significative overhead here. %%----------------------------------------------------------------- terminate_dynamic_children(State) -> Child = get_dynamic_child(State), {Pids, EStack0} = monitor_dynamic_children(Child,State), Sz = sets:size(Pids), EStack = case Child#child.shutdown of brutal_kill -> sets:fold(fun(P, _) -> exit(P, kill) end, ok, Pids), wait_dynamic_children(Child, Pids, Sz, undefined, EStack0); infinity -> sets:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids), wait_dynamic_children(Child, Pids, Sz, undefined, EStack0); Time -> sets:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids), TRef = erlang:start_timer(Time, self(), kill), wait_dynamic_children(Child, Pids, Sz, TRef, EStack0) end, %% Unroll stacked errors and report them dict:fold(fun(Reason, Ls, _) -> ?report_error(shutdown_error, Reason, Child#child{pid=Ls}, State#state.name) end, ok, EStack). monitor_dynamic_children(Child,State) -> dyn_fold(fun(P,{Pids, EStack}) when is_pid(P) -> case monitor_child(P) of ok -> {sets:add_element(P, Pids), EStack}; {error, normal} when not (?is_permanent(Child)) -> {Pids, EStack}; {error, Reason} -> {Pids, dict:append(Reason, P, EStack)} end; (?restarting(_), {Pids, EStack}) -> {Pids, EStack} end, {sets:new(), dict:new()}, State). wait_dynamic_children(_Child, _Pids, 0, undefined, EStack) -> EStack; wait_dynamic_children(_Child, _Pids, 0, TRef, EStack) -> %% If the timer has expired before its cancellation, we must empty the %% mail-box of the 'timeout'-message. _ = erlang:cancel_timer(TRef), receive {timeout, TRef, kill} -> EStack after 0 -> EStack end; wait_dynamic_children(#child{shutdown=brutal_kill} = Child, Pids, Sz, TRef, EStack) -> receive {'DOWN', _MRef, process, Pid, killed} -> wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1, TRef, EStack); {'DOWN', _MRef, process, Pid, Reason} -> wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1, TRef, dict:append(Reason, Pid, EStack)) end; wait_dynamic_children(Child, Pids, Sz, TRef, EStack) -> receive {'DOWN', _MRef, process, Pid, shutdown} -> wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1, TRef, EStack); {'DOWN', _MRef, process, Pid, {shutdown, _}} -> wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1, TRef, EStack); {'DOWN', _MRef, process, Pid, normal} when not (?is_permanent(Child)) -> wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1, TRef, EStack); {'DOWN', _MRef, process, Pid, Reason} -> wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1, TRef, dict:append(Reason, Pid, EStack)); {timeout, TRef, kill} -> sets:fold(fun(P, _) -> exit(P, kill) end, ok, Pids), wait_dynamic_children(Child, Pids, Sz, undefined, EStack) end. %%----------------------------------------------------------------- %% Access #state.children %%----------------------------------------------------------------- %% Note we do not want to save the parameter list for temporary processes as %% they will not be restarted, and hence we do not need this information. %% Especially for dynamic children to simple_one_for_one supervisors %% it could become very costly as it is not uncommon to spawn %% very many such processes. -spec save_child(child_rec(), state()) -> state(). save_child(#child{mfargs = {M, F, _}} = Child, State) when ?is_temporary(Child) -> do_save_child(Child#child{mfargs = {M, F, undefined}}, State); save_child(Child, State) -> do_save_child(Child, State). -spec do_save_child(child_rec(), state()) -> state(). do_save_child(#child{id = Id} = Child, #state{children = {Ids,Db}} = State) -> State#state{children = {[Id|Ids],Db#{Id => Child}}}. -spec del_child(child_rec(), state()) -> state(); (child_id(), children()) -> children(). del_child(#child{pid = Pid}, State) when ?is_simple(State) -> dyn_erase(Pid,State); del_child(Child, State) when is_record(Child,child), is_record(State,state) -> NChildren = del_child(Child#child.id, State#state.children), State#state{children = NChildren}; del_child(Id, {Ids,Db}) -> case maps:get(Id, Db) of Child when Child#child.restart_type =:= temporary -> {lists:delete(Id, Ids), maps:remove(Id, Db)}; Child -> {Ids, Db#{Id=>Child#child{pid=undefined}}} end. %% In: {[S4, S3, Ch, S1, S0],Db} %% Ret: {{[S4, S3, Ch],Db1}, {[S1, S0],Db2}} %% Db1 and Db2 contain the keys in the lists they are associated with. -spec split_child(child_id(), children()) -> {children(), children()}. split_child(Id, {Ids,Db}) -> {IdsAfter,IdsBefore} = split_ids(Id, Ids, []), DbBefore = maps:with(IdsBefore,Db), #{Id:=Ch} = DbAfter = maps:with(IdsAfter,Db), {{IdsAfter,DbAfter#{Id=>Ch#child{pid=undefined}}},{IdsBefore,DbBefore}}. split_ids(Id, [Id|Ids], After) -> {lists:reverse([Id|After]), Ids}; split_ids(Id, [Other|Ids], After) -> split_ids(Id, Ids, [Other | After]). %% Find the child record for a given Pid (dynamic child) or Id %% (non-dynamic child). This is called from the API functions. -spec find_child(pid() | child_id(), state()) -> {ok,child_rec()} | error. find_child(Pid, State) when is_pid(Pid), ?is_simple(State) -> case find_dynamic_child(Pid, State) of error -> case find_dynamic_child(restarting(Pid), State) of error -> case erlang:is_process_alive(Pid) of true -> error; false -> {ok, get_dynamic_child(State)} end; Other -> Other end; Other -> Other end; find_child(Id, #state{children = {_Ids,Db}}) -> maps:find(Id, Db). %% Get the child record - either by child id or by pid. If %% simple_one_for_one, then insert the pid and args into the returned %% child record. This is called when trying to restart the child. -spec find_child_and_args(IdOrPid, state()) -> {ok, child_rec()} | error when IdOrPid :: pid() | {restarting,pid()} | child_id(). find_child_and_args(Pid, State) when ?is_simple(State) -> case find_dynamic_child(Pid, State) of {ok,#child{mfargs={M,F,_}} = Child} -> {ok, Args} = dyn_args(Pid, State), {ok, Child#child{mfargs = {M, F, Args}}}; error -> error end; find_child_and_args(Pid, State) when is_pid(Pid) -> find_child_by_pid(Pid, State); find_child_and_args(Id, #state{children={_Ids,Db}}) -> maps:find(Id, Db). %% Given the pid, find the child record for a dynamic child, and %% include the pid in the returned record. -spec find_dynamic_child(IdOrPid, state()) -> {ok, child_rec()} | error when IdOrPid :: pid() | {restarting,pid()} | child_id(). find_dynamic_child(Pid, State) -> case dyn_exists(Pid, State) of true -> Child = get_dynamic_child(State), {ok, Child#child{pid=Pid}}; false -> error end. %% Given the pid, find the child record for a non-dyanamic child. -spec find_child_by_pid(IdOrPid, state()) -> {ok,child_rec()} | error when IdOrPid :: pid() | {restarting,pid()}. find_child_by_pid(Pid,#state{children={_Ids,Db}}) -> Fun = fun(_Id,#child{pid=P}=Ch,_) when P =:= Pid -> throw(Ch); (_,_,error) -> error end, try maps:fold(Fun,error,Db) catch throw:Child -> {ok,Child} end. %% Get the child record from a simple_one_for_one supervisor - no pid %% It is assumed that the child can always be found -spec get_dynamic_child(state()) -> child_rec(). get_dynamic_child(#state{children={[Id],Db}}) -> #{Id := Child} = Db, Child. %% Update pid in the given child record and store it in the process state -spec set_pid(term(), child_id(), state()) -> state(); (term(), child_id(), children()) -> children(). set_pid(Pid, Id, #state{children=Children} = State) -> State#state{children = set_pid(Pid, Id, Children)}; set_pid(Pid, Id, {Ids, Db}) -> NewDb = maps:update_with(Id, fun(Child) -> Child#child{pid=Pid} end, Db), {Ids,NewDb}. %% Remove the Id and the child record from the process state -spec remove_child(child_id(), state()) -> state(). remove_child(Id, #state{children={Ids,Db}} = State) -> NewIds = lists:delete(Id,Ids), NewDb = maps:remove(Id,Db), State#state{children = {NewIds,NewDb}}. %% In the order of Ids, traverse the children and update each child %% according to the return value of the Fun. %% On error, abort and return the merge of the old and the updated map. %% NOTE: The returned list of Ids is reverted compared to the input. -spec children_map(Fun, children()) -> {ok, children()} | {error,children(),Reason} when Fun :: fun((child_id(),child_rec()) -> {update,child_rec()} | remove | {abort, Reason}), Reason :: term(). children_map(Fun,{Ids,Db}) -> children_map(Fun, Ids, Db, []). children_map(Fun,[Id|Ids],Db,Acc) -> case Fun(Id,maps:get(Id,Db)) of {update,Child} -> children_map(Fun,Ids,Db#{Id => Child},[Id|Acc]); remove -> children_map(Fun,Ids,maps:remove(Id,Db),Acc); {abort,Reason} -> {error,{lists:reverse(Ids)++[Id|Acc],Db},Reason} end; children_map(_Fun,[],Db,Acc) -> {ok,{Acc,Db}}. %% In the order of Ids, map over all children and return the list -spec children_to_list(Fun, children()) -> List when Fun :: fun((child_id(), child_rec()) -> Elem), List :: list(Elem), Elem :: term(). children_to_list(Fun,{Ids,Db}) -> children_to_list(Fun, Ids, Db, []). children_to_list(Fun,[Id|Ids],Db,Acc) -> children_to_list(Fun,Ids,Db,[Fun(Id,maps:get(Id,Db))|Acc]); children_to_list(_Fun,[],_Db,Acc) -> lists:reverse(Acc). %% The order is not important - so ignore Ids -spec children_fold(Fun, Acc0, children()) -> Acc1 when Fun :: fun((child_id(), child_rec(), AccIn) -> AccOut), Acc0 :: term(), Acc1 :: term(), AccIn :: term(), AccOut :: term(). children_fold(Fun,Init,{_Ids,Db}) -> maps:fold(Fun, Init, Db). -spec append(children(), children()) -> children(). append({Ids1,Db1},{Ids2,Db2}) -> {Ids1++Ids2,maps:merge(Db1,Db2)}. %%----------------------------------------------------------------- %% Func: init_state/4 %% Args: SupName = {local, atom()} | {global, atom()} | self %% Type = {Strategy, MaxIntensity, Period} %% Strategy = one_for_one | one_for_all | simple_one_for_one | %% rest_for_one %% MaxIntensity = integer() >= 0 %% Period = integer() > 0 %% Mod :== atom() %% Args :== term() %% Purpose: Check that Type is of correct type (!) %% Returns: {ok, state()} | Error %%----------------------------------------------------------------- init_state(SupName, Type, Mod, Args) -> set_flags(Type, #state{name = supname(SupName,Mod), module = Mod, args = Args}). set_flags(Flags, State) -> try check_flags(Flags) of #{strategy := Strategy, intensity := MaxIntensity, period := Period} -> {ok, State#state{strategy = Strategy, intensity = MaxIntensity, period = Period}} catch Thrown -> Thrown end. check_flags(SupFlags) when is_map(SupFlags) -> do_check_flags(maps:merge(?default_flags,SupFlags)); check_flags({Strategy, MaxIntensity, Period}) -> check_flags(#{strategy => Strategy, intensity => MaxIntensity, period => Period}); check_flags(What) -> throw({invalid_type, What}). do_check_flags(#{strategy := Strategy, intensity := MaxIntensity, period := Period} = Flags) -> validStrategy(Strategy), validIntensity(MaxIntensity), validPeriod(Period), Flags. validStrategy(simple_one_for_one) -> true; validStrategy(one_for_one) -> true; validStrategy(one_for_all) -> true; validStrategy(rest_for_one) -> true; validStrategy(What) -> throw({invalid_strategy, What}). validIntensity(Max) when is_integer(Max), Max >= 0 -> true; validIntensity(What) -> throw({invalid_intensity, What}). validPeriod(Period) when is_integer(Period), Period > 0 -> true; validPeriod(What) -> throw({invalid_period, What}). supname(self, Mod) -> {self(), Mod}; supname(N, _) -> N. %%% ------------------------------------------------------ %%% Check that the children start specification is valid. %%% Input: [child_spec()] %%% Returns: {ok, [child_rec()]} | Error %%% ------------------------------------------------------ check_startspec(Children) -> check_startspec(Children, [], #{}). check_startspec([ChildSpec|T], Ids, Db) -> case check_childspec(ChildSpec) of {ok, #child{id=Id}=Child} -> case maps:is_key(Id, Db) of %% The error message duplicate_child_name is kept for %% backwards compatibility, although %% duplicate_child_id would be more correct. true -> {duplicate_child_name, Id}; false -> check_startspec(T, [Id | Ids], Db#{Id=>Child}) end; Error -> Error end; check_startspec([], Ids, Db) -> {ok, {lists:reverse(Ids),Db}}. check_childspec(ChildSpec) when is_map(ChildSpec) -> catch do_check_childspec(maps:merge(?default_child_spec,ChildSpec)); check_childspec({Id, Func, RestartType, Shutdown, ChildType, Mods}) -> check_childspec(#{id => Id, start => Func, restart => RestartType, shutdown => Shutdown, type => ChildType, modules => Mods}); check_childspec(X) -> {invalid_child_spec, X}. do_check_childspec(#{restart := RestartType, type := ChildType} = ChildSpec)-> Id = case ChildSpec of #{id := I} -> I; _ -> throw(missing_id) end, Func = case ChildSpec of #{start := F} -> F; _ -> throw(missing_start) end, validId(Id), validFunc(Func), validRestartType(RestartType), validChildType(ChildType), Shutdown = case ChildSpec of #{shutdown := S} -> S; #{type := worker} -> 5000; #{type := supervisor} -> infinity end, validShutdown(Shutdown), Mods = case ChildSpec of #{modules := Ms} -> Ms; _ -> {M,_,_} = Func, [M] end, validMods(Mods), {ok, #child{id = Id, mfargs = Func, restart_type = RestartType, shutdown = Shutdown, child_type = ChildType, modules = Mods}}. validChildType(supervisor) -> true; validChildType(worker) -> true; validChildType(What) -> throw({invalid_child_type, What}). validId(_Id) -> true. validFunc({M, F, A}) when is_atom(M), is_atom(F), is_list(A) -> true; validFunc(Func) -> throw({invalid_mfa, Func}). validRestartType(permanent) -> true; validRestartType(temporary) -> true; validRestartType(transient) -> true; validRestartType(RestartType) -> throw({invalid_restart_type, RestartType}). validShutdown(Shutdown) when is_integer(Shutdown), Shutdown > 0 -> true; validShutdown(infinity) -> true; validShutdown(brutal_kill) -> true; validShutdown(Shutdown) -> throw({invalid_shutdown, Shutdown}). validMods(dynamic) -> true; validMods(Mods) when is_list(Mods) -> lists:foreach(fun(Mod) -> if is_atom(Mod) -> ok; true -> throw({invalid_module, Mod}) end end, Mods); validMods(Mods) -> throw({invalid_modules, Mods}). child_to_spec(#child{id = Id, mfargs = Func, restart_type = RestartType, shutdown = Shutdown, child_type = ChildType, modules = Mods}) -> #{id => Id, start => Func, restart => RestartType, shutdown => Shutdown, type => ChildType, modules => Mods}. %%% ------------------------------------------------------ %%% Add a new restart and calculate if the max restart %%% intensity has been reached (in that case the supervisor %%% shall terminate). %%% All restarts accured inside the period amount of seconds %%% are kept in the #state.restarts list. %%% Returns: {ok, State'} | {terminate, State'} %%% ------------------------------------------------------ add_restart(State) -> I = State#state.intensity, P = State#state.period, R = State#state.restarts, Now = erlang:monotonic_time(1), R1 = add_restart([Now|R], Now, P), State1 = State#state{restarts = R1}, case length(R1) of CurI when CurI =< I -> {ok, State1}; _ -> {terminate, State1} end. add_restart([R|Restarts], Now, Period) -> case inPeriod(R, Now, Period) of true -> [R|add_restart(Restarts, Now, Period)]; _ -> [] end; add_restart([], _, _) -> []. inPeriod(Then, Now, Period) -> Now =< Then + Period. %%% ------------------------------------------------------ %%% Error and progress reporting. %%% ------------------------------------------------------ extract_child(Child) when is_list(Child#child.pid) -> [{nb_children, length(Child#child.pid)}, {id, Child#child.id}, {mfargs, Child#child.mfargs}, {restart_type, Child#child.restart_type}, {shutdown, Child#child.shutdown}, {child_type, Child#child.child_type}]; extract_child(Child) -> [{pid, Child#child.pid}, {id, Child#child.id}, {mfargs, Child#child.mfargs}, {restart_type, Child#child.restart_type}, {shutdown, Child#child.shutdown}, {child_type, Child#child.child_type}]. report_progress(Child, SupName) -> ?LOG_INFO(#{label=>{supervisor,progress}, report=>[{supervisor,SupName}, {started,extract_child(Child)}]}, #{domain=>[otp,sasl], report_cb=>fun logger:format_otp_report/1, logger_formatter=>#{title=>"PROGRESS REPORT"}, error_logger=>#{tag=>info_report,type=>progress}}). format_status(terminate, [_PDict, State]) -> State; format_status(_, [_PDict, State]) -> [{data, [{"State", State}]}, {supervisor, [{"Callback", State#state.module}]}]. %%%----------------------------------------------------------------- %%% Dynamics database access dyn_size(#state{dynamics = {Mod,Db}}) -> Mod:size(Db). dyn_erase(Pid,#state{dynamics={sets,Db}}=State) -> State#state{dynamics={sets,sets:del_element(Pid,Db)}}; dyn_erase(Pid,#state{dynamics={maps,Db}}=State) -> State#state{dynamics={maps,maps:remove(Pid,Db)}}. dyn_store(Pid,_,#state{dynamics={sets,Db}}=State) -> State#state{dynamics={sets,sets:add_element(Pid,Db)}}; dyn_store(Pid,Args,#state{dynamics={maps,Db}}=State) -> State#state{dynamics={maps,Db#{Pid => Args}}}. dyn_fold(Fun,Init,#state{dynamics={sets,Db}}) -> sets:fold(Fun,Init,Db); dyn_fold(Fun,Init,#state{dynamics={maps,Db}}) -> maps:fold(fun(Pid,_,Acc) -> Fun(Pid,Acc) end, Init, Db). dyn_map(Fun, #state{dynamics={sets,Db}}) -> lists:map(Fun, sets:to_list(Db)); dyn_map(Fun, #state{dynamics={maps,Db}}) -> lists:map(Fun, maps:keys(Db)). dyn_exists(Pid, #state{dynamics={sets, Db}}) -> sets:is_element(Pid, Db); dyn_exists(Pid, #state{dynamics={maps, Db}}) -> maps:is_key(Pid, Db). dyn_args(_Pid, #state{dynamics={sets, _Db}}) -> {ok,undefined}; dyn_args(Pid, #state{dynamics={maps, Db}}) -> maps:find(Pid, Db). dyn_init(State) -> dyn_init(get_dynamic_child(State),State). dyn_init(Child,State) when ?is_temporary(Child) -> State#state{dynamics={sets,sets:new()}}; dyn_init(_Child,State) -> State#state{dynamics={maps,maps:new()}}.