Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ MnesiaCore.*
/debug/
/doc/
/ebin/
/escript
/escript.lock
/etc/
/logs/
/plugins/
/plugins.lock
/test/ct.cover.spec
/test/config_schema_SUITE_data/schema/**
/xrefr
/escript
/sbin/
/sbin.lock
rabbit.d

# Generated documentation.
Expand Down
31 changes: 13 additions & 18 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,19 @@ define PROJECT_ENV
]
endef

LOCAL_DEPS = sasl mnesia os_mon inets compiler public_key crypto ssl syntax_tools xmerl
# With Erlang.mk default behavior, the value of `$(APPS_DIR)` is always
# relative to the top-level executed Makefile. In our case, it could be
# a plugin for instance. However, the rabbitmq_prelaunch application is
# in this repository, not the plugin's. That's why we need to override
# this value here.
APPS_DIR := $(CURDIR)/apps

LOCAL_DEPS = sasl rabbitmq_prelaunch os_mon inets compiler public_key crypto ssl syntax_tools xmerl
BUILD_DEPS = rabbitmq_cli syslog
DEPS = ranch lager rabbit_common ra sysmon_handler stdout_formatter recon observer_cli
DEPS = cuttlefish ranch lager rabbit_common ra sysmon_handler stdout_formatter recon observer_cli
TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp_client meck proper

dep_cuttlefish = hex 2.2.0
dep_syslog = git https://github.com/schlagert/syslog 3.4.5

define usage_xml_to_erl
Expand All @@ -164,6 +172,9 @@ ERLANG_MK_COMMIT = rabbitmq-tmp
include rabbitmq-components.mk
include erlang.mk

# See above why we mess with `$(APPS_DIR)`.
unexport APPS_DIR

ifeq ($(strip $(BATS)),)
BATS := $(ERLANG_MK_TMP)/bats/bin/bats
endif
Expand Down Expand Up @@ -240,20 +251,6 @@ USE_PROPER_QC := $(shell $(ERL) -eval 'io:format({module, proper} =:= code:ensur
RMQ_ERLC_OPTS += $(if $(filter true,$(USE_PROPER_QC)),-Duse_proper_qc)
endif

.PHONY: copy-escripts clean-extra-sources clean-escripts

CLI_ESCRIPTS_DIR = escript

copy-escripts:
$(gen_verbose) $(MAKE) -C $(DEPS_DIR)/rabbitmq_cli install \
PREFIX="$(abspath $(CLI_ESCRIPTS_DIR))" \
DESTDIR=

clean:: clean-escripts

clean-escripts:
$(gen_verbose) rm -rf "$(CLI_ESCRIPTS_DIR)"

# --------------------------------------------------------------------
# Documentation.
# --------------------------------------------------------------------
Expand Down Expand Up @@ -297,5 +294,3 @@ distclean:: distclean-manpages

distclean-manpages::
$(gen_verbose) rm -f $(WEB_MANPAGES)

app-build: copy-escripts
12 changes: 12 additions & 0 deletions apps/rabbitmq_prelaunch/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
*~
.sw?
.*.sw?
*.beam
*.coverdata
/ebin/
/.erlang.mk/
/rabbitmq_prelaunch.d
/xrefr

# Dialyzer
*.plt
11 changes: 11 additions & 0 deletions apps/rabbitmq_prelaunch/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
PROJECT = rabbitmq_prelaunch
PROJECT_DESCRIPTION = RabbitMQ prelaunch setup
PROJECT_VERSION = 1.0.0
PROJECT_MOD = rabbit_prelaunch_app

DEPS = rabbit_common lager

DEP_PLUGINS = rabbit_common/mk/rabbitmq-build.mk

include ../../rabbitmq-components.mk
include ../../erlang.mk
274 changes: 274 additions & 0 deletions apps/rabbitmq_prelaunch/src/rabbit_prelaunch.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
-module(rabbit_prelaunch).

-include_lib("eunit/include/eunit.hrl").

-export([run_prelaunch_first_phase/0,
assert_mnesia_is_stopped/0,
get_context/0,
get_boot_state/0,
set_boot_state/1,
is_boot_state_reached/1,
wait_for_boot_state/1,
wait_for_boot_state/2,
get_stop_reason/0,
set_stop_reason/1,
clear_stop_reason/0,
is_initial_pass/0,
initial_pass_finished/0,
shutdown_func/1]).

-define(PT_KEY_CONTEXT, {?MODULE, context}).
-define(PT_KEY_BOOT_STATE, {?MODULE, boot_state}).
-define(PT_KEY_INITIAL_PASS, {?MODULE, initial_pass_finished}).
-define(PT_KEY_SHUTDOWN_FUNC, {?MODULE, chained_shutdown_func}).
-define(PT_KEY_STOP_REASON, {?MODULE, stop_reason}).

run_prelaunch_first_phase() ->
try
do_run()
catch
throw:{error, _} = Error ->
rabbit_prelaunch_errors:log_error(Error),
set_stop_reason(Error),
set_boot_state(stopped),
Error;
Class:Exception:Stacktrace ->
rabbit_prelaunch_errors:log_exception(
Class, Exception, Stacktrace),
Error = {error, Exception},
set_stop_reason(Error),
set_boot_state(stopped),
Error
end.

do_run() ->
%% Indicate RabbitMQ is booting.
clear_stop_reason(),
set_boot_state(booting),

%% Configure dbg if requested.
rabbit_prelaunch_early_logging:enable_quick_dbg(rabbit_env:dbg_config()),

%% We assert Mnesia is stopped before we run the prelaunch
%% phases.
%%
%% We need this because our cluster consistency check (in the second
%% phase) depends on Mnesia not being started before it has a chance
%% to run.
%%
%% Also, in the initial pass, we don't want Mnesia to run before
%% Erlang distribution is configured.
assert_mnesia_is_stopped(),

%% Get informations to setup logging.
Context0 = rabbit_env:get_context_before_logging_init(),
?assertMatch(#{}, Context0),

%% Setup logging for the prelaunch phase.
ok = rabbit_prelaunch_early_logging:setup_early_logging(Context0, true),

IsInitialPass = is_initial_pass(),
case IsInitialPass of
true ->
rabbit_log_prelaunch:debug(""),
rabbit_log_prelaunch:debug(
"== Prelaunch phase [1/2] (initial pass) =="),
rabbit_log_prelaunch:debug("");
false ->
rabbit_log_prelaunch:debug(""),
rabbit_log_prelaunch:debug("== Prelaunch phase [1/2] =="),
rabbit_log_prelaunch:debug("")
end,
rabbit_env:log_process_env(),

%% Load rabbitmq-env.conf, redo logging setup and continue.
Context1 = rabbit_env:get_context_after_logging_init(Context0),
?assertMatch(#{}, Context1),
ok = rabbit_prelaunch_early_logging:setup_early_logging(Context1, true),
rabbit_env:log_process_env(),

%% Complete context now that we have the final environment loaded.
Context2 = rabbit_env:get_context_after_reloading_env(Context1),
?assertMatch(#{}, Context2),
store_context(Context2),
rabbit_env:log_context(Context2),
ok = setup_shutdown_func(),

Context = Context2#{initial_pass => IsInitialPass},

rabbit_env:context_to_code_path(Context),
rabbit_env:context_to_app_env_vars(Context),

%% 1. Erlang/OTP compatibility check.
ok = rabbit_prelaunch_erlang_compat:check(Context),

%% 2. Erlang distribution check + start.
ok = rabbit_prelaunch_dist:setup(Context),

%% 3. Write PID file.
rabbit_log_prelaunch:debug(""),
_ = write_pid_file(Context),
ignore.

assert_mnesia_is_stopped() ->
?assertNot(lists:keymember(mnesia, 1, application:which_applications())).

store_context(Context) when is_map(Context) ->
persistent_term:put(?PT_KEY_CONTEXT, Context).

get_context() ->
case persistent_term:get(?PT_KEY_CONTEXT, undefined) of
undefined -> undefined;
Context -> Context#{initial_pass => is_initial_pass()}
end.

get_boot_state() ->
persistent_term:get(?PT_KEY_BOOT_STATE, stopped).

set_boot_state(stopped) ->
rabbit_log_prelaunch:debug("Change boot state to `stopped`"),
persistent_term:erase(?PT_KEY_BOOT_STATE);
set_boot_state(BootState) ->
rabbit_log_prelaunch:debug("Change boot state to `~s`", [BootState]),
?assert(is_boot_state_valid(BootState)),
persistent_term:put(?PT_KEY_BOOT_STATE, BootState).

wait_for_boot_state(BootState) ->
wait_for_boot_state(BootState, infinity).

wait_for_boot_state(BootState, Timeout) ->
?assert(is_boot_state_valid(BootState)),
wait_for_boot_state1(BootState, Timeout).

wait_for_boot_state1(BootState, infinity = Timeout) ->
case is_boot_state_reached(BootState) of
true -> ok;
false -> wait_for_boot_state1(BootState, Timeout)
end;
wait_for_boot_state1(BootState, Timeout)
when is_integer(Timeout) andalso Timeout >= 0 ->
case is_boot_state_reached(BootState) of
true -> ok;
false -> Wait = 200,
timer:sleep(Wait),
wait_for_boot_state1(BootState, Timeout - Wait)
end;
wait_for_boot_state1(_, _) ->
{error, timeout}.

boot_state_idx(stopped) -> 0;
boot_state_idx(booting) -> 1;
boot_state_idx(ready) -> 2;
boot_state_idx(stopping) -> 3;
boot_state_idx(_) -> undefined.

is_boot_state_valid(BootState) ->
is_integer(boot_state_idx(BootState)).

is_boot_state_reached(TargetBootState) ->
is_boot_state_reached(get_boot_state(), TargetBootState).

is_boot_state_reached(CurrentBootState, CurrentBootState) ->
true;
is_boot_state_reached(stopping, stopped) ->
false;
is_boot_state_reached(_CurrentBootState, stopped) ->
true;
is_boot_state_reached(stopped, _TargetBootState) ->
true;
is_boot_state_reached(CurrentBootState, TargetBootState) ->
boot_state_idx(TargetBootState) =< boot_state_idx(CurrentBootState).

get_stop_reason() ->
persistent_term:get(?PT_KEY_STOP_REASON, undefined).

set_stop_reason(Reason) ->
case get_stop_reason() of
undefined ->
rabbit_log_prelaunch:debug("Set stop reason to: ~p", [Reason]),
persistent_term:put(?PT_KEY_STOP_REASON, Reason);
_ ->
ok
end.

clear_stop_reason() ->
persistent_term:erase(?PT_KEY_STOP_REASON).

is_initial_pass() ->
not persistent_term:get(?PT_KEY_INITIAL_PASS, false).

initial_pass_finished() ->
persistent_term:put(?PT_KEY_INITIAL_PASS, true).

setup_shutdown_func() ->
ThisMod = ?MODULE,
ThisFunc = shutdown_func,
ExistingShutdownFunc = application:get_env(kernel, shutdown_func),
case ExistingShutdownFunc of
{ok, {ThisMod, ThisFunc}} ->
ok;
{ok, {ExistingMod, ExistingFunc}} ->
rabbit_log_prelaunch:debug(
"Setting up kernel shutdown function: ~s:~s/1 "
"(chained with ~s:~s/1)",
[ThisMod, ThisFunc, ExistingMod, ExistingFunc]),
ok = persistent_term:put(
?PT_KEY_SHUTDOWN_FUNC,
ExistingShutdownFunc),
ok = record_kernel_shutdown_func(ThisMod, ThisFunc);
_ ->
rabbit_log_prelaunch:debug(
"Setting up kernel shutdown function: ~s:~s/1",
[ThisMod, ThisFunc]),
ok = record_kernel_shutdown_func(ThisMod, ThisFunc)
end.

record_kernel_shutdown_func(Mod, Func) ->
application:set_env(
kernel, shutdown_func, {Mod, Func},
[{persistent, true}]).

shutdown_func(Reason) ->
rabbit_log_prelaunch:debug(
"Running ~s:shutdown_func() as part of `kernel` shutdown", [?MODULE]),
Context = get_context(),
remove_pid_file(Context),
ChainedShutdownFunc = persistent_term:get(
?PT_KEY_SHUTDOWN_FUNC,
undefined),
case ChainedShutdownFunc of
{ChainedMod, ChainedFunc} -> ChainedMod:ChainedFunc(Reason);
_ -> ok
end.

write_pid_file(#{pid_file := PidFile}) ->
rabbit_log_prelaunch:debug("Writing PID file: ~s", [PidFile]),
case filelib:ensure_dir(PidFile) of
ok ->
OSPid = os:getpid(),
case file:write_file(PidFile, OSPid) of
ok ->
ok;
{error, Reason} = Error ->
rabbit_log_prelaunch:warning(
"Failed to write PID file \"~s\": ~s",
[PidFile, file:format_error(Reason)]),
Error
end;
{error, Reason} = Error ->
rabbit_log_prelaunch:warning(
"Failed to create PID file \"~s\" directory: ~s",
[PidFile, file:format_error(Reason)]),
Error
end;
write_pid_file(_) ->
ok.

remove_pid_file(#{pid_file := PidFile, keep_pid_file_on_exit := true}) ->
rabbit_log_prelaunch:debug("Keeping PID file: ~s", [PidFile]),
ok;
remove_pid_file(#{pid_file := PidFile}) ->
rabbit_log_prelaunch:debug("Deleting PID file: ~s", [PidFile]),
_ = file:delete(PidFile);
remove_pid_file(_) ->
ok.
11 changes: 11 additions & 0 deletions apps/rabbitmq_prelaunch/src/rabbit_prelaunch_app.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-module(rabbit_prelaunch_app).
-behaviour(application).

-export([start/2]).
-export([stop/1]).

start(_Type, _Args) ->
rabbit_prelaunch_sup:start_link().

stop(_State) ->
ok.
Loading