|
|
@@ -21,6 +21,13 @@
|
|
|
, prep_stop/1
|
|
|
]).
|
|
|
|
|
|
+%% Shutdown and reboot
|
|
|
+-export([ shutdown/1
|
|
|
+ , ensure_apps_started/0
|
|
|
+ ]).
|
|
|
+
|
|
|
+-export([sorted_reboot_apps/0]).
|
|
|
+
|
|
|
-behaviour(application).
|
|
|
|
|
|
-include_lib("emqx/include/logger.hrl").
|
|
|
@@ -28,18 +35,21 @@
|
|
|
start(_Type, _Args) ->
|
|
|
ok = set_backtrace_depth(),
|
|
|
ok = print_otp_version_warning(),
|
|
|
- _ = load_modules(),
|
|
|
|
|
|
+ %% need to load some app envs
|
|
|
+ %% TODO delete it once emqx boot does not depend on modules envs
|
|
|
+ _ = load_modules(),
|
|
|
ok = load_config_files(),
|
|
|
|
|
|
{ok, RootSupPid} = emqx_machine_sup:start_link(),
|
|
|
|
|
|
- {ok, _} = application:ensure_all_started(emqx),
|
|
|
+ ok = ensure_apps_started(),
|
|
|
|
|
|
_ = emqx_plugins:load(),
|
|
|
- _ = start_modules(),
|
|
|
|
|
|
ok = print_vsn(),
|
|
|
+
|
|
|
+ ok = start_autocluster(),
|
|
|
{ok, RootSupPid}.
|
|
|
|
|
|
prep_stop(_State) ->
|
|
|
@@ -71,13 +81,9 @@ print_vsn() ->
|
|
|
-ifndef(EMQX_ENTERPRISE).
|
|
|
load_modules() ->
|
|
|
application:load(emqx_modules).
|
|
|
-start_modules() ->
|
|
|
- application:ensure_all_started(emqx_modules).
|
|
|
-else.
|
|
|
load_modules() ->
|
|
|
ok.
|
|
|
-start_modules() ->
|
|
|
- ok.
|
|
|
-endif.
|
|
|
|
|
|
load_config_files() ->
|
|
|
@@ -88,3 +94,83 @@ load_config_files() ->
|
|
|
ok = emqx_config:init_load(emqx_machine_schema, ConfFiles),
|
|
|
%% to avoid config being loaded again when emqx app starts.
|
|
|
ok = emqx_app:set_init_config_load_done().
|
|
|
+
|
|
|
+start_autocluster() ->
|
|
|
+ ekka:callback(prepare, fun ?MODULE:shutdown/1),
|
|
|
+ ekka:callback(reboot, fun ?MODULE:ensure_apps_started/0),
|
|
|
+ _ = ekka:autocluster(emqx), %% returns 'ok' or a pid or 'any()' as in spec
|
|
|
+ ok.
|
|
|
+
|
|
|
+shutdown(Reason) ->
|
|
|
+ ?SLOG(critical, #{msg => "stopping_apps", reason => Reason}),
|
|
|
+ _ = emqx_alarm_handler:unload(),
|
|
|
+ lists:foreach(fun stop_one_app/1, lists:reverse(sorted_reboot_apps())).
|
|
|
+
|
|
|
+stop_one_app(App) ->
|
|
|
+ ?SLOG(debug, #{msg => "stopping_app", app => App}),
|
|
|
+ application:stop(App).
|
|
|
+
|
|
|
+ensure_apps_started() ->
|
|
|
+ lists:foreach(fun start_one_app/1, sorted_reboot_apps()).
|
|
|
+
|
|
|
+start_one_app(App) ->
|
|
|
+ ?SLOG(debug, #{msg => "starting_app", app => App}),
|
|
|
+ case application:ensure_all_started(App) of
|
|
|
+ {ok, Apps} ->
|
|
|
+ ?SLOG(debug, #{msg => "started_apps", apps => [App | Apps]});
|
|
|
+ {error, Reason} ->
|
|
|
+ ?SLOG(critical, #{msg => "failed_to_start_app", app => App, reason => Reason}),
|
|
|
+ error({faile_to_start_app, App, Reason})
|
|
|
+ end.
|
|
|
+
|
|
|
+%% list of app names which should be rebooted when:
|
|
|
+%% 1. due to static static config change
|
|
|
+%% 2. after join a cluster
|
|
|
+reboot_apps() ->
|
|
|
+ [gproc, esockd, ranch, cowboy, ekka, quicer, emqx | ?EMQX_DEP_APPS].
|
|
|
+
|
|
|
+%% quicer can not be added to emqx's .app because it might be opted out at build time
|
|
|
+implicit_deps() ->
|
|
|
+ [{emqx, [quicer]}].
|
|
|
+
|
|
|
+sorted_reboot_apps() ->
|
|
|
+ Apps = [{App, app_deps(App)} || App <- reboot_apps()],
|
|
|
+ sorted_reboot_apps(Apps ++ implicit_deps()).
|
|
|
+
|
|
|
+app_deps(App) ->
|
|
|
+ case application:get_key(App, applications) of
|
|
|
+ undefined -> [];
|
|
|
+ {ok, List} -> lists:filter(fun(A) -> lists:member(A, reboot_apps()) end, List)
|
|
|
+ end.
|
|
|
+
|
|
|
+sorted_reboot_apps(Apps) ->
|
|
|
+ G = digraph:new(),
|
|
|
+ lists:foreach(fun({App, Deps}) -> add_app(G, App, Deps) end, Apps),
|
|
|
+ case digraph_utils:topsort(G) of
|
|
|
+ Sorted when is_list(Sorted) ->
|
|
|
+ Sorted;
|
|
|
+ false ->
|
|
|
+ Loops = find_loops(G),
|
|
|
+ error({circular_application_dependency, Loops})
|
|
|
+ end.
|
|
|
+
|
|
|
+add_app(G, App, undefined) ->
|
|
|
+ ?SLOG(debug, #{msg => "app_is_not_loaded", app => App}),
|
|
|
+ %% not loaded
|
|
|
+ add_app(G, App, []);
|
|
|
+add_app(_G, _App, []) ->
|
|
|
+ ok;
|
|
|
+add_app(G, App, [Dep | Deps]) ->
|
|
|
+ digraph:add_vertex(G, App),
|
|
|
+ digraph:add_vertex(G, Dep),
|
|
|
+ digraph:add_edge(G, Dep, App), %% dep -> app as dependency
|
|
|
+ add_app(G, App, Deps).
|
|
|
+
|
|
|
+find_loops(G) ->
|
|
|
+ lists:filtermap(
|
|
|
+ fun (App) ->
|
|
|
+ case digraph:get_short_cycle(G, App) of
|
|
|
+ false -> false;
|
|
|
+ Apps -> {true, Apps}
|
|
|
+ end
|
|
|
+ end, digraph:vertices(G)).
|