summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/app_utils.erl16
-rw-r--r--src/rabbit.erl1
-rw-r--r--src/rabbit_boot.erl175
3 files changed, 146 insertions, 46 deletions
diff --git a/src/app_utils.erl b/src/app_utils.erl
index d08dfe6a5a..22e2df44bb 100644
--- a/src/app_utils.erl
+++ b/src/app_utils.erl
@@ -60,7 +60,7 @@ stop_applications(Apps) ->
start_applications(Apps, ErrorHandler) ->
manage_applications(fun lists:foldl/3,
- fun start/1,
+ fun application:start/1,
fun application:stop/1,
already_started,
ErrorHandler,
@@ -101,7 +101,7 @@ direct_dependencies(Root) ->
end,
sets:from_list([Root]),
digraph:out_edges(G, Root)),
- sets:to_list(sets:del_element(rabbit, Deps))
+ sets:to_list(Deps)
catch {graph_error, Reason} ->
{error, Reason}
after
@@ -132,14 +132,10 @@ app_dependency_order(RootApps, StripUnreachable) ->
%%---------------------------------------------------------------------------
%% Private API
-start(rabbit) ->
- case application:start(rabbit) of
- ok -> rabbit_boot:run_boot_steps(rabbit), ok;
- Err -> Err
- end;
-start(App) ->
- rabbit_boot:run_boot_steps(App),
- application:start(App).
+%% It might be worth documenting this on the plugin author's guide/page.
+%% A plugin should expect its boot steps to run /before/ the application
+%% is started, and its cleanup steps to run /after/ the application has
+%% fully stopped.
wait_for_application(Application) ->
case lists:keymember(Application, 1, rabbit_misc:which_applications()) of
diff --git a/src/rabbit.erl b/src/rabbit.erl
index 44cd275d3b..374fccc3a2 100644
--- a/src/rabbit.erl
+++ b/src/rabbit.erl
@@ -411,6 +411,7 @@ start(normal, []) ->
true = register(rabbit, self()),
print_banner(),
log_banner(),
+ rabbit_boot:run_boot_steps(),
{ok, SupPid};
Error ->
Error
diff --git a/src/rabbit_boot.erl b/src/rabbit_boot.erl
index af2bc9c406..e2174ddd9a 100644
--- a/src/rabbit_boot.erl
+++ b/src/rabbit_boot.erl
@@ -18,7 +18,7 @@
-export([boot_with/1, shutdown/1]).
-export([start/1, stop/1]).
--export([run_boot_steps/1]).
+-export([run_boot_steps/0]).
-export([boot_error/2, boot_error/4]).
-ifdef(use_specs).
@@ -27,7 +27,7 @@
-spec(shutdown/1 :: ([atom()]) -> 'ok').
-spec(start/1 :: ([atom()]) -> 'ok').
-spec(stop/1 :: ([atom()]) -> 'ok').
--spec(run_boot_steps/1 :: (atom()) -> 'ok').
+-spec(run_boot_steps/0 :: () -> 'ok').
-spec(boot_error/2 :: (term(), not_available | [tuple()]) -> no_return()).
-spec(boot_error/4 :: (term(), string(), [any()], not_available | [tuple()])
-> no_return()).
@@ -36,6 +36,31 @@
-define(BOOT_FILE, "boot.info").
+%%
+%% When the broker is starting, we must run all the boot steps within the
+%% rabbit:start/2 application callback, after rabbit_sup has started and
+%% before any plugin applications begin to start. To achieve this, we process
+%% the boot steps from all loaded applications and run them in dependent
+%% order.
+%%
+%% When the broker is already running, we must run all the boot steps for
+%% each application/plugin we're starting, plus any other (dependent) steps
+%% that have not been run. To achieve this, we process the boot steps from
+%% all loaded applications, but skip those that have already been run (i.e.,
+%% steps that have been run once whilst or since the broker started).
+%%
+%% Tracking which boot steps have already been run is done via an ets table.
+%% Because we frequently find ourselves needing to query this table without
+%% the rabbit application running (e.g., during the initial boot sequence
+%% and when we've "cold started" a node without any running applications),
+%% this table is serialized to a file after each operation. When the node is
+%% stopped cleanly, the file is deleted. When a node is in the process of
+%% starting, the file is also removed and replaced (since we do not want to
+%% track boot steps from a previous incarnation of the node).
+%%
+%% Cleanup steps...
+%%
+
%%---------------------------------------------------------------------------
%% Public API
@@ -43,6 +68,7 @@ boot_with(StartFun) ->
%% TODO: this should be done with monitors, not links, I think
Marker = spawn_link(fun() -> receive stop -> ok end end),
register(rabbit_boot, Marker),
+ ensure_boot_table(),
try
StartFun()
catch
@@ -73,8 +99,13 @@ shutdown(Apps) ->
start(Apps) ->
try
ensure_boot_table(),
- ok = app_utils:load_applications(Apps),
+ force_reload(Apps),
StartupApps = app_utils:app_dependency_order(Apps, false),
+ io:format("App Start Order: ~p~n", [StartupApps]),
+ case whereis(?MODULE) of
+ undefined -> run_boot_steps();
+ _ -> ok
+ end,
ok = app_utils:start_applications(StartupApps,
handle_app_error(could_not_start))
after
@@ -84,33 +115,71 @@ start(Apps) ->
stop(Apps) ->
ensure_boot_table(),
TargetApps =
- lists:usort(
- lists:append([app_utils:direct_dependencies(App) || App <- Apps])),
+ sets:to_list(
+ lists:foldl(
+ fun(App, Set) ->
+ lists:foldl(fun sets:add_element/2, Set,
+ app_utils:direct_dependencies(App) -- [rabbit])
+ end, sets:new(), Apps)),
+ io:format("Target Apps = ~p~n", [TargetApps]),
try
ok = app_utils:stop_applications(
TargetApps, handle_app_error(error_during_shutdown))
after
try
- [run_steps(App, rabbit_cleanup_step) || App <- TargetApps]
+ io:format("Running Cleanup~n"),
+ BootSteps = load_steps(rabbit_boot_step),
+ ToDelete = [Step || {App, _, _}=Step <- BootSteps,
+ lists:member(App, TargetApps)],
+ io:format("Boot steps on shutdown: ~p~n", [ToDelete]),
+ [begin
+ ets:delete(?MODULE, Step),
+ io:format("Deleted ~p~n", [Step])
+ end || {_, Step, _} <- ToDelete],
+ io:format("Run cleanup steps~n"),
+ run_cleanup_steps(TargetApps)
after
+ io:format("save boot table~n"),
save_boot_table()
- end
+ end,
+ [begin
+ {ok, Mods} = application:get_key(App, modules),
+ io:format("cleanup ~p...~n", [Mods]),
+ [begin
+ code:soft_purge(Mod),
+ code:delete(Mod),
+ false = code:is_loaded(Mod)
+ end || Mod <- Mods],
+ application:unload(App)
+ end || App <- TargetApps]
end.
-run_boot_steps(App) ->
- run_steps(App, rabbit_boot_step).
+run_cleanup_steps(Apps) ->
+ Completed = sets:new(),
+ lists:foldl(
+ fun({_, Name, _}=Step, Acc) ->
+ case sets:is_element(Name, Completed) of
+ true -> Acc;
+ false -> run_boot_step(Step, rabbit_cleanup_step),
+ sets:add_element(Name, Acc)
+ end
+ end,
+ Completed,
+ [Step || {App, _, _}=Step <- load_steps(rabbit_cleanup_step),
+ lists:member(App, Apps)]),
+ ok.
-run_steps(App, StepType) ->
- RootApps = [App|app_utils:direct_dependencies(App)],
- StepAttrs = rabbit_misc:all_app_module_attributes(StepType),
- BootSteps =
- sort_boot_steps(
- lists:usort(
- [{Mod, Steps} || {AppName, Mod, Steps} <- StepAttrs,
- lists:member(AppName, RootApps)])),
- [ok = run_boot_step(Step, StepType) || Step <- BootSteps],
+run_boot_steps() ->
+ Steps = load_steps(rabbit_boot_step),
+ [ok = run_boot_step(Step, rabbit_boot_step) || Step <- Steps],
ok.
+load_steps(StepType) ->
+ StepAttrs = rabbit_misc:all_app_module_attributes(StepType),
+ sort_boot_steps(
+ lists:usort(
+ [{Mod, {AppName, Steps}} || {AppName, Mod, Steps} <- StepAttrs])).
+
boot_error(Term={error, {timeout_waiting_for_tables, _}}, _Stacktrace) ->
AllNodes = rabbit_mnesia:cluster_nodes(all),
{Err, Nodes} =
@@ -139,6 +208,34 @@ boot_error(Reason, Fmt, Args, Stacktrace) ->
%%---------------------------------------------------------------------------
%% Private API
+force_reload(Apps) ->
+ ok = app_utils:load_applications(Apps),
+ ok = do_reload(Apps).
+
+do_reload([App|Apps]) ->
+ case application:get_key(App, modules) of
+ {ok, Modules} -> reload_all(Modules);
+ _ -> ok
+ end,
+ force_reload(Apps);
+do_reload([]) ->
+ ok.
+
+reload_all(Modules) ->
+ [begin
+ case code:soft_purge(Mod) of
+ true -> load_mod(Mod);
+ false -> io:format("unable to purge ~p~n", [Mod])
+ end
+ end || Mod <- Modules].
+
+load_mod(Mod) ->
+ case code:is_loaded(Mod) of
+ {file, preloaded} -> code:load_file(Mod);
+ {file, Path} -> code:load_abs(Path);
+ false -> code:load_file(Mod)
+ end.
+
await_startup(Apps) ->
app_utils:wait_for_applications(Apps).
@@ -160,15 +257,18 @@ ensure_boot_table() ->
end.
clean_table() ->
- ets:new(?MODULE, [named_table, public, ordered_set]).
+ delete_boot_table(),
+ case ets:info(?MODULE) of
+ undefined ->
+ ets:new(?MODULE, [named_table, public, ordered_set]);
+ _ ->
+ ok
+ end.
load_table() ->
case ets:file2tab(boot_file(), [{verify, true}]) of
- {error, _} -> error(fuck);
- {ok, _Tab} -> ok,
- io:format("Starting with pre-loaded boot table:~n~p~n"
- "----------------~n",
- [ets:tab2list(?MODULE)])
+ {error, _} -> clean_table();
+ {ok, _Tab} -> ok
end.
save_boot_table() ->
@@ -190,14 +290,17 @@ handle_app_error(Term) ->
throw({Term, App, Reason})
end.
-run_boot_step({StepName, Attributes}, StepType) ->
- case catch {StepType, already_run(StepName)} of
- {rabbit_clean_step, _} -> run_it(StepName, Attributes);
- {_, false} -> run_it(StepName, Attributes);
- {_, true} -> ok
- end.
+run_boot_step({_, _, Attributes}, rabbit_cleanup_step) ->
+ run_step(Attributes);
+run_boot_step({_, StepName, Attributes}, rabbit_boot_step) ->
+ case catch already_run(StepName) of
+ false -> ok = run_step(Attributes),
+ mark_complete(StepName);
+ true -> ok
+ end,
+ ok.
-run_it(StepName, Attributes) ->
+run_step(Attributes) ->
case [MFA || {mfa, MFA} <- Attributes] of
[] ->
ok;
@@ -205,7 +308,7 @@ run_it(StepName, Attributes) ->
[try
apply(M,F,A)
of
- ok -> mark_complete(StepName);
+ ok -> ok;
{error, Reason} -> boot_error(Reason, not_available)
catch
_:Reason -> boot_error(Reason, erlang:get_stacktrace())
@@ -239,10 +342,10 @@ log_location(Type) ->
_ -> undefined
end.
-vertices(_Module, Steps) ->
- [{StepName, {StepName, Atts}} || {StepName, Atts} <- Steps].
+vertices(_Module, {AppName, Steps}) ->
+ [{StepName, {AppName, StepName, Atts}} || {StepName, Atts} <- Steps].
-edges(_Module, Steps) ->
+edges(_Module, {_AppName, Steps}) ->
[case Key of
requires -> {StepName, OtherStep};
enables -> {OtherStep, StepName}
@@ -265,7 +368,7 @@ sort_boot_steps(UnsortedSteps) ->
digraph:delete(G),
%% Check that all mentioned {M,F,A} triples are exported.
case [{StepName, {M,F,A}} ||
- {StepName, Attributes} <- SortedSteps,
+ {_App, StepName, Attributes} <- SortedSteps,
{mfa, {M,F,A}} <- Attributes,
not erlang:function_exported(M, F, length(A))] of
[] -> SortedSteps;