diff options
Diffstat (limited to 'deps/rabbit_common/src/app_utils.erl')
-rw-r--r-- | deps/rabbit_common/src/app_utils.erl | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/deps/rabbit_common/src/app_utils.erl b/deps/rabbit_common/src/app_utils.erl new file mode 100644 index 0000000000..df965575be --- /dev/null +++ b/deps/rabbit_common/src/app_utils.erl @@ -0,0 +1,167 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. +%% + +-module(app_utils). + +-export([load_applications/1, + start_applications/1, start_applications/2, start_applications/3, + stop_applications/1, stop_applications/2, app_dependency_order/2, + app_dependencies/1]). + +-type error_handler() :: fun((atom(), any()) -> 'ok' | no_return()). +-type restart_type() :: 'permanent' | 'transient' | 'temporary'. + +-spec load_applications([atom()]) -> 'ok'. +-spec start_applications([atom()]) -> 'ok'. +-spec stop_applications([atom()]) -> 'ok'. +-spec start_applications([atom()], error_handler()) -> 'ok'. +-spec start_applications([atom()], error_handler(), #{atom() => restart_type()}) -> 'ok'. +-spec stop_applications([atom()], error_handler()) -> 'ok'. +-spec app_dependency_order([atom()], boolean()) -> [digraph:vertex()]. +-spec app_dependencies(atom()) -> [atom()]. +-spec failed_to_start_app(atom(), any()) -> no_return(). +-spec failed_to_stop_app(atom(), any()) -> no_return(). + +%%--------------------------------------------------------------------------- +%% Public API + +load_applications(Apps) -> + load_applications(queue:from_list(Apps), sets:new()), + ok. + +start_applications(Apps) -> + start_applications( + Apps, fun failed_to_start_app/2). + +stop_applications(Apps) -> + stop_applications( + Apps, fun failed_to_stop_app/2). + +failed_to_start_app(App, Reason) -> + throw({error, {cannot_start_application, App, Reason}}). + +failed_to_stop_app(App, Reason) -> + throw({error, {cannot_stop_application, App, Reason}}). + +start_applications(Apps, ErrorHandler) -> + start_applications(Apps, ErrorHandler, #{}). + +start_applications(Apps, ErrorHandler, RestartTypes) -> + manage_applications(fun lists:foldl/3, + fun(App) -> ensure_all_started(App, RestartTypes) end, + fun application:stop/1, + already_started, + ErrorHandler, + Apps). + +stop_applications(Apps, ErrorHandler) -> + manage_applications(fun lists:foldr/3, + fun(App) -> + rabbit_log:info("Stopping application '~s'", [App]), + application:stop(App) + end, + fun(App) -> ensure_all_started(App, #{}) end, + not_started, + ErrorHandler, + Apps). + +app_dependency_order(RootApps, StripUnreachable) -> + {ok, G} = rabbit_misc:build_acyclic_graph( + fun ({App, _Deps}) -> [{App, App}] end, + fun ({App, Deps}) -> [{Dep, App} || Dep <- Deps] end, + [{App, app_dependencies(App)} || + {App, _Desc, _Vsn} <- application:loaded_applications()]), + try + case StripUnreachable of + true -> digraph:del_vertices(G, digraph:vertices(G) -- + digraph_utils:reachable(RootApps, G)); + false -> ok + end, + digraph_utils:topsort(G) + after + true = digraph:delete(G) + end. + +%%--------------------------------------------------------------------------- +%% Private API + +load_applications(Worklist, Loaded) -> + case queue:out(Worklist) of + {empty, _WorkList} -> + ok; + {{value, App}, Worklist1} -> + case sets:is_element(App, Loaded) of + true -> load_applications(Worklist1, Loaded); + false -> case application:load(App) of + ok -> ok; + {error, {already_loaded, App}} -> ok; + Error -> throw(Error) + end, + load_applications( + queue:join(Worklist1, + queue:from_list(app_dependencies(App))), + sets:add_element(App, Loaded)) + end + end. + +app_dependencies(App) -> + case application:get_key(App, applications) of + undefined -> []; + {ok, Lst} -> Lst + end. + +manage_applications(Iterate, Do, Undo, SkipError, ErrorHandler, Apps) -> + Iterate(fun (App, Acc) -> + case Do(App) of + ok -> [App | Acc]; + {ok, []} -> Acc; + {ok, [App]} -> [App | Acc]; + {ok, StartedApps} -> StartedApps ++ Acc; + {error, {SkipError, _}} -> Acc; + {error, Reason} -> + lists:foreach(Undo, Acc), + ErrorHandler(App, Reason) + end + end, [], Apps), + ok. + +%% Stops the Erlang VM when the rabbit application stops abnormally +%% i.e. message store reaches its restart limit +default_restart_type(rabbit) -> transient; +default_restart_type(_) -> temporary. + +%% Copyright Ericsson AB 1996-2016. All Rights Reserved. +%% +%% Code originally from Erlang/OTP source lib/kernel/src/application.erl +%% and modified to use RestartTypes map +%% +ensure_all_started(Application, RestartTypes) -> + case ensure_all_started(Application, RestartTypes, []) of + {ok, Started} -> + {ok, lists:reverse(Started)}; + {error, Reason, Started} -> + _ = [application:stop(App) || App <- Started], + {error, Reason} + end. + +ensure_all_started(Application, RestartTypes, Started) -> + RestartType = maps:get(Application, RestartTypes, default_restart_type(Application)), + case application:start(Application, RestartType) of + ok -> + {ok, [Application | Started]}; + {error, {already_started, Application}} -> + {ok, Started}; + {error, {not_started, Dependency}} -> + case ensure_all_started(Dependency, RestartTypes, Started) of + {ok, NewStarted} -> + ensure_all_started(Application, RestartTypes, NewStarted); + Error -> + Error + end; + {error, Reason} -> + {error, {Application, Reason}, Started} + end. |