summaryrefslogtreecommitdiff
path: root/deps/rabbit_common/src/app_utils.erl
diff options
context:
space:
mode:
Diffstat (limited to 'deps/rabbit_common/src/app_utils.erl')
-rw-r--r--deps/rabbit_common/src/app_utils.erl167
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.