1-module(rabbit_prelaunch). 2 3-include_lib("kernel/include/logger.hrl"). 4-include_lib("eunit/include/eunit.hrl"). 5 6-include_lib("rabbit_common/include/logging.hrl"). 7 8-export([run_prelaunch_first_phase/0, 9 assert_mnesia_is_stopped/0, 10 get_context/0, 11 get_stop_reason/0, 12 set_stop_reason/1, 13 clear_stop_reason/0, 14 is_initial_pass/0, 15 initial_pass_finished/0, 16 shutdown_func/1]). 17 18-ifdef(TEST). 19-export([store_context/1, 20 clear_context_cache/0]). 21-endif. 22 23-define(PT_KEY_CONTEXT, {?MODULE, context}). 24-define(PT_KEY_INITIAL_PASS, {?MODULE, initial_pass_finished}). 25-define(PT_KEY_SHUTDOWN_FUNC, {?MODULE, chained_shutdown_func}). 26-define(PT_KEY_STOP_REASON, {?MODULE, stop_reason}). 27 28run_prelaunch_first_phase() -> 29 try 30 ok = logger:set_process_metadata( 31 #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), 32 do_run() 33 catch 34 throw:{error, _} = Error -> 35 rabbit_prelaunch_errors:log_error(Error), 36 set_stop_reason(Error), 37 rabbit_boot_state:set(stopped), 38 Error; 39 Class:Exception:Stacktrace -> 40 rabbit_prelaunch_errors:log_exception( 41 Class, Exception, Stacktrace), 42 Error = {error, Exception}, 43 set_stop_reason(Error), 44 rabbit_boot_state:set(stopped), 45 Error 46 end. 47 48do_run() -> 49 %% Indicate RabbitMQ is booting. 50 clear_stop_reason(), 51 rabbit_boot_state:set(booting), 52 53 %% Configure dbg if requested. 54 rabbit_prelaunch_early_logging:enable_quick_dbg(rabbit_env:dbg_config()), 55 56 %% Setup signal handler. 57 ok = rabbit_prelaunch_sighandler:setup(), 58 59 %% We assert Mnesia is stopped before we run the prelaunch 60 %% phases. 61 %% 62 %% We need this because our cluster consistency check (in the second 63 %% phase) depends on Mnesia not being started before it has a chance 64 %% to run. 65 %% 66 %% Also, in the initial pass, we don't want Mnesia to run before 67 %% Erlang distribution is configured. 68 assert_mnesia_is_stopped(), 69 70 %% Get informations to setup logging. 71 Context0 = rabbit_env:get_context_before_logging_init(), 72 ?assertMatch(#{}, Context0), 73 74 %% Setup logging for the prelaunch phase. 75 ok = rabbit_prelaunch_early_logging:setup_early_logging(Context0), 76 77 IsInitialPass = is_initial_pass(), 78 case IsInitialPass of 79 true -> 80 ?LOG_DEBUG(""), 81 ?LOG_DEBUG("== Prelaunch phase [1/2] (initial pass) =="), 82 ?LOG_DEBUG(""); 83 false -> 84 ?LOG_DEBUG(""), 85 ?LOG_DEBUG("== Prelaunch phase [1/2] =="), 86 ?LOG_DEBUG("") 87 end, 88 rabbit_env:log_process_env(), 89 90 %% Load rabbitmq-env.conf, redo logging setup and continue. 91 Context1 = rabbit_env:get_context_after_logging_init(Context0), 92 ?assertMatch(#{}, Context1), 93 ok = rabbit_prelaunch_early_logging:setup_early_logging(Context1), 94 rabbit_env:log_process_env(), 95 96 %% Complete context now that we have the final environment loaded. 97 Context2 = rabbit_env:get_context_after_reloading_env(Context1), 98 ?assertMatch(#{}, Context2), 99 store_context(Context2), 100 rabbit_env:log_context(Context2), 101 ok = setup_shutdown_func(), 102 103 Context = Context2#{initial_pass => IsInitialPass}, 104 105 rabbit_env:context_to_code_path(Context), 106 rabbit_env:context_to_app_env_vars(Context), 107 108 %% 1. Erlang/OTP compatibility check. 109 ok = rabbit_prelaunch_erlang_compat:check(Context), 110 111 %% 2. Configuration check + loading. 112 ok = rabbit_prelaunch_conf:setup(Context), 113 114 %% 3. Erlang distribution check + start. 115 ok = rabbit_prelaunch_dist:setup(Context), 116 117 %% 4. Write PID file. 118 ?LOG_DEBUG(""), 119 _ = write_pid_file(Context), 120 121 %% Garbage collect before returning because we do not want 122 %% to keep memory around forever unnecessarily, even if just 123 %% a few MiBs, because it will pollute output from tools like 124 %% Observer or observer_cli. 125 _ = erlang:garbage_collect(), 126 127 ignore. 128 129assert_mnesia_is_stopped() -> 130 ?assertNot(lists:keymember(mnesia, 1, application:which_applications())). 131 132store_context(Context) when is_map(Context) -> 133 persistent_term:put(?PT_KEY_CONTEXT, Context). 134 135get_context() -> 136 case persistent_term:get(?PT_KEY_CONTEXT, undefined) of 137 undefined -> undefined; 138 Context -> Context#{initial_pass => is_initial_pass()} 139 end. 140 141-ifdef(TEST). 142clear_context_cache() -> 143 persistent_term:erase(?PT_KEY_CONTEXT). 144-endif. 145 146get_stop_reason() -> 147 persistent_term:get(?PT_KEY_STOP_REASON, undefined). 148 149set_stop_reason(Reason) -> 150 case get_stop_reason() of 151 undefined -> 152 ?LOG_DEBUG("Set stop reason to: ~p", [Reason]), 153 persistent_term:put(?PT_KEY_STOP_REASON, Reason); 154 _ -> 155 ok 156 end. 157 158clear_stop_reason() -> 159 persistent_term:erase(?PT_KEY_STOP_REASON). 160 161is_initial_pass() -> 162 not persistent_term:get(?PT_KEY_INITIAL_PASS, false). 163 164initial_pass_finished() -> 165 persistent_term:put(?PT_KEY_INITIAL_PASS, true). 166 167setup_shutdown_func() -> 168 ThisMod = ?MODULE, 169 ThisFunc = shutdown_func, 170 ExistingShutdownFunc = application:get_env(kernel, shutdown_func), 171 case ExistingShutdownFunc of 172 {ok, {ThisMod, ThisFunc}} -> 173 ok; 174 {ok, {ExistingMod, ExistingFunc}} -> 175 ?LOG_DEBUG( 176 "Setting up kernel shutdown function: ~s:~s/1 " 177 "(chained with ~s:~s/1)", 178 [ThisMod, ThisFunc, ExistingMod, ExistingFunc]), 179 ok = persistent_term:put( 180 ?PT_KEY_SHUTDOWN_FUNC, 181 ExistingShutdownFunc), 182 ok = record_kernel_shutdown_func(ThisMod, ThisFunc); 183 _ -> 184 ?LOG_DEBUG( 185 "Setting up kernel shutdown function: ~s:~s/1", 186 [ThisMod, ThisFunc]), 187 ok = record_kernel_shutdown_func(ThisMod, ThisFunc) 188 end. 189 190record_kernel_shutdown_func(Mod, Func) -> 191 application:set_env( 192 kernel, shutdown_func, {Mod, Func}, 193 [{persistent, true}]). 194 195shutdown_func(Reason) -> 196 ?LOG_DEBUG( 197 "Running ~s:shutdown_func() as part of `kernel` shutdown", [?MODULE]), 198 Context = get_context(), 199 remove_pid_file(Context), 200 ChainedShutdownFunc = persistent_term:get( 201 ?PT_KEY_SHUTDOWN_FUNC, 202 undefined), 203 case ChainedShutdownFunc of 204 {ChainedMod, ChainedFunc} -> ChainedMod:ChainedFunc(Reason); 205 _ -> ok 206 end. 207 208write_pid_file(#{pid_file := PidFile}) -> 209 ?LOG_DEBUG("Writing PID file: ~s", [PidFile]), 210 case filelib:ensure_dir(PidFile) of 211 ok -> 212 OSPid = os:getpid(), 213 case file:write_file(PidFile, OSPid) of 214 ok -> 215 ok; 216 {error, Reason} = Error -> 217 ?LOG_WARNING( 218 "Failed to write PID file \"~s\": ~s", 219 [PidFile, file:format_error(Reason)]), 220 Error 221 end; 222 {error, Reason} = Error -> 223 ?LOG_WARNING( 224 "Failed to create PID file \"~s\" directory: ~s", 225 [PidFile, file:format_error(Reason)]), 226 Error 227 end; 228write_pid_file(_) -> 229 ok. 230 231remove_pid_file(#{pid_file := PidFile, keep_pid_file_on_exit := true}) -> 232 ?LOG_DEBUG("Keeping PID file: ~s", [PidFile]), 233 ok; 234remove_pid_file(#{pid_file := PidFile}) -> 235 ?LOG_DEBUG("Deleting PID file: ~s", [PidFile]), 236 _ = file:delete(PidFile), 237 ok; 238remove_pid_file(_) -> 239 ok. 240