1%% This Source Code Form is subject to the terms of the Mozilla Public
2%% License, v. 2.0. If a copy of the MPL was not distributed with this
3%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
4%%
5%% Copyright (c) 2007-2021 VMware, Inc. or its affiliates.  All rights reserved.
6%%
7
8-module(app_utils).
9
10-export([load_applications/1,
11         start_applications/1, start_applications/2, start_applications/3,
12         stop_applications/1, stop_applications/2, app_dependency_order/2,
13         app_dependencies/1]).
14
15-type error_handler() :: fun((atom(), any()) -> 'ok' | no_return()).
16-type restart_type() :: 'permanent' | 'transient' | 'temporary'.
17
18-spec load_applications([atom()])                   -> 'ok'.
19-spec start_applications([atom()])                  -> 'ok'.
20-spec stop_applications([atom()])                   -> 'ok'.
21-spec start_applications([atom()], error_handler()) -> 'ok'.
22-spec start_applications([atom()], error_handler(), #{atom() => restart_type()}) -> 'ok'.
23-spec stop_applications([atom()], error_handler())  -> 'ok'.
24-spec app_dependency_order([atom()], boolean())     -> [digraph:vertex()].
25-spec app_dependencies(atom())                      -> [atom()].
26-spec failed_to_start_app(atom(), any())            -> no_return().
27-spec failed_to_stop_app(atom(), any())             -> no_return().
28
29%%---------------------------------------------------------------------------
30%% Public API
31
32load_applications(Apps) ->
33    load_applications(queue:from_list(Apps), sets:new()),
34    ok.
35
36start_applications(Apps) ->
37    start_applications(
38      Apps, fun failed_to_start_app/2).
39
40stop_applications(Apps) ->
41    stop_applications(
42      Apps, fun failed_to_stop_app/2).
43
44failed_to_start_app(App, Reason) ->
45    throw({error, {cannot_start_application, App, Reason}}).
46
47failed_to_stop_app(App, Reason) ->
48    throw({error, {cannot_stop_application, App, Reason}}).
49
50start_applications(Apps, ErrorHandler) ->
51    start_applications(Apps, ErrorHandler, #{}).
52
53start_applications(Apps, ErrorHandler, RestartTypes) ->
54    manage_applications(fun lists:foldl/3,
55                        fun(App) -> ensure_all_started(App, RestartTypes) end,
56                        fun application:stop/1,
57                        already_started,
58                        ErrorHandler,
59                        Apps).
60
61stop_applications(Apps, ErrorHandler) ->
62    manage_applications(fun lists:foldr/3,
63                        fun(App) ->
64                            rabbit_log:info("Stopping application '~s'", [App]),
65                            application:stop(App)
66                        end,
67                        fun(App) -> ensure_all_started(App, #{}) end,
68                        not_started,
69                        ErrorHandler,
70                        Apps).
71
72app_dependency_order(RootApps, StripUnreachable) ->
73    {ok, G} = rabbit_misc:build_acyclic_graph(
74                fun ({App, _Deps}) -> [{App, App}] end,
75                fun ({App,  Deps}) -> [{Dep, App} || Dep <- Deps] end,
76                [{App, app_dependencies(App)} ||
77                    {App, _Desc, _Vsn} <- application:loaded_applications()]),
78    try
79        case StripUnreachable of
80            true -> digraph:del_vertices(G, digraph:vertices(G) --
81                     digraph_utils:reachable(RootApps, G));
82            false -> ok
83        end,
84        digraph_utils:topsort(G)
85    after
86        true = digraph:delete(G)
87    end.
88
89%%---------------------------------------------------------------------------
90%% Private API
91
92load_applications(Worklist, Loaded) ->
93    case queue:out(Worklist) of
94        {empty, _WorkList} ->
95            ok;
96        {{value, App}, Worklist1} ->
97            case sets:is_element(App, Loaded) of
98                true  -> load_applications(Worklist1, Loaded);
99                false -> case application:load(App) of
100                             ok                             -> ok;
101                             {error, {already_loaded, App}} -> ok;
102                             Error                          -> throw(Error)
103                         end,
104                         load_applications(
105                           queue:join(Worklist1,
106                                      queue:from_list(app_dependencies(App))),
107                           sets:add_element(App, Loaded))
108            end
109    end.
110
111app_dependencies(App) ->
112    case application:get_key(App, applications) of
113        undefined -> [];
114        {ok, Lst} -> Lst
115    end.
116
117manage_applications(Iterate, Do, Undo, SkipError, ErrorHandler, Apps) ->
118    Iterate(fun (App, Acc) ->
119                    case Do(App) of
120                        ok -> [App | Acc];
121                        {ok, []} -> Acc;
122                        {ok, [App]} -> [App | Acc];
123                        {ok, StartedApps} -> StartedApps ++ Acc;
124                        {error, {SkipError, _}} -> Acc;
125                        {error, Reason} ->
126                            lists:foreach(Undo, Acc),
127                            ErrorHandler(App, Reason)
128                    end
129            end, [], Apps),
130    ok.
131
132%% Stops the Erlang VM when the rabbit application stops abnormally
133%% i.e. message store reaches its restart limit
134default_restart_type(rabbit) -> transient;
135default_restart_type(_)      -> temporary.
136
137%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
138%%
139%% Code originally from Erlang/OTP source lib/kernel/src/application.erl
140%% and modified to use RestartTypes map
141%%
142ensure_all_started(Application, RestartTypes) ->
143    case ensure_all_started(Application, RestartTypes, []) of
144        {ok, Started} ->
145            {ok, lists:reverse(Started)};
146        {error, Reason, Started} ->
147            _ = [application:stop(App) || App <- Started],
148        {error, Reason}
149    end.
150
151ensure_all_started(Application, RestartTypes, Started) ->
152    RestartType = maps:get(Application, RestartTypes, default_restart_type(Application)),
153    case application:start(Application, RestartType) of
154        ok ->
155            {ok, [Application | Started]};
156        {error, {already_started, Application}} ->
157            {ok, Started};
158        {error, {not_started, Dependency}} ->
159            case ensure_all_started(Dependency, RestartTypes, Started) of
160                {ok, NewStarted} ->
161                    ensure_all_started(Application, RestartTypes, NewStarted);
162                Error ->
163                    Error
164            end;
165        {error, Reason} ->
166            {error, {Application, Reason}, Started}
167    end.
168