|
|
@@ -0,0 +1,129 @@
|
|
|
+%%--------------------------------------------------------------------
|
|
|
+%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
|
+%%
|
|
|
+%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+%% you may not use this file except in compliance with the License.
|
|
|
+%% You may obtain a copy of the License at
|
|
|
+%%
|
|
|
+%% http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+%%
|
|
|
+%% Unless required by applicable law or agreed to in writing, software
|
|
|
+%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+%% See the License for the specific language governing permissions and
|
|
|
+%% limitations under the License.
|
|
|
+%%--------------------------------------------------------------------
|
|
|
+-module(emqx_machine_app_booter).
|
|
|
+
|
|
|
+%% @doc This process serves as a serialization point for starting and stopping
|
|
|
+%% applications as part of the boot process or when joining a new cluster. One motivation
|
|
|
+%% for this is that a join request might start while the node is still starting its list
|
|
|
+%% of applications (e.g. when booting the first time).
|
|
|
+
|
|
|
+-include_lib("emqx/include/logger.hrl").
|
|
|
+-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
|
|
+
|
|
|
+%% API
|
|
|
+-export([
|
|
|
+ start_link/0,
|
|
|
+
|
|
|
+ start_apps/0,
|
|
|
+ stop_apps/0
|
|
|
+]).
|
|
|
+
|
|
|
+%% `gen_server' API
|
|
|
+-export([
|
|
|
+ init/1,
|
|
|
+ handle_call/3,
|
|
|
+ handle_cast/2,
|
|
|
+ handle_info/2
|
|
|
+]).
|
|
|
+
|
|
|
+%%------------------------------------------------------------------------------
|
|
|
+%% Type declarations
|
|
|
+%%------------------------------------------------------------------------------
|
|
|
+
|
|
|
+%% calls/casts/infos
|
|
|
+-record(start_apps, {}).
|
|
|
+-record(stop_apps, {}).
|
|
|
+
|
|
|
+%%------------------------------------------------------------------------------
|
|
|
+%% API
|
|
|
+%%------------------------------------------------------------------------------
|
|
|
+
|
|
|
+start_link() ->
|
|
|
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
|
|
+
|
|
|
+start_apps() ->
|
|
|
+ gen_server:call(?MODULE, #start_apps{}, infinity).
|
|
|
+
|
|
|
+stop_apps() ->
|
|
|
+ gen_server:call(?MODULE, #stop_apps{}, infinity).
|
|
|
+
|
|
|
+%%------------------------------------------------------------------------------
|
|
|
+%% `gen_server' API
|
|
|
+%%------------------------------------------------------------------------------
|
|
|
+
|
|
|
+init(_Opts) ->
|
|
|
+ %% Ensure that the stop callback is set, so that join requests concurrent to the
|
|
|
+ %% startup are serialized here.
|
|
|
+ %% It would still be problematic if a join request arrives before this process is
|
|
|
+ %% started, though.
|
|
|
+ ekka:callback(stop, fun emqx_machine_boot:stop_apps/0),
|
|
|
+ State = #{},
|
|
|
+ {ok, State}.
|
|
|
+
|
|
|
+handle_call(#start_apps{}, _From, State) ->
|
|
|
+ handle_start_apps(),
|
|
|
+ {reply, ok, State};
|
|
|
+handle_call(#stop_apps{}, _From, State) ->
|
|
|
+ handle_stop_apps(),
|
|
|
+ {reply, ok, State};
|
|
|
+handle_call(_Call, _From, State) ->
|
|
|
+ {reply, ignored, State}.
|
|
|
+
|
|
|
+handle_cast(_Cast, State) ->
|
|
|
+ {noreply, State}.
|
|
|
+
|
|
|
+handle_info(_Info, State) ->
|
|
|
+ {noreply, State}.
|
|
|
+
|
|
|
+%%------------------------------------------------------------------------------
|
|
|
+%% Internal fns
|
|
|
+%%------------------------------------------------------------------------------
|
|
|
+
|
|
|
+handle_start_apps() ->
|
|
|
+ ?SLOG(notice, #{msg => "(re)starting_emqx_apps"}),
|
|
|
+ lists:foreach(fun start_one_app/1, emqx_machine_boot:sorted_reboot_apps()),
|
|
|
+ ?tp(emqx_machine_boot_apps_started, #{}).
|
|
|
+
|
|
|
+handle_stop_apps() ->
|
|
|
+ ?SLOG(notice, #{msg => "stopping_emqx_apps"}),
|
|
|
+ _ = emqx_alarm_handler:unload(),
|
|
|
+ ok = emqx_conf_app:unset_config_loaded(),
|
|
|
+ ok = emqx_plugins:ensure_stopped(),
|
|
|
+ lists:foreach(fun stop_one_app/1, lists:reverse(emqx_machine_boot:sorted_reboot_apps())).
|
|
|
+
|
|
|
+start_one_app(App) ->
|
|
|
+ ?SLOG(debug, #{msg => "starting_app", app => App}),
|
|
|
+ case application:ensure_all_started(App, emqx_machine_boot:restart_type(App)) of
|
|
|
+ {ok, Apps} ->
|
|
|
+ ?SLOG(debug, #{msg => "started_apps", apps => Apps});
|
|
|
+ {error, Reason} ->
|
|
|
+ ?SLOG(critical, #{msg => "failed_to_start_app", app => App, reason => Reason}),
|
|
|
+ error({failed_to_start_app, App, Reason})
|
|
|
+ end.
|
|
|
+
|
|
|
+stop_one_app(App) ->
|
|
|
+ ?SLOG(debug, #{msg => "stopping_app", app => App}),
|
|
|
+ try
|
|
|
+ _ = application:stop(App)
|
|
|
+ catch
|
|
|
+ C:E ->
|
|
|
+ ?SLOG(error, #{
|
|
|
+ msg => "failed_to_stop_app",
|
|
|
+ app => App,
|
|
|
+ exception => C,
|
|
|
+ reason => E
|
|
|
+ })
|
|
|
+ end.
|