Current section
Files
Jump to
Current section
Files
src/director_utils.erl
%%% ------------------------------------------------------------------------------------------------%%% Director is available for use under the following license, commonly known as the 3-clause (or%%% "modified") BSD license:%%%%%% Copyright (c) 2016-2017, Pouriya Jahanbakhsh%%% (pouriya.jahanbakhsh@gmail.com)%%% All rights reserved.%%%%%% Redistribution and use in source and binary forms, with or without modification, are permitted%%% provided that the following conditions are met:%%%%%% 1. Redistributions of source code must retain the above copyright notice, this list of%%% conditions and the following disclaimer.%%%%%% 2. Redistributions in binary form must reproduce the above copyright notice, this list of%%% conditions and the following disclaimer in the documentation and/or other materials provided%%% with the distribution.%%%%%% 3. Neither the name of the copyright holder nor the names of its contributors may be used to%%% endorse or promote products derived from this software without specific prior written%%% permission.%%%%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR%%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND%%% FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR%%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR%%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR%%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY%%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR%%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE%%% POSSIBILITY OF SUCH DAMAGE.%%% ------------------------------------------------------------------------------------------------%% @author Pouriya Jahanbakhsh <pouriya.jahanbakhsh@gmail.com>%% @version 17.9.2%% @hidden%% --------------------------------------------------------------------------------------------------module(director_utils).-author("pouriya.jahanbakhsh@gmail.com").%% -------------------------------------------------------------------------------------------------%% Exports:%% API:-export([debug/3 ,get_debug_options/3 ,progress_report/3 ,error_report/5 ,run_log_validator/3 ,check_childspecs/1 ,check_childspecs/2 ,check_childspec/2 ,check_default_childspec/1 ,filter_plan/1 ,filter_plan_element/1 ,is_whole_integer/1 ,get_log_validator/3 ,get_table_type/3 ,combine_child/2 ,separate_child/2 ,c2cs/1 ,c_r2p/2 ,cs2c/1]).%% -------------------------------------------------------------------------------------------------%% Records & Macros & Includes:-include("internal/director_child.hrl").-include("internal/director_defaults.hrl").%% -------------------------------------------------------------------------------------------------%% Functions:get_debug_options(Name, Opts, Def) -> case lists:keyfind(debug, 1, Opts) of {_, DbgOpts} -> try sys:debug_options(DbgOpts) catch _:_ -> error_logger:format("~p: ignoring erroneous debug options: ~p~n", [Name, DbgOpts]), Def end; false -> Def; Other -> error_logger:format("~p: ignoring erroneous debug options: ~p~n", [Name, Other]), Def end.progress_report(Name, #?CHILD{id = Id}=Child, LogValidator) -> case run_log_validator(LogValidator, Id, {info, start}) of none -> ok; LogMode -> error_logger:info_report(progress, [{supervisor, Name} ,{started, c_r2p(Child, LogMode)}]) end.error_report(Name, ErrorContext, Reason, #?CHILD{id = Id}=Child, LogValidator) -> case run_log_validator(LogValidator, Id, {error, Reason}) of none -> ok; LogMode -> error_logger:error_report(supervisor_report ,[{supervisor, Name} ,{errorContext, ErrorContext} ,{reason, Reason} ,{offender, c_r2p(Child, LogMode)}]) end.debug([], _Name, _MsgInfo) -> [];debug(Dbg, Name, MsgInfo) -> sys:handle_debug(Dbg, fun print/3, Name, MsgInfo).check_childspecs(ChildSpecs) -> check_childspecs(ChildSpecs, ?DEF_DEF_CHILDSPEC).check_default_childspec(ChildSpec) when erlang:is_map(ChildSpec) -> Keys = [{start, fun filter_start/1} ,{plan, fun filter_plan/1} ,{count, fun filter_count/1} ,{type, fun filter_type/1} ,{terminate_timeout, fun filter_terminate_timeout/1} ,{modules, fun filter_modules/1}], check_map2(ChildSpec, Keys, #{});check_default_childspec(Other) -> {error, {default_childspec_type, [{childspec, Other}]}}.get_log_validator(Name, Opts, Def) -> case lists:keyfind(log_validator, 1, Opts) of false -> Def; {_, Fun} when erlang:is_function(Fun, 2) -> Fun; {_, Other} -> error_logger:format("~p: ignoring erroneous log validator: ~p~n", [Name, Other]), Def; Other -> error_logger:format("~p: ignoring erroneous log validator: ~p~n", [Name, Other]), Def end.get_table_type(Name, Opts, Def) -> case lists:keyfind(table_type, 1, Opts) of false -> Def; {_, list} -> list; {_, ets} -> ets; {_, Mode} -> error_logger:format("~p: ignoring erroneous table type: ~p~n", [Name, Mode]), Def; Mode -> error_logger:format("~p: ignoring erroneous table type: ~p~n", [Name, Mode]), Def end.run_log_validator(Validator, Id, Extra) -> case catch Validator(Id, Extra) of none -> none; short -> short; long -> long; {'EXIT', Reason} -> error_logger:format("~p: log validator crashed: ~p~n", [erlang:self(), Reason]), ?DEF_LOG_MODE; Other -> error_logger:format("~p: ignoring erroneous log mode: ~p~n", [erlang:self(), Other]), ?DEF_LOG_MODE end.check_childspecs([Elem|Elems], DefChildSpec, Children) -> case check_childspec(Elem, DefChildSpec) of {ok, ChildSpec} -> check_childspecs(Elems ,DefChildSpec ,[ChildSpec|Children]); {error, _Reason}=Error -> Error end;check_childspecs([], _DefChildSpec, Children) -> {ok, lists:reverse(Children)};check_childspecs(Other, _DefChildSpec, _Children) -> {error, {childspecs_type, [{childspecs, Other}]}}.separate_child(ChildSpec, DefChildSpec) -> case maps:get(append, ChildSpec) of true -> maps:fold(fun separate_child/3, DefChildSpec, ChildSpec); false -> ChildSpec end.cs2c(#{id := Id ,plan := Plan ,count := Count ,start := Start ,terminate_timeout := TerminateTimeout ,modules := Mods ,type := Type ,append := Append}) -> PlanLen = erlang:length(Plan), PlanElemIndex = if PlanLen =:= 0 -> 0; true -> 1 end, #?CHILD{id = Id ,pid = undefined ,plan = Plan ,count = Count ,count2 = 0 ,restart_count = 0 ,start = Start ,plan_element_index = PlanElemIndex ,plan_length = PlanLen ,timer_reference = undefined ,terminate_timeout = TerminateTimeout ,extra = undeined ,modules = Mods ,type = Type ,append = Append}.c2cs(#?CHILD{id = Id ,start = Start ,plan = Plan ,count = Count ,terminate_timeout = TerminateTimeout ,modules = Modules ,type = Type ,append = Append}) -> #{id => Id ,start => Start ,plan => Plan ,count => Count ,terminate_timeout => TerminateTimeout ,modules => Modules ,type => Type ,append => Append}.c_r2p(#?CHILD{pid = Pid ,id = Id ,plan = Plan ,count = Count ,restart_count = ResCount ,start = Start ,terminate_timeout = TerminateTimeout ,modules = Mods ,type = Type ,append = Append} ,short) -> [{id, Id} ,{pid, Pid} ,{plan, Plan} ,{count, Count} ,{restart_count, ResCount} ,{mfargs, Start} ,{restart_type, temporary} ,{shutdown, case TerminateTimeout of 0 -> brutal_kill; Timeout -> Timeout end} ,{child_type, Type} ,{modules, Mods} ,{append, Append}];c_r2p(#?CHILD{pid = Pid ,id = Id ,plan = Plan ,count = Count ,count2 = Count2 ,restart_count = ResCount ,start = Start ,plan_element_index = PlanElemIndex ,plan_length = PlanLen ,timer_reference = TimerRef ,terminate_timeout = TerminateTimeout ,extra = Extra ,modules = Mods ,type = Type ,append = Append} ,long) -> [{id, Id} ,{pid, Pid} ,{plan, Plan} ,{count, Count} ,{count2, Count2} ,{restart_count, ResCount} ,{mfargs, Start} ,{plan_element_index, PlanElemIndex} ,{plan_length, PlanLen} ,{timer_reference, TimerRef} ,{restart_type, temporary} ,{shutdown, case TerminateTimeout of 0 -> brutal_kill; Timeout -> Timeout end} ,{child_type, Type} ,{extra, Extra} ,{modules, Mods} ,{append, Append}].check_map(ChildSpec, [{Key, Filter, DEF}|Keys], ChildSpec2) -> try maps:get(Key, ChildSpec) of Value -> case Filter(Value) of {ok, Value2} -> check_map(ChildSpec ,Keys ,maps:put(Key ,Value2 ,ChildSpec2)); {error, Reason} -> {error, {childspec_value, [{key, Key}, {reason, Reason}]}} end catch _:_ -> check_map(ChildSpec ,Keys ,maps:put(Key ,DEF ,ChildSpec2)) end;check_map(ChildSpec, [{Key, Filter}|Keys], ChildSpec2) -> try maps:get(Key, ChildSpec) of Value -> case Filter(Value) of {ok, Value2} -> check_map(ChildSpec ,Keys ,maps:put(Key ,Value2 ,ChildSpec2)); {error, Reason} -> {error, {childspec_value, [{key, Key}, {reason, Reason}]}} end catch _:_-> {error, {key_not_found, [{key, Key}, {childspec, ChildSpec}]}} end;check_map(ChildSpec, [Key|Keys], ChildSpec2) -> try maps:get(Key, ChildSpec) of Value -> check_map(ChildSpec ,Keys ,maps:put(Key, Value, ChildSpec2)) catch _:_ -> {error, {key_not_found, [{key, Key}, {childspec, ChildSpec}]}} end;check_map(_ChidlSpec, [], ChildSpec2) -> {ok, ChildSpec2}.check_map2(ChildSpec, [{Key, Filter}|Keys], ChildSpec2) -> try maps:get(Key, ChildSpec) of Value -> case Filter(Value) of {ok, Value2} -> check_map2(ChildSpec ,Keys ,maps:put(Key ,Value2 ,ChildSpec2)); {error, Reason} -> {error, {childspec_value, [{key, Key}, {reason, Reason}]}} end catch _:_ -> check_map2(ChildSpec, Keys, ChildSpec2) end;check_map2(_ChidlSpec, [], ChildSpec2) -> {ok, ChildSpec2}.filter_start({_Mod, _Func, _Args}=Start) -> {ok, Start};filter_start({Mod, Func}) -> {ok, {Mod, Func, []}};filter_start(Other) -> {error, {format, [{start, Other}]}}.combine_child(ChildSpec, DefChildSpec) -> case maps:get(append, ChildSpec) of true -> maps:fold(fun combine_child/3, DefChildSpec, ChildSpec); false -> ChildSpec end.combine_child(start ,{Mod, Func, Args} ,#{start := {_Mod2, _Func2, Args2}}=Map) -> Map#{start => {Mod, Func, Args2 ++ Args}};combine_child(count, infinity, Map) -> Map#{count => infinity};combine_child(count, Count, #{count := Count2}=Map) -> if Count2 =:= infinity -> Map#{count => Count}; true -> Map#{count => Count + Count2} end;combine_child(terminate_timeout, infinity, Map) -> Map#{terminate_timeout => infinity};combine_child(terminate_timeout ,TerminateTimeout ,#{terminate_timeout := TerminateTimeout2}=Map) -> if TerminateTimeout2 =:= infinity -> Map#{terminate_timeout => TerminateTimeout}; true -> Map#{terminate_timeout => TerminateTimeout + TerminateTimeout2} end;combine_child(modules, dynamic, Map) -> Map#{modules => dynamic};combine_child(modules, Mods, #{modules := Mods2}=Map) -> if Mods2 =:= dynamic -> Map#{modules => Mods}; true -> Map#{modules => Mods2 ++ Mods} end;combine_child(plan, Plan, #{plan := Plan2}=Map) -> Map#{plan => Plan2 ++ Plan};combine_child(Key, Value, Map) -> Map#{Key => Value}.separate_child(start ,{Mod, Func, Args} ,#{start := {_Mod2, _Func2, Args2}}=Map) -> Map#{start => {Mod, Func, Args -- Args2}};separate_child(count, infinity, Map) -> Map#{count => infinity};separate_child(count, Count, #{count := Count2}=Map) -> if Count2 =:= infinity -> Map#{count => Count}; true -> Map#{count => Count - Count2} end;separate_child(terminate_timeout, infinity, Map) -> Map#{terminate_timeout => infinity};separate_child(terminate_timeout ,TerminateTimeout ,#{terminate_timeout := TerminateTimeout2}=Map) -> if TerminateTimeout2 =:= infinity -> Map#{terminate_timeout => TerminateTimeout}; true -> Map#{terminate_timeout => TerminateTimeout - TerminateTimeout2} end;separate_child(modules, dynamic, Map) -> Map#{modules => dynamic};separate_child(modules, Mods, #{modules := Mods2}=Map) -> if Mods2 =:= dynamic -> Map#{modules => Mods}; true -> Map#{modules => Mods -- Mods2} end;separate_child(plan, Plan, #{plan := Plan2}=Map) -> Map#{plan => Plan -- Plan2};separate_child(Key, Value, Map) -> Map#{Key => Value}.print(IODev, {in, Msg}, Name) -> io:format(IODev ,"*DBG* director ~p got message \"~p\" ~n" ,[Name, Msg]);print(IODev, {?GEN_CALL_TAG, {Pid, Tag}, Request}, Name) -> io:format(IODev ,"*DBG* director ~p got request \"~p\" from \"~p\" wit" "h tag \"~p\" ~n" ,[Name, Request, Pid, Tag]);print(IODev, {'EXIT', Pid, Reason}, Name) -> io:format(IODev ,"*DBG* director ~p got exit signal for pid \"~p\" wit" "h reason \"~p\"~n" ,[Name, Pid, Reason]);print(IODev, {timeout, Ref, Id}, Name) -> io:format(IODev ,"*DBG* director ~p got restart event for child-id \"~" "p\" with timer reference \"~p\"~n" ,[Name, Id, Ref]);print(IODev, {out, To, Msg}, Name) -> io:format(IODev ,"*DBG* director ~p sent \"~p\" to \"~p\"~n" ,[Name, Msg, To]);print(IODev, {plan, Id, Strategy}, Name) -> io:format(IODev ,"*DBG* director ~p is running plan \"~p\" for id \"~p\"~n" ,[Name, Strategy, Id]);print(IODev, Other, Name) -> io:format(IODev ,"*DBG* director ~p got debug \"~p\" ~n" ,[Name, Other]).check_childspec(ChildSpec, DefChildSpec) when erlang:is_map(ChildSpec) -> Keys = [{append, fun filter_append/1, ?DEF_APPEND}], {ok, #{append := Append}} = {ok, ChildSpec2} = check_map(ChildSpec, Keys, #{}), StartKey = if Append -> case DefChildSpec of #{start := {Mod, Func, _Args}} -> {start, fun filter_start/1, {Mod, Func, []}}; _Other -> {start, fun filter_start/1} end; true -> {start, fun filter_start/1} end, Keys2 = [id ,StartKey ,{plan, fun filter_plan/1, ?DEF_PLAN} ,{count, fun filter_count/1, ?DEF_COUNT} ,{type, fun filter_type/1, ?DEF_TYPE}], case check_map(ChildSpec, Keys2, ChildSpec2) of {ok, ChildSpec3} -> DefTerminateTimeout = case maps:get(type, ChildSpec3) of worker -> ?DEF_WORKER_TERMINATE_TIMEOUT; supervisor -> ?DEF_SUPERVISOR_TERMINATE_TIMEOUT end, DefMods = [erlang:element(1, maps:get(start, ChildSpec3))], Keys3 = [{terminate_timeout, fun filter_terminate_timeout/1, DefTerminateTimeout} ,{modules, fun filter_modules/1, DefMods}], case check_map(ChildSpec ,Keys3 ,ChildSpec3) of {ok, ChildSpec4} -> {ok, cs2c((combine_child(ChildSpec4, DefChildSpec)))}; {error, _Reason}=Error -> Error end; {error, _Reason}=Error -> Error end;check_childspec(Other, _DefChildSpec) -> {error, {childspec_type, [{childspec, Other}]}}.filter_plan(Plan) when erlang:is_list(Plan) -> filter_plan(Plan, []);filter_plan(Other) -> {error, {plan_type, [{plan, Other}]}}.filter_plan_element(restart) -> {ok, restart};filter_plan_element({restart, WholeInt}=PlanElem) -> case is_whole_integer(WholeInt) of true -> {ok, PlanElem}; false -> {error, {plan_restart_time_integer, [{plan_element, PlanElem}]}} end;filter_plan_element(delete) -> {ok, delete};filter_plan_element({stop, reason}) -> {ok, {stop, reason}};filter_plan_element({stop, _Reason}=PlanElem) -> {ok, PlanElem};filter_plan_element(stop) -> {ok, stop};filter_plan_element(wait) -> {ok, wait};filter_plan_element(Fun) when erlang:is_function(Fun) -> case erlang:fun_info(Fun, arity) of {arity, 3} -> {ok, Fun}; {arity, Other} -> {error, {plan_fun_arity, [{'fun', Fun}, {arity, Other}]}} end;filter_plan_element(Other) -> {error, {plan_element_type, [{plan_element, Other}]}}.filter_plan([PlanElem|Plan], Plan2) -> case filter_plan_element(PlanElem) of {ok, PlanElem2} -> filter_plan(Plan, [PlanElem2|Plan2]); {error, _Reason}=Error -> Error end;filter_plan([], Plan2) -> {ok, lists:reverse(Plan2)}.is_whole_integer(Int) when erlang:is_integer(Int) -> if Int >= 0 -> true; true -> false end;is_whole_integer(_Other) -> false.filter_count(infinity) -> {ok, infinity};filter_count(Count) -> case is_whole_integer(Count) of true -> {ok, Count}; false -> {error, {count_range_or_type, [{count, Count}]}} end.filter_terminate_timeout(infinity) -> {ok, infinity};filter_terminate_timeout(TerminateTimeout) -> case is_whole_integer(TerminateTimeout) of true -> {ok, TerminateTimeout}; false -> {error, {terminate_timeout_range_or_type, [{terminate_timeout, TerminateTimeout}]}} end.filter_type(worker) -> {ok, worker};filter_type(supervisor) -> {ok, supervisor};filter_type(Other) -> {error, {type_type, [{type, Other}]}}.filter_modules(dynamic) -> {ok, dynamic};filter_modules(Mod) when erlang:is_atom(Mod) -> {ok, [Mod]};filter_modules(Mods) when erlang:is_list(Mods) -> {ok, Mods};filter_modules(Other) -> {error, {modules_type, [{modules, Other}]}}.filter_append(Bool) when erlang:is_boolean(Bool) -> {ok, Bool};filter_append(Other) -> {error, {append_type, [{append, Other}]}}.check_childspecs([], _DefChildSpec) -> {ok, []};check_childspecs(ChildSpecs, DefChildSpec) -> check_childspecs(ChildSpecs, DefChildSpec, []).